Android通过ACTION_OPEN_DOCUMENT_TREE选文件夹获URI后如何搜索文件
问题根因
你遇到的报错核心原因是:ACTION_OPEN_DOCUMENT_TREE返回的content://格式URI属于安卓存储访问框架(SAF)的专用标识,不存在可以直接传给File类使用的传统文件系统路径。不管是解码URI拼接路径,还是取DocumentFile.name字段当路径,本质都是把SAF的标识错误当成了文件系统路径,自然会报open failed: ENOENT错误。
你写的遍历代码错误点非常明确:tree.name返回的只是选中文件夹本身的名称(比如你选的midi文件夹,这个字段就只返回字符串midi),根本不是完整路径,传给File类当然找不到任何对应文件。
另外从安卓4.4开始,第三方应用默认就没有直接通过File类访问SD卡任意路径的权限,安卓10+强制分区存储后,这个限制在内置存储非应用专属目录也同样生效,就算你硬凑出了真实路径,没有对应权限一样访问失败。
正确实现目录遍历/文件操作的方法
不需要找所谓的“真实路径”,直接用SAF配套的API完成所有操作即可,步骤如下:
- 首先持久化目录访问权限,避免应用重启后权限失效
- 通过
DocumentFile.fromTreeUri拿到选中目录的实例 - 调用
DocumentFile自带的listFiles()方法递归遍历目录,所有文件读写操作通过ContentResolver结合文件URI完成,不要使用File类。
对应实现代码(Kotlin):
// 在选择器回调中执行 // 1. 持久化拿到的读写权限 val takeFlags = intent.flags and ( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) contentResolver.takePersistableUriPermission(selectedTreeUri, takeFlags) // 2. 获取选中目录的DocumentFile实例 val rootDir = DocumentFile.fromTreeUri(this, selectedTreeUri) ?: return // 3. 递归遍历目录下所有文件 fun traverseDirectory(dir: DocumentFile) { dir.listFiles().forEach { entry -> when { entry.isDirectory -> { println("子目录:${entry.name}") // 递归遍历子目录 traverseDirectory(entry) } entry.isFile -> { println("文件:${entry.name},大小:${entry.length()},MIME类型:${entry.type}") // 读取文件示例: // contentResolver.openInputStream(entry.uri)?.use { input -> // 在这里处理输入流即可 // } // 写入文件示例: // contentResolver.openOutputStream(entry.uri)?.use { output -> // 在这里写输出流 // } } } } } // 启动遍历 traverseDirectory(rootDir)
注意事项
- 不要浪费时间尝试把content URI转成文件路径:不同设备SD卡挂载点不统一,且就算拼出了路径,应用也没有
File类访问该路径的权限,完全是无效操作。 - 所有对授权目录下内容的操作,都通过
DocumentFile或者ContentResolver搭配返回的URI完成,这是官方支持的唯一合规方式,在所有安卓版本、内外置存储上都能正常工作。
内容的提问来源于stack exchange,提问作者peterc
相关产品推荐
相关产品推荐

