北辰遴选头像
关注

Android Content URI解析:应对分区存储与厂商文件管理器的兼容性挑战

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... (来自第三方应用,如浏览器)

这个机制的核心优势是 安全 抽象

  1. 安全 :应用无需申请 READ_EXTERNAL_STORAGE 运行时权限,就能访问用户授权的特定文件。权限是临时的(默认)或持久的(通过 takePersistableUriPermission 获取)。
  2. 抽象 :应用无需关心文件实际存储在设备的哪个物理位置(是内部存储、SD卡,还是某个云盘应用的虚拟文件系统),只需通过标准的 ContentResolver API 操作即可。

2.2 分区存储下的路径“黑盒化”

在 Android 10 及更高版本,即使你的应用拥有 READ_EXTERNAL_STORAGE 权限,通过 MediaStore 查询到的 _data 字段也 不一定 是真

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_27014595/article/details/163350486

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--