使用ACTION_OPEN_DOCUMENT_TREE时加载错误,无法选择USB驱动器目录
解决SAF选择USB驱动器时「Can't load content at the moment」错误
这个问题不少开发者都碰到过,属于SAF与系统存储交互的边缘场景问题,不一定完全是系统bug,结合你提供的代码,我们可以从逻辑优化、兼容性处理两个方向解决,避免使用权限过大的All Files Access。
问题根源分析
直接跳转USB卷的兼容性问题
你通过usbDriveVolume.createOpenDocumentTreeIntent()直接跳转至USB卷的逻辑,在部分厂商定制的Android系统中可能失效——比如系统SAF未正确映射USB卷的路径、卷的挂载状态存在延迟,或者预授权标志的传递时机不匹配,导致SAF无法加载对应目录。USB/SD卡区分逻辑的局限性
isRemovableVolumeAnSdCard()中依赖固定的设备节点前缀/dev/block/vold/public:179判断SD卡,不同厂商的存储设备命名规则差异很大:有些USB驱动器可能使用相同前缀,部分SD卡则用其他编号,这会导致误判USB卷为SD卡,进而跳转到错误的目录引发加载失败。
优化方案
1. 调整SAF跳转逻辑,增加错误 fallback
取消强制直接跳转USB卷的逻辑,优先引导用户手动选择,同时保留自动跳转的尝试,但增加失败后的降级处理:
private fun promptSAFPermissions() { val storageManager = requireActivity().getSystemService(Context.STORAGE_SERVICE) as StorageManager val storageVolumes = storageManager.recentStorageVolumes val usbDriveVolume = storageVolumes.firstOrNull { !it.isPrimary && it.isRemovable && it.state == Environment.MEDIA_MOUNTED && !isRemovableVolumeAnSdCard(it) } val intent = usbDriveVolume?.takeIf { it.state == Environment.MEDIA_MOUNTED } ?.createOpenDocumentTreeIntent() ?: Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) requireActivity().startActivityForResult(intent, REQUEST_CODE_SAF_PERMISSION) } // 在onActivityResult中处理失败场景 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SAF_PERMISSION && resultCode == Activity.RESULT_CANCELED) { // 自动跳转失败,重新打开通用SAF选择器并提示用户手动选择USB驱动器 val fallbackIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) fallbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) startActivityForResult(fallbackIntent, REQUEST_CODE_SAF_PERMISSION_FALLBACK) // 弹出Toast提示用户选择USB驱动器 Toast.makeText(context, "请手动选择USB驱动器根目录", Toast.LENGTH_SHORT).show() } }
2. 改进USB/SD卡区分逻辑,增强兼容性
去掉固定设备节点前缀的判断,改用更通用的特征:
fun isRemovableVolumeAnSdCard(volume: StorageVolume): Boolean { PKDrive.instance.uuidIsAnSdCardMap[volume.uuid]?.let { return it } // 优先通过描述判断 val description = volume.getDescription(App.appContext) .trim() .replace(" ", "") .lowercase(Locale.ROOT) if (description.contains("sdcard")) return true // 增加USB关键词反向排除 if (description.contains("usb") || description.contains("u盘")) return false if (volume.uuid == null) return false var sdCardFound = false runCatching { Runtime.getRuntime().exec("mount").inputStream.use { inputStream -> BufferedReader(InputStreamReader(inputStream)).forEachLine { line -> if (line.contains(volume.uuid!!, true) && line.contains("fat") && line.contains("media_rw")) { // 通过mount信息中的"sd"或"mmc"关键词判断SD卡,USB通常不含这些 sdCardFound = line.contains("sd") || line.contains("mmc") return@forEachLine } } } } PKDrive.instance.uuidIsAnSdCardMap[volume.uuid!!] = sdCardFound return sdCardFound }
3. 增加用户引导与异常提示
- 在调用SAF前,提示用户确认USB驱动器已正确挂载;
- 若SAF加载失败,引导用户重新插拔USB设备,或进入系统设置检查存储状态。
内容的提问来源于stack exchange,提问作者Gavin Wright
相关产品推荐
相关产品推荐

