You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ACTION_OPEN_DOCUMENT_TREE时加载错误,无法选择USB驱动器目录

解决SAF选择USB驱动器时「Can't load content at the moment」错误

这个问题不少开发者都碰到过,属于SAF与系统存储交互的边缘场景问题,不一定完全是系统bug,结合你提供的代码,我们可以从逻辑优化、兼容性处理两个方向解决,避免使用权限过大的All Files Access。

问题根源分析

  1. 直接跳转USB卷的兼容性问题
    你通过usbDriveVolume.createOpenDocumentTreeIntent()直接跳转至USB卷的逻辑,在部分厂商定制的Android系统中可能失效——比如系统SAF未正确映射USB卷的路径、卷的挂载状态存在延迟,或者预授权标志的传递时机不匹配,导致SAF无法加载对应目录。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:06:20