文件管理器的组成

调用系统自带的文件管理器有如下几个选项,分为两类

  • 系统自带的文件管理器
  • 第三方集成到系统管理器中

下面是调用选择文件方法后,调用系统文件管理器出来的界面
网上可以搜到很多 Uri 转路径的方法,但都是互相抄袭根本没经过验证的。现在最新是 Android 10,而网上那些方法,大多连 Android 7 引入文件权限(fileProvider)都不支持。本着认真仔细的研究,我安装了如下文件浏览器。

不同类别 Uri 的格式

系统自带的文件管理器

列名 uri
1. 图片 content://com.android.providers.media.documents/document/image%3A1598915
2. 视频 content://com.android.providers.media.documents/document/video%3A1594850
3. 音频 content://com.android.providers.media.documents/document/audio%3A920365
4. 最近 content://com.android.providers.media.documents/document/image%3A1598915
5. 下载 content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Ftest.txt
6. 手机(vivo X20A) content://com.android.externalstorage.documents/document/primary%3Atest.txt

第三方集成到系统管理器中

列名 uri
7. 视频 content://media/external/video/media/1594849
8. 选择曲目 content://media/external/audio/media/1151693
9. 录音机
10. 相册 content://media/external/images/media/1508729
11. WPS Office content://cn.wps.moffice_eng.fileprovider/external/test.txt
12. 文件管理器 file:///storage/emulated/0/test.txt
13. ES文件管理器 content://com.estrongs.files/storage/emulated/0/test.txt
14. ES文件管理器 content://com.gzhesnet.filemanager.FILE_PROVIDER/storage_root/test.txt
15. 文件管理器 content://com.jinghong.fileguanlijh.FILE_PROVIDER/storage_root/Android/log.txt
16. 文件管理 content://com.tencent.mtt.fileprovider/QQBrowser/test.txt
17. 7Zipper content://org.joa.zipperplus7//storage/emulated/0/test.txt

有通过上面的 Uri 文本可以发现有如下几个共同点

  1. 自带的 图片、音频、视频、最近 的提供者都是 com.android.providers.media.documents,%3A是冒号,后面跟的是 ID
  2. 下载的提供者是 com.android.providers.downloads.documents
  3. 手机文件夹的提供者是 com.android.externalstorage.documents
  4. 视频、曲目、相机 都来自 media
  5. 其它第三方文件管理器的格式基本一致:content:// 第三方 fileprovider / 暴露路径名 / 文件相对SD卡的具体路径。
  6. 只有一个估计是旧版的文件管理器使用的是 file 协议下的绝对路径。

代码讲解

uri 拆分

以 content://com.android.providers.media.documents/document/image%3A1598915 为例

函数 返回值 说明
uri.getScheme() content uri 协议
uri.getAuthority() com.android.providers.media.documents 文件提供器标识
uri.getPath() /document/image:159815 获取文件提供器之后的路径
uri.getPathSegments() [document, image:1598915] 获取文件提供器之后的路径,以File.separator切分成数组(自动解码)
Uri.withAppendedPath(uri, segment) content://com.android.providers.media.documents/document/image%3A1598915/segment 在uri最后添加一个子路径

uri 分类

  1. 系统的内容提供器
  2. 第三方的内容提供器
  3. 旧式file类型的uri

系统的内容提供器

系统内容提供器创建的文件都是来自 DocumentsProvider,使用 DocumentsContract.isDocumentUri(context, uri) 可以判断该 uri 是否来自 系统内容提供器,源码如下

首先 uri 的协议必须是 content。

public static final String SCHEME_CONTENT = "content";public static boolean isContentUri(@Nullable Uri uri) {return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
}

其次,必须来自文档提供器。通过文件提供器的意图去系统里查询,对于查询到的结果,一一比对。如果存在一致的提供器标识(authority),则返回 true

/*** Intent action used to identify {@link DocumentsProvider} instances. This* is used in the {@code <intent-filter>} of a {@code <provider>}.*/
public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";private static boolean isDocumentsProvider(Context context, String authority) {final Intent intent = new Intent(PROVIDER_INTERFACE);final List<ResolveInfo> infos = context.getPackageManager().queryIntentContentProviders(intent, 0);for (ResolveInfo info : infos) {if (authority.equals(info.providerInfo.authority)) {return true;}}return false;
}

DocumentsContract.isDocumentUri(Context context, Uri uri) 函数,先判断 uri 的类型是content,并且内容提供器是 DocumentsProvider。然后通过 uri.getPathSegments() 获取被 File.separator 分隔路径片段并自动解码(注意到:%3A是:;%2F是/ )所以我们上面给出的 uri 的 paths.size 一般都等于 2,然后再判断第一个片段的值是否是 document

@UnsupportedAppUsage
private static final String PATH_DOCUMENT = "document";
@UnsupportedAppUsage
private static final String PATH_TREE = "tree";public static boolean isDocumentUri(Context context, @Nullable Uri uri) {if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {final List<String> paths = uri.getPathSegments();if (paths.size() == 2) {return PATH_DOCUMENT.equals(paths.get(0));} else if (paths.size() == 4) {return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));}}return false;
}

DocumentsContract.getDocumentId(Uri uri) 这个函数就是获取 DocumentsProvider 提供器的 uri 的 path 部分,然后如果 第一个路径片段是 document,则取第二个部分

public static String getDocumentId(Uri documentUri) {final List<String> paths = documentUri.getPathSegments();if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {return paths.get(1);}if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))&& PATH_DOCUMENT.equals(paths.get(2))) {return paths.get(3);}throw new IllegalArgumentException("Invalid URI: " + documentUri);
}

%3A,也就是冒号之前的,共有5种格式,分别是

  • image
  • video
  • audio
  • raw
  • primary
//如果是document类型的Uri,通过document id处理,内部会调用Uri.decode(docId)进行解码
String docId = DocumentsContract.getDocumentId(uri);
//primary:Azbtrace.txt
//video:A1283522
String[] splits = docId.split(":");
Log.i(TAG, "docId " + docId + ", " + Arrays.toString(splits));
String type = null, id = null;
if(splits.length == 2) {type = splits[0];id = splits[1];
}

系统的 DocumentsProvider 一共有3种

  • com.android.providers.media.documents(image、video、audio、document)
  • com.android.providers.downloads.documents(raw)
  • com.android.externalstorage.documents(primary)
switch (uri.getAuthority()) {case "com.android.externalstorage.documents":if("primary".equals(type)) {path = Environment.getExternalStorageDirectory() + File.separator + id;}break;case "com.android.providers.downloads.documents":if("raw".equals(type)) {path = id;} else {Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));path = getMediaPathFromUri(context, contentUri, null, null);}break;case "com.android.providers.media.documents":Uri externalUri = null;switch (type) {case "image":externalUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;break;case "video":externalUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;break;case "audio":externalUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;break;case "document":externalUri = MediaStore.Files.getContentUri("external");break;}if(externalUri != null) {String selection = "_id=?";String[] selectionArgs = new String[]{ id };path = getMediaPathFromUri(context, externalUri, selection, selectionArgs);}break;
}

第三方的内容提供器

非 DocumentsProvider,并且协议类型是 content

if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) {……
}

以下这些字段的值都是 _data

  • MediaStore.Images.Media.DATA
  • MediaStore.Audio.Media.DATA
  • MediaStore.Video.Media.DATA
  • MediaStore.MediaColumns.DATA
ContentResolver resolver = context.getContentResolver();
String[] projection = new String[]{ MediaStore.MediaColumns.DATA };
Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, null);
if (cursor != null) {if (cursor.moveToFirst()) {try {int index = cursor.getColumnIndexOrThrow(projection[0]);if (index != -1) path = cursor.getString(index);Log.i(TAG, "getMediaPathFromUri query " + path);} catch (IllegalArgumentException e) {e.printStackTrace();path = null;} finally {cursor.close();}}
}

当通过相机或第三方文件管理器获取文件时就会报:IllegalArgumentException: column’_data’ does not exist. Available columns: [],而ES文件浏览器,通过resolver去query,会报RuntimeException。因此,它们要提出来在 query 之前做特殊处理。

对于第三方提供的路径,有2种情况

  • authority 后面直接的是实际路径
  • authority 后面跟了一个应用路径,然后是相对sd卡的路径
String path;
String authroity = uri.getAuthority();
path = uri.getPath();
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
if(!path.startsWith(sdPath)) {int sepIndex = path.indexOf(File.separator, 1);if(sepIndex == -1) path = null;else {path = sdPath + path.substring(sepIndex);}
}

旧式file类型的uri

协议类型是 file

if(ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme()) {……
}
方法 说明
Uri.parse(String uriString) uri 字符串转uri
Uri.fromFile(File file) 路径转 uri
uri.getPath() uri 转路径

假设路径为 storage/emulated/0/test.txt,通过 Uri.fromFile 就可以转成 file:///storage/emulated/0/test.txt
而 file:///storage/emulated/0/test.txt 这种格式的 Uri,直接调用 uri.getPath,就会得到 storage/emulated/0/test.txt

完整代码如下

public static String getPathFromUri(Context context, Uri uri) {String path = null;if (DocumentsContract.isDocumentUri(context, uri)) {//如果是document类型的Uri,通过document id处理,内部会调用Uri.decode(docId)进行解码String docId = DocumentsContract.getDocumentId(uri);//primary:Azbtrace.txt//video:A1283522String[] splits = docId.split(":");String type = null, id = null;if(splits.length == 2) {type = splits[0];id = splits[1];}switch (uri.getAuthority()) {case "com.android.externalstorage.documents":if("primary".equals(type)) {path = Environment.getExternalStorageDirectory() + File.separator + id;}break;case "com.android.providers.downloads.documents":if("raw".equals(type)) {path = id;} else {Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));path = getMediaPathFromUri(context, contentUri, null, null);}break;case "com.android.providers.media.documents":Uri externalUri = null;switch (type) {case "image":externalUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;break;case "video":externalUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;break;case "audio":externalUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;break;case "document":externalUri = MediaStore.Files.getContentUri("external");break;}if(externalUri != null) {String selection = "_id=?";String[] selectionArgs = new String[]{ id };path = getMediaPathFromUri(context, externalUri, selection, selectionArgs);}break;}} else if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) {path = getMediaPathFromUri(context, uri, null, null);} else if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {//如果是file类型的Uri(uri.fromFile),直接获取图片路径即可path = uri.getPath();}//确保如果返回路径,则路径合法return path == null ? null : (new File(path).exists() ? path : null);
}private static String getMediaPathFromUri(Context context, Uri uri, String selection, String[] selectionArgs) {String path;String authroity = uri.getAuthority();path = uri.getPath();String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();if(!path.startsWith(sdPath)) {int sepIndex = path.indexOf(File.separator, 1);if(sepIndex == -1) path = null;else {path = sdPah + path.substring(sepIndex);}}if(path == null || !new File(path).exists()) {ContentResolver resolver = context.getContentResolver();String[] projection = new String[]{ MediaStore.MediaColumns.DATA };Cursor cursor = resolver.query(uri, projection, selection, selectionArgs, null);if (cursor != null) {if (cursor.moveToFirst()) {try {int index = cursor.getColumnIndexOrThrow(projection[0]);if (index != -1) path = cursor.getString(index);Log.i(TAG, "getMediaPathFromUri query " + path);} catch (IllegalArgumentException e) {e.printStackTrace();path = null;} finally {cursor.close();}}}}return path;
}

Android Uri 转 Path相关推荐

  1. Android Uri to Path

    本文转载自:http://www.jianshu.com/p/f9a63fcc0b91 起因 Android在4.4之后的版本(包括4.4)中,从相册中选取图片返回Uri进行了改动.所以我们无法通过该 ...

  2. Android中Uri和Path之间的转换

    Android Uri to Path 现在遇到的常规Uri有两种: 媒体文件的Uri是content://, 表示这是一个数据库数据.去数据库查询正常返回 其他的文件Uri是file://, 表示这 ...

  3. Android中Uri 和Path之间的相互转化

    Android Uri to Path 现在遇到的常规Uri有两种: 媒体文件的Uri是content://, 表示这是一个数据库数据.去数据库查询正常返回. 其他的文件Uri是file://, 表示 ...

  4. android 本地地址转换为url,android本地mipmap图片转url、绝对路径转URL URL URI File Path 转换...

    标签: url uri file path File to URI: File file = ...; URI uri = file.toURI(); File to URL: File file = ...

  5. Android中Uri和path file三者的相互转换

    一.path转file File file = new File(path); 二.path转uri Uri uri = Uri.parse(path); 三.uri转path /*** 将URI路径 ...

  6. Android自定义控件系列--Path综述

    Android自定义控件系列–Path综述 项目源码 点击查看详情 Path 中文 释义为路径 然而它在自定义控件中却有着神一样的着色,这个神,是创造神奇效果的意思 1 Path 的创建 Path p ...

  7. Tomcat启动报异常:com.sun.org.apache.xerces.internal.util.URI$MalformedURIException: Path contains invalid

    报错 com.sun.org.apache.xerces.internal.util.URI$MalformedURIException: Path contains invalid characte ...

  8. Android URI简介

    就Android平台而言,URI主要分三个部分:scheme, authority and path.其中authority又分为host和port.格式如下: scheme://host:port/ ...

  9. android uri图片压缩,详解android 通过uri获取bitmap图片并压缩

    详解android 通过uri获取bitmap图片并压缩 很多人在调用图库选择图片时会在onactivityresult中用media.getbitmap来获取返回的图片,如下: uri mimage ...

最新文章

  1. 3D 引擎 Unity 2019.1 正式发布,引入新的轻量级渲染管道
  2. 用了那么多年的 Master 分支或因种族歧视而成为历史?
  3. Transformer性能被高估?DeepMind动态评估模型的时间泛化能力
  4. 全栈工程师之路-中级篇之小程序开发-第二章第五节小程序中的Promise
  5. di-tech2016_2016年Tech最佳愚人节笑话
  6. 图像目标检测(Object Detection)原理与实现(二)
  7. 抖音正考虑赴美IPO?字节跳动回应:消息不实
  8. Brocade博科光纤交换机之 常用命令
  9. gmail邮箱注册成功流程
  10. 队列元素逆置 数据结构 队列
  11. AUTOCAD——文件管理
  12. CISCO交换机备份和恢复配置文件
  13. Maven使用与学习
  14. BPSK信号产生(二)--载波调制
  15. fixed:error:0308010C:digital envelope routines::unsupported
  16. 大数据面试题Hbase篇
  17. 记Linux服务器中的 kdevtmpfsi 挖矿病毒
  18. 若依框架---权限控制角色设计
  19. docker搭建蚂蚁笔记
  20. 变频器按启动没反应_变频器通电后无反应的解决方案

热门文章

  1. tf Quaternion
  2. R语言:平稳性的检验1
  3. 实验二:用python实现SVM支持向量机并对鸢尾花数据集分类
  4. linux卸载unbound,linux下的unbound DNS服务器设置详解
  5. 两个卡方分布之和_机器学习算法数学基础之 —— 统计与概率论篇(3)
  6. a 标签之假链接 和假链接
  7. window location 之应用
  8. 10分钟快速搭建自己的服务器
  9. 布隆过滤器在hbase的应用
  10. Unity的UGUI中使用ETC1+Alpha的格式