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

已获权限仍在Android SD卡创建文件时触发IO异常问题咨询

解决Android中将文件从内部存储移动到SD卡时的创建文件IO异常问题

你遇到的这个IO异常,核心原因是Android 10(API 29)及以上版本的Scoped Storage机制限制了对可移除SD卡非私有目录的直接文件路径访问——哪怕你申请了传统的存储权限也不行。下面我来拆解问题并给出具体解决方案:

为什么直接用File类操作SD卡目录会失败?

  • 从Android 10开始,系统引入了Scoped Storage,App只能直接访问自身的私有目录(比如/storage/emulated/0/Android/data/你的包名/)和通过MediaStore访问媒体文件。对于可移除SD卡的自定义目录(比如你代码里的/storage/48EE-C144/chosenDirectory/),直接用File类的绝对路径进行写入操作会被系统拒绝。
  • 你获取的WRITE_EXTERNAL_STORAGE权限在Android 10+已经不再授予对外部存储全局写入的能力,仅能访问媒体文件和App私有目录。

正确的解决方案:使用Storage Access Framework(SAF)

要写入SD卡的自定义目录,你需要通过SAF让用户手动选择目标目录,获取该目录的访问权限,然后通过ContentResolver来创建文件和复制内容,而不是直接用File类。

步骤1:启动SAF让用户选择目标目录

private val REQUEST_CODE_PICK_DIRECTORY = 1001

fun pickTargetDirectory() {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
    startActivityForResult(intent, REQUEST_CODE_PICK_DIRECTORY)
}

步骤2:处理选择结果,保存目录的持久权限

在onActivityResult(或Jetpack的registerForActivityResult)中获取选中目录的Uri,并申请持久访问权限:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_PICK_DIRECTORY && resultCode == Activity.RESULT_OK) {
        data?.data?.let { treeUri ->
            // 申请持久访问权限,重启App后依然有效
            contentResolver.takePersistableUriPermission(
                treeUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
            // 保存treeUri,比如存入SharedPreferences,后续用来操作目录
            saveTreeUriToPrefs(treeUri.toString())
            // 调用文件移动方法
            moveFileToTargetDirectory(treeUri, oldPath)
        }
    }
}

步骤3:通过ContentResolver创建文件并复制内容

fun moveFileToTargetDirectory(treeUri: Uri, oldFilePath: String) {
    val oldFile = File(oldFilePath)
    if (!oldFile.exists()) return

    // 目标文件名,和原文件名一致
    val fileName = oldFile.name

    // 在选中的目录下创建新文件
    val docUri = DocumentsContract.createDocument(
        contentResolver,
        treeUri,
        "application/octet-stream", // 根据你的文件类型修改MIME类型
        fileName
    ) ?: return

    // 复制文件内容
    contentResolver.openOutputStream(docUri)?.use { outputStream ->
        oldFile.inputStream().use { inputStream ->
            inputStream.copyTo(outputStream)
        }
    }

    // 复制完成后删除原文件
    oldFile.delete()
}

额外注意事项

  • 如果你需要支持Android 9及以下版本,可以保留原有的File类操作逻辑,但要确保已经申请了WRITE_EXTERNAL_STORAGE动态权限。
  • 不要硬编码SD卡的绝对路径(比如/storage/48EE-C144/),因为不同设备的SD卡路径可能不同,SAF是唯一可靠的方式。

内容的提问来源于stack exchange,提问作者Michał Jabłoński

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:50:18