SAF文件选择、谷歌PhotoPicker图片视频选择与真实路径转换
一、构建选择文件与回调方法
//文件选择回调ActivityResultLauncher<String[]> pickFile = registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri->{if (uri != null) {Log.e("cxy", "返回的uri:" + uri);Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));}});//启动选择
pickFile.launch(new String[]{"*/*"})
//photopicker选择器回调(多选同理)ActivityResultLauncher<PickVisualMediaRequest> pickMedia =registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), uri -> {if (uri != null) {Log.e("cxy", "返回的uri:" + uri);Log.e("cxy","Path是:"+ FileUtil.getFilePathByUri(context,uri));} else {}});//photopicker选择器启动选择,选择模式:ImageOnly、VideoOnly、ImageAndVideo
pickMedia.launch(new PickVisualMediaRequest.Builder().setMediaType(isImg ? ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE : ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE).build()));
二、uri转真实路径工具类
备注:
1、针对Download目录先判空,为空则复制文件至内部路径再返回
2、SAF选择文件时,存储设备选择返回的uri路径为:
content://com.android.externalstorage.documents/document/primary%3ADocuments%2Fenvironment.pub
通过选择侧边栏中的图片、视频、音频、文档等分类进入选择文件目录时,返回的路径为:
content://com.android.providers.media.documents/document/image%3A1000010017
content://com.android.providers.media.documents/document/video%3A1000008421
content://com.android.providers.media.documents/document/audio%3A1000000212
content://com.android.providers.media.documents/document/document%3A1000009571
这时如果没有授予存储权限,则走复制方法返回路径,Android13以上可按需申请图片、视频、音频读取权限,但是针对doc、pdf等其它类型文件无法返回真实路径,除非授予管理所有文件权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
权限:使用photopicker时,需要Android13以上按需申请以下使用的权限,否则MediaStore不返回真实路径,无权限情况下走复制方法返回路径
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" /><uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/><uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/><uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>//Activity中权限申请与回调ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), map->{//处理权限结果{android.permission.READ_MEDIA_IMAGES=true, android.permission.READ_MEDIA_VIDEO=true}});if (!PmUtil.hasStoragePm(this)){PmUtil.requestStoragePm(permissionLauncher);}//权限申请工具类
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;import androidx.activity.result.ActivityResultLauncher;
import androidx.core.content.ContextCompat;public class PmUtil {public static boolean hasStoragePm(Context context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED;} else {return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;}}public static void requestStoragePm(ActivityResultLauncher<String[]> launcher) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {launcher.launch(new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO});} else {launcher.launch(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE});}}
}
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;import androidx.loader.content.CursorLoader;import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;public class FileUtil {public static String getFilePathByUri(Context context, Uri uri) {// 以 file:// 开头的if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {return uri.getPath();}// 以/storage开头的也直接返回if (isOtherDocument(uri)) {return uri.getPath();}// 版本兼容的获取!String path;if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {path = getFilePathByUri_BELOWAPI11(context, uri);if (path != null) {Log.e("cxy", "getFilePathByUri_BELOWAPI11获取到的路径为:" + path);return path;}}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {path = getFilePathByUri_API11to18(context, uri);if (path != null) {Log.e("cxy", "getFilePathByUri_API11to18获取到的路径为:" + path);return path;}}path = getFilePathByUri_API19(context, uri);Log.e("cxy", "getFilePathByUri_API19获取到的路径为:" + path);return path;}private static String getFilePathByUri_BELOWAPI11(Context context, Uri uri) {// 以 content:// 开头的,比如 content://media/extenral/images/media/17766if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {String path = null;String[] projection = new String[]{MediaStore.Images.Media.DATA};Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);if (cursor != null) {if (cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);if (columnIndex > -1) {path = cursor.getString(columnIndex);}}cursor.close();}return path;}return null;}private static String getFilePathByUri_API11to18(Context context, Uri contentUri) {String[] projection = {MediaStore.Images.Media.DATA};String result = null;CursorLoader cursorLoader = new CursorLoader(context, contentUri, projection, null, null, null);Cursor cursor = cursorLoader.loadInBackground();if (cursor != null) {int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);cursor.moveToFirst();result = cursor.getString(column_index);cursor.close();}return result;}private static String getFilePathByUri_API19(Context context, Uri uri) {// 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {if (DocumentsContract.isDocumentUri(context, uri)) {if (isExternalStorageDocument(uri)) {// ExternalStorageProviderString docId = DocumentsContract.getDocumentId(uri);String[] split = docId.split(":");String type = split[0];if ("primary".equalsIgnoreCase(type)) {if (split.length > 1) {return Environment.getExternalStorageDirectory() + "/" + split[1];} else {return Environment.getExternalStorageDirectory() + "/";}// This is for checking SD Card}} else if (isDownloadsDocument(uri)) {//下载内容提供者时应当判断下载管理器是否被禁用int stateCode = context.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");if (stateCode != 0 && stateCode != 1) {return null;}String id = DocumentsContract.getDocumentId(uri);// 如果出现这个RAW地址,我们则可以直接返回!if (id.startsWith("raw:")) {return id.replaceFirst("raw:", "");}if (id.contains(":")) {String[] tmp = id.split(":");if (tmp.length > 1) {id = tmp[1];}}Uri contentUri = Uri.parse("content://downloads/public_downloads");Log.e("cxy", "测试打印Uri: " + uri);try {contentUri = ContentUris.withAppendedId(contentUri, Long.parseLong(id));} catch (Exception e) {e.printStackTrace();}String path = getDataColumn(context, contentUri, null, null);if (path != null) return path;// 兼容某些特殊情况下的文件管理器!String fileName = getFileNameByUri(context, uri);if (fileName != null) {path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;File pathFile = new File(path);if (!pathFile.exists()) {path = getFilePathForCopy(context, uri);}return path;}} else if (isMediaDocument(uri)) {// MediaProviderString docId = DocumentsContract.getDocumentId(uri);String[] split = docId.split(":");String type = split[0];Uri contentUri;if ("image".equals(type)) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if ("video".equals(type)) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if ("audio".equals(type)) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;} else {contentUri = MediaStore.Files.getContentUri("external");}String selection = "_id=?";String[] selectionArgs = new String[]{split[1]};String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);if (resultPath == null) {resultPath = getFilePathForCopy(context, uri);}return resultPath;}} else {//content://media/picker/0/com.android.providers.media.photopicker/media/1000008424//photopicker情况,有权限则返回真实路径,无权限则返回复制后的内部路径,但复制后的名称是IDString uriPath = uri.getPath();String id = "";if (uriPath != null) {String[] strings = uriPath.split("/");id = strings[strings.length - 1];}String selection = "_id=?";String[] selectionArgs = new String[]{id};Uri contentUri = MediaStore.Files.getContentUri("external");String resultPath = getDataColumn(context, contentUri, selection, selectionArgs);if (resultPath == null) {resultPath = getFilePathForCopy(context, uri);}return resultPath;}}return null;}private static String getFileNameByUri(Context context, Uri uri) {String relativePath = getFileRelativePathByUri_API18(context, uri);if (relativePath == null) relativePath = "";final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {if (cursor != null && cursor.moveToFirst()) {int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);String path = relativePath + cursor.getString(index);cursor.close();return path;}}return null;}private static String getFileRelativePathByUri_API18(Context context, Uri uri) {final String[] projection;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {projection = new String[]{MediaStore.MediaColumns.RELATIVE_PATH};try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) {if (cursor != null && cursor.moveToFirst()) {int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH);String path = cursor.getString(index);cursor.close();return path;}}}return null;}private static String getFilePathForCopy(Context context, Uri uri) {try {Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);returnCursor.moveToFirst();String name = (returnCursor.getString(nameIndex));File file = new File(context.getCacheDir(), name);InputStream inputStream = context.getContentResolver().openInputStream(uri);FileOutputStream outputStream = new FileOutputStream(file);int read = 0;int maxBufferSize = 1024 * 1024;int bytesAvailable = inputStream.available();int bufferSize = Math.min(bytesAvailable, maxBufferSize);final byte[] buffers = new byte[bufferSize];while ((read = inputStream.read(buffers)) != -1) {outputStream.write(buffers, 0, read);}returnCursor.close();inputStream.close();outputStream.close();return file.getPath();} catch (Exception e) {e.printStackTrace();}return null;}private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {final String column = MediaStore.Images.Media.DATA;final String[] projection = {column};try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {if (cursor != null && cursor.moveToFirst()) {final int column_index = cursor.getColumnIndexOrThrow(column);String path = cursor.getString(column_index);cursor.close();return path;}} catch (IllegalArgumentException iae) {iae.printStackTrace();}return null;}private static boolean isExternalStorageDocument(Uri uri) {return "com.android.externalstorage.documents".equals(uri.getAuthority());}private static boolean isOtherDocument(Uri uri) {// 以/storage开头的也直接返回if (uri != null && uri.getPath() != null) {String path = uri.getPath();if (path.startsWith("/storage")) {return true;}if (path.startsWith("/external_files")) {return true;}}return false;}private static boolean isDownloadsDocument(Uri uri) {return "com.android.providers.downloads.documents".equals(uri.getAuthority());}private static boolean isMediaDocument(Uri uri) {return "com.android.providers.media.documents".equals(uri.getAuthority());}//如果是复制的文件,完成后清除缓存public static void deleteCacheFile(Context context, String path) {if (path == null) {return;}File file = new File(path);if (file.exists() && context.getCacheDir().equals(file.getParentFile())) {Log.e("cxy", "删除成功?" + file.delete());}}
}
相关文章:
SAF文件选择、谷歌PhotoPicker图片视频选择与真实路径转换
一、构建选择文件与回调方法 //文件选择回调ActivityResultLauncher<String[]> pickFile registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri->{if (uri ! null) {Log.e("cxy", "返回的uri:" uri);Log.e("cxy&q…...
java可变参数
前言 我们虽然能够用重载实现,但多个参数无法弹性匹配 代码 class mycalculator{//下面的四个calculate方法构成了重载//计算2个数的和,3个数的和,4,5,6个数的和// public void calculate(int n1){// System.out.…...
Flutter 中的 Expanded 小部件:全面指南
Flutter 中的 Expanded 小部件:全面指南 在 Flutter 中,Expanded 是一个用于控制子控件占据可用空间的布局小部件,通常与 Row、Column 或 Flex 等父级布局小部件一起使用。Expanded 允许你创建灵活的布局,其中子控件可以按照指定…...

[Kubernetes] KubeKey 部署 K8s v1.28.8
文章目录 1.K8s 部署方式2.操作系统基础配置3.安装部署 K8s4.验证 K8s 集群5.部署测试资源 1.K8s 部署方式 kubeadm: kubekey, sealos, kubespray二进制: kubeaszrancher 2.操作系统基础配置 主机名内网IP外网IPmaster192.168.66.2139.198.9.7node1192.168.66.3139.198.40.17…...
C# 与 Qt 的对比分析
C# 与 Qt 的对比分析 目录 C# 与 Qt 的对比分析 1. 语言特性 2. 开发环境 3. 框架和库 4. 用户界面设计 5. 企业级应用 6. 性能考量 在软件开发领域,C# 和 Qt 是两种常用的技术栈,它们分别在.NET平台和跨平台桌面应用开发中占据重要位置。本文将深…...

MapReduce | 二次排序
1.需求 主播数据--按照观众人数降序排序,如果观众人数相同,按照直播时长降序 # 案例数据 用户id 观众人数 直播时长 团团 300 1000 小黑 200 2000 哦吼 400 7000 卢本伟 100 6000 八戒 250 5000 悟空 100 4000 唐僧 100 3000 # 期望结果 哦吼 4…...

Java后端初始化项目(项目模板)
介绍 emmmm,最近看了一些网络资料,也是心血来潮,想自己手工搭建一个java后端的初始化项目模板来简化一下开发,也就发一个模板的具体制作流程,(一步一步搭建,从易到难) okÿ…...
electron 多窗口 vuex/pinia 数据状态同步简易方案(利用 LocalStorage)
全局 stroe 添加 mutations 状态同步方法 // 用于其他窗口同步 vuex 中的 DeviceTcpDataasyncDeviceTcpData(state: StateType, data: any) {state.deviceTcpData data},App.vue 里 onMounted(() > {console.log("App mounted");/*** vuex 多窗口 store 同步*//…...
自定义数据集图像分类实现
模型训练 要使用自己的图片分类数据集进行训练,这意味着数据集应该包含一个目录,其中每个子目录代表一个类别,子目录中包含该类别的所有图片。以下是一个使用Keras和TensorFlow加载自定义图片数据集进行分类训练的例子。 我们自己创建的数据集…...
【C++】手搓读写ini文件源码
【C】手搓读写ini文件源码 思路需求:ini.hini.cppconfig.confmian.cpp 思路 ini文件是一种系统配置文件,它有特定的格式组成。通常做法,我们读取ini文件并按照ini格式进行解析即可。在c语言中,提供了模板类的功能,所以…...

undolog
undolog回滚段 undolog执行的时间:在执行器操作bufferpool之前。 undolog页...
项目文档分享
Hello , 我是小恒。提前祝福妈妈母亲节快乐 。 本文写一篇初成的项目文档 (不是README.md哈),仅供参考 项目名称 脚本存储网页 项目简介 本项目旨在创建一个网页,用于存储和展示各种命令,用户可以通过粘贴复制命令到…...

【深耕 Python】Quantum Computing 量子计算机(5)量子物理概念(二)
写在前面 往期量子计算机博客: 【深耕 Python】Quantum Computing 量子计算机(1)图像绘制基础 【深耕 Python】Quantum Computing 量子计算机(2)绘制电子运动平面波 【深耕 Python】Quantum Computing 量子计算机&…...
手写Spring5【笔记】
Spring5【笔记】 前言前言推荐Spring5【笔记】1介绍2手写 最后 前言 这是陈旧已久的草稿2022-12-01 23:32:59 这个是刷B站的时候,看到一个手写Spring的课程。 最后我自己好像运行不了,就没写。 现在2024-5-12 22:22:46,发布到[笔记]专栏中…...

2024中国(重庆)机器人展览会8月举办
2024中国(重庆)机器人展览会8月举办 邀请函 主办单位: 中国航空学会 重庆市南岸区人民政府 招商执行单位: 重庆港华展览有限公司 2024中国重庆机器人展会将汇聚机器人全产业链知名企业,世界科技领先的生产制造企业与来自多个国家和地区…...
Apache 开源项目文档中心 (英文 + 中文)
进度:持续更新中。。。 Apache Ambari 2.7.5 Apache Ambari Installation 2.7.5.0 (latest)Apache Ambari Installation 2.7.5.0 中文版 (latest) Apache DolphinScheduler Apache DolphinScheduler 1.2.0 中文版Apache DolphinScheduler 1.2.1 中文版...
蓝桥杯 算法提高 ADV-1164 和谐宿舍 python AC
贪心,二分 同类型题:蓝桥杯 算法提高 ADV-1175 打包 def judge(x):wood 0max_val 0ans_len 0for i in ll:if i > x:return Falseelif max(max_val, i) * (ans_len 1) < x:max_val max(max_val, i)ans_len 1else:wood 1max_val ians_len …...

Dragonfly 拓扑的路由算法
Dragonfly 拓扑的路由算法 1. Dragonfly 上的路由 (1)最小路由(2)非最小路由 2. 评估3. 存在问题 (1)吞吐量限制(2)较高的中间延迟 references Dragonfly 拓扑的路由算法 John Kim, William J. Dally 等人在 2008 年的 ISCA 中提出技术驱动、高度可扩展的 Dragonfly 拓扑。而…...

android基础-服务
同样使用intent来传递服务 oncreate是服务第一次启动调用,onStartCommand是服务每次启动的时候调用,也就是说服务只要启动后就不会调用oncreate方法了。可以在myservice中的任何位置调用stopself方法让服务停止下来。 服务生命周期 前台服务类似于通知会…...
mysql 事物
MySQL中的事务(Transaction)是一个确保数据完整性和一致性的重要概念。它将一组SQL操作捆绑在一起,当作一个单一的工作单元来执行。事务具备以下四个关键特性,即ACID特性: 原子性(Atomicity)&am…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...