1. 从一次文件分享崩溃说起:Content URI的“薛定谔”路径
那天下午,我正在调试一个图片编辑模块,功能很简单:用户从系统相册或文件管理器选择一张图片,应用获取到它的真实路径,然后加载、处理。在测试机上一切顺利,直到我把APK装到了一台某主流品牌的新款手机上。用户选择文件后,应用直接闪退了。日志里赫然躺着一行 FileNotFoundException ,而我要打开的文件路径,看起来却非常“诡异”: content://com.xxx.browser.fileprovider/external_root/download/IMG_20231001.jpg 。
我相信很多Android开发者都见过这种以 content:// 开头的URI,而不是熟悉的 /storage/emulated/0/Download/... 。我们通常称之为 Content URI 或 Content Provider URI。直觉告诉我们,这玩意儿得用 ContentResolver 去打开流读取。但我的场景偏偏需要文件的绝对路径——可能是要调用一个只接受文件路径字符串的底层Native库,也可能是要把文件路径传递给另一个仅支持 file:// 协议的应用。
于是,我自然而然地想到了 Uri 转 File 。网上搜一下,满屏的“一行代码解决”:
fun uriToFile(context: Context, uri: Uri): File? {
val filePathColumn = arrayOf(MediaStore.MediaColumns.DATA)
val cursor = context.contentResolver.query(uri, filePathColumn, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val columnIndex = it.getColumnIndex(filePathColumn[0])
return File(it.getString(columnIndex))
}
}
return null
}
这段代码在早些年(Android 6.0 / API 23 以前)几乎是标准答案。它通过查询 MediaStore 来获取 _data 字段,这个字段存储的就是文件在存储上的绝对路径。然而,当我满怀希望地在 Android 10(API 29)的设备上运行它时, cursor 可能非空,但 _data 字段查出来经常是 null ,或者返回一个你无法直接访问的、位于应用私有目录的路径。这就是 Android 分区存储(Scoped Storage)引入后带来的核心变化之一:为了增强用户隐私和安全,应用对文件系统的直接路径访问受到了严格限制。
那么,在分区存储时代,我们是不是就束手无策了?当然不是。官方推荐我们使用 ContentResolver.openInputStream(uri) 来获取文件内容。但“绝对路径”这个需求依然顽固地存在。这时,一个看似更“高级”的方案浮出水面:使用 DocumentFile 或者 ContentResolver 的 takePersistableUriPermission 来获取持久化权限,然后……等等,我们好像离“绝对路径”越来越远了。
就在我研究各种 DocumentFile.fromSingleUri 和 FileDescriptor 的时候,我掉进了一个更大的坑——这个坑的挖掘者,正是我们每天都会使用的、各个手机厂商“精心定制”的 系统自带文件管理器 。
2. 深入Content URI:不只是MediaStore的戏码
在开始吐槽文件管理器之前,我们必须先理解 Content URI 到底是什么,以及为什么它会成为现代Android文件交互的“标准货币”。
2.1 Content URI的本质:一个安全的访问凭据
你可以把 Content URI 理解为一个指向数据的“门票”或“句柄”,而不是数据本身的位置。这张门票由数据的“提供者”(Content Provider)签发。最常见的提供者就是系统的 MediaStore ,它管理着图片、视频、音频等媒体文件。当你通过 Intent.ACTION_GET_CONTENT 或 Intent.ACTION_OPEN_DOCUMENT 请求用户选择文件时,系统(或文件管理器)返回给你的,就是这样一个由对应 Content Provider 签发的 URI。
它的格式通常是这样的: content://[authority]/[path_to_resource] 例如:
-
content://media/external/images/media/123(来自 MediaStore) -
content://com.android.externalstorage.documents/document/primary:Download/myfile.pdf(来自 Storage Access Framework, SAF) -
content://com.quark.browser.fileprovider/external_root/download/quarkdow...(来自第三方应用,如浏览器)
这个机制的核心优势是 安全 和 抽象 :
- 安全 :应用无需申请
READ_EXTERNAL_STORAGE运行时权限,就能访问用户授权的特定文件。权限是临时的(默认)或持久的(通过takePersistableUriPermission获取)。 - 抽象 :应用无需关心文件实际存储在设备的哪个物理位置(是内部存储、SD卡,还是某个云盘应用的虚拟文件系统),只需通过标准的
ContentResolverAPI 操作即可。
2.2 分区存储下的路径“黑盒化”
在 Android 10 及更高版本,即使你的应用拥有 READ_EXTERNAL_STORAGE 权限,通过 MediaStore 查询到的 _data 字段也 不一定 是真
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_27014595/article/details/163350486



