Android应用SD卡写入权限问题:授权后仍遇SecurityException异常
解决Android中SAF授权后写入SD卡目录的SecurityException问题
你遇到的是Android Scoped Storage下的典型权限问题——虽然通过ACTION_OPEN_DOCUMENT_TREE获取了SD卡目录的写入权限,但直接用传统File流操作content://格式的URI会触发权限拒绝。从Android 10(API 29)开始,系统要求通过Storage Access Framework (SAF) 提供的专属API来操作授权目录,不能直接访问文件路径。
核心原因
你拿到的content://com.android.externalstorage.documents/tree/0FFF170E%3AMyDir/MyFile.MyExt是SAF的文档URI,并非传统文件系统路径。Android会阻止通过FileOutputStream直接写入这类URI,必须用ContentResolver配合DocumentFile完成文件创建和写入。
解决方案步骤
- 将授权目录URI转换为DocumentFile
把用户授权返回的目录tree URI转换成DocumentFile对象,这是SAF操作目录的入口。 - 定位已存在的目标子目录
如果用户授权的是SD卡根目录,需要先找到已存在的MyDir子目录。 - 通过SAF API创建文件并写入
用DocumentFile.createFile()创建文件,再通过ContentResolver.openOutputStream()获取输出流写入内容。
完整代码示例
// 假设你已从ACTION_OPEN_DOCUMENT_TREE回调中拿到授权的treeUri Uri treeUri = ...; // 示例:content://com.android.externalstorage.documents/tree/0FFF170E%3A // 1. 获取根目录的DocumentFile实例 DocumentFile rootDir = DocumentFile.fromTreeUri(getContext(), treeUri); if (rootDir == null || !rootDir.isDirectory()) { // 目录无效,处理错误逻辑 return; } // 2. 找到已存在的目标子目录MyDir DocumentFile targetDir = rootDir.findFile("MyDir"); if (targetDir == null || !targetDir.isDirectory()) { // 目标目录不存在,处理错误逻辑 return; } // 3. 在目标目录中创建指定文件 // 第一个参数为MIME类型,根据文件类型调整:文本用"text/plain",图片用"image/jpeg",通用二进制用"application/octet-stream" DocumentFile newFile = targetDir.createFile("application/octet-stream", "MyFile.MyExt"); if (newFile == null) { // 文件创建失败,可能是权限不足或目录不可写 return; } // 4. 写入文件内容 try (OutputStream outputStream = getContext().getContentResolver().openOutputStream(newFile.getUri())) { // 示例:写入字符串内容 String content = "测试写入SD卡文件"; outputStream.write(content.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); // 处理写入异常 }
关键注意事项
- 禁止混用File API和SAF URI:永远不要把
content://URI转换成File对象直接操作,这必然触发权限拒绝。 - MIME类型要匹配:
createFile()的MIME类型尽量和文件扩展名对应,不确定时用application/octet-stream(通用二进制类型)即可。 - 权限持久化:通过SAF获取的目录权限会被系统持久保存,除非用户手动在应用设置中撤销,无需每次操作都重新申请。
内容的提问来源于stack exchange,提问作者Tomas Ondrej




