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

Android 10下外部SD卡文件创建与写入权限问题求助

解决Android可移除SD卡写入权限拒绝问题(EACCES)

你遇到的问题核心在于:Android 10(API 29)及以上版本对可移除SD卡的写入权限有特殊限制,即使你拥有WRITE_EXTERNAL_STORAGE权限并开启了requestLegacyExternalStorage,也无法直接写入可移除SD卡的根目录或非应用专属文件夹——但读取不受此限制,这就是为什么你的读功能正常、写功能失败的原因。

原因拆解

  • WRITE_EXTERNAL_STORAGE权限在Android 10+仅对内置外部存储(/storage/emulated/0)的公共目录有效,对可移除SD卡的共享目录完全失效。
  • requestLegacyExternalStorage="true"是兼容Android 10的临时方案,但同样只作用于内置外部存储,无法覆盖可移除SD卡的写入限制。
  • 可移除SD卡的根目录(如/storage/3066-3133)属于系统级共享存储区域,写入需要用户手动授权目录访问权限。

解决方案

方案1:使用存储访问框架(SAF)获取目录授权(推荐)

这是Android官方推荐的访问可移除SD卡共享目录的方式,需要让用户手动选择目标目录并授予读写权限:

  1. 启动SAF目录选择器
private static final int REQUEST_CODE_SD_DIR = 2;

// 调用此方法启动目录选择
private void openSDDirectoryPicker() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    startActivityForResult(intent, REQUEST_CODE_SD_DIR);
}
  1. 处理授权结果并写入文件
    在onActivityResult中获取授权的目录Uri,然后用ContentResolver写入文件(替代原来的FileWriter):
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_SD_DIR && resultCode == RESULT_OK && data != null) {
        Uri treeUri = data.getData();
        // 持久化权限,重启应用后依然有效
        getContentResolver().takePersistableUriPermission(treeUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        
        // 写入title.txt到授权的SD卡目录
        writeFileWithSAF(treeUri, "title.txt", " write text ");
    }
}

private void writeFileWithSAF(Uri treeUri, String fileName, String content) {
    DocumentFile rootDir = DocumentFile.fromTreeUri(this, treeUri);
    if (rootDir == null) return;
    
    DocumentFile targetFile = rootDir.findFile(fileName);
    if (targetFile == null) {
        targetFile = rootDir.createFile("text/plain", fileName);
    }
    
    try (OutputStream outputStream = getContentResolver().openOutputStream(targetFile.getUri())) {
        outputStream.write(content.getBytes());
        popupToast("write success");
    } catch (IOException e) {
        e.printStackTrace();
        popupToast("write fail");
    }
}

方案2:写入SD卡的应用专属目录(无需额外授权)

如果你的业务允许将文件放在应用私有目录下,可以直接写入可移除SD卡的Android/data/[你的包名]/files/目录,这个目录属于应用私有空间,无需额外权限:

修改你的writeFile方法:

public void writeFile() {
    String fileTitle = "title.txt";
    // 获取SD卡应用专属目录,下标1对应可移除SD卡,需先判断是否存在
    File[] externalFilesDirs = getExternalFilesDirs(null);
    if (externalFilesDirs.length < 2 || externalFilesDirs[1] == null) {
        popupToast("SD card not available");
        return;
    }
    File sdAppDir = externalFilesDirs[1];
    
    File file = new File(sdAppDir, fileTitle);
    try {
        if (!file.exists()) {
            file.createNewFile();
        }
        FileWriter writer = new FileWriter(file, false);
        String str = " write text ";
        writer.write(str);
        writer.close();
        popupToast("write success");
    } catch (IOException e) {
        e.printStackTrace();
        popupToast("write fail");
    }
}

⚠️ 注意:卸载应用时,这个目录下的文件会被自动删除。

方案3:升级到targetSdk 31并申请MANAGE_EXTERNAL_STORAGE权限(不推荐)

如果你的应用是文件管理器类的应用,可以考虑升级到targetSdk 31,然后申请MANAGE_EXTERNAL_STORAGE权限,这个权限允许完全访问所有存储设备,但需要在应用商店说明用途,且可能无法通过审核。

额外检查点

  • 确认build.gradle中的targetSdkVersion确实是29,同步项目后生效。
  • 检查设备是否开启了SD卡的物理写保护(部分SD卡有实体开关)。

内容的提问来源于stack exchange,提问作者Redwings

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 16:57:45