关于ACTION_OPEN_DOCUMENT_TREE遍历大量文件的性能警告及替代方案咨询
问题解答
关于ACTION_OPEN_DOCUMENT_TREE遍历性能警告的严重性
这个警告的核心原因是SAF(存储访问框架)依赖跨进程调用系统存储服务,不像直接访问内部存储那样直接。当遍历大量文件时,频繁的IPC(进程间通信)会明显增加耗时——比如处理上千个文件时,遍历时间可能比直接访问长几倍,若在主线程操作还容易触发ANR(应用无响应)。但也不是完全不能用,只要做好异步处理和优化,大部分场景都能正常运行。
替代方案及优化建议
1. 修复MediaStore访问外置SD卡的问题
你之前用MediaStore没获取到内容,大概率是没处理多存储卷的情况。Android 10+的MediaStore支持访问可移除SD卡的媒体,步骤如下:
- 申请对应权限:Android 12及以下用
READ_EXTERNAL_STORAGE,Android 13+用READ_MEDIA_IMAGES/READ_MEDIA_VIDEO。 - 获取所有存储卷名称,遍历查询每个卷的媒体:
val volumeNames = MediaStore.getExternalVolumeNames(context) for (volumeName in volumeNames) { val contentUri = MediaStore.Files.getContentUri(volumeName) // 筛选图片和视频的查询条件 val projection = arrayOf( MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.MIME_TYPE ) val selection = "${MediaStore.Files.FileColumns.MEDIA_TYPE} IN (?, ?)" val selectionArgs = arrayOf( MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(), MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString() ) context.contentResolver.query(contentUri, projection, selection, selectionArgs, null)?.use { cursor -> // 处理查询到的媒体文件 } }
这样就能覆盖包括可移除SD卡在内的所有外部存储卷的媒体文件。
2. 优化SAF的使用方式(如果必须用的话)
- 异步处理:把遍历、文件读取操作放到后台线程(比如Kotlin协程的
IO调度器、Java的ExecutorService),绝对不要在主线程执行。 - 过滤文件类型:调用
DocumentFile.listFiles()前,先通过MIME类型过滤,只保留图片和视频,减少遍历数量:
val imageVideoFilter = DocumentsContract.Document.MIME_TYPE_DIR + " OR " + DocumentsContract.Document.MIME_TYPE_IMAGE + " OR " + DocumentsContract.Document.MIME_TYPE_VIDEO val files = documentTree.listFiles(imageVideoFilter)
- 缓存文件信息:把已经遍历到的文件的Uri、名称、类型等信息缓存到本地数据库(比如Room)或SharedPreferences,下次打开应用直接读取缓存,不用重新遍历。
- 分页/按需加载:不要一次性加载所有文件,比如用户滚动到列表底部再加载下一页内容,或者只加载当前目录的文件,进入子目录再加载对应内容。
3. 特殊场景:MANAGE_EXTERNAL_STORAGE权限(谨慎使用)
如果以上方法都不行,Android 11+可以申请MANAGE_EXTERNAL_STORAGE权限,这个权限允许应用直接访问所有外部存储文件,但Google Play对这个权限的审核非常严格,只有文件管理器、备份、杀毒这类核心功能需要的应用才能通过,非必要不建议使用。
内容的提问来源于stack exchange,提问作者windows 11
相关产品推荐
相关产品推荐

