
本文详解如何在 Android 13(API 33)及更高版本中,合规、稳定地获取对任意用户目录(如 Downloads/my_app_folder)的读写权限——核心方案是 Storage Access Framework(SAF)配合 ACTION_OPEN_DOCUMENT_TREE,而非已弃用的直接路径或危险权限。
本文详解如何在 android 13(api 33)及更高版本中,合规、稳定地获取对任意用户目录(如 downloads/my_app_folder)的读写权限——核心方案是 storage access framework(saf)配合 action_open_document_tree,而非已弃用的直接路径或危险权限。
在 Android 11(API 30)起全面推行分区存储(Scoped Storage)后,应用无法再通过 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 权限直接访问公共目录(如 Downloads/、Documents/)下的任意子文件夹。你当前遇到的问题——ACTION_OPEN_DOCUMENT_TREE 无响应、MANAGE_EXTERNAL_STORAGE 被 Google Play 严格限制、getExternalStoragePublicDirectory() 在 Android 10+ 已废弃且抛出 SecurityException——正是这一演进的典型表现。
✅ 正确解法:使用 SAF 获取持久化目录访问权限ACTION_OPEN_DOCUMENT_TREE 并非“不工作”,而是需满足以下三个关键前提,缺一不可:
-
目标 SDK 必须 ≥ 30(Android 11+),并在
AndroidManifest.xml中声明:<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"></uses-permission><uses-permission android:name="android.permission.READ_MEDIA_VIDEO"></uses-permission><uses-permission android:name="android.permission.READ_MEDIA_AUDIO"></uses-permission><!-- 注意:无需申请 READ_EXTERNAL_STORAGE -->
-
启动 Intent 必须显式设置
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION(许多教程遗漏此步):private void selectFolder() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // 关键:添加 FLAG,否则系统可能忽略请求 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 持久化授权必备 startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY); } -
在
onActivityResult中正确处理返回结果并持久化 URI 权限:
Android Adb Skill下载Android 开发调试技能,通过系统 ADB 工具操作 Android 设备。以下场景必须触发此技能:(1) 直接 ADB 操作——安装 APK、查看设备列表、抓取 logcat 日志、查看已安装应用、清除应用数据、截图、重启设备、拉取/推送文件、查看 CPU/内存/电池信息、adb shell 操作;(2)...
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == RESULT_OK && data != null) { Uri treeUri = data.getData(); // 持久化授权:使权限在重启后仍有效(核心!) getContentResolver().takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION ); // 保存 treeUri 到 SharedPreferences,供后续使用 SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); prefs.edit().putString("selected_dir_uri", treeUri.toString()).apply(); // ✅ 现在可安全创建/读取子文件(如 .obj, .atlas) createFileInSelectedDir(treeUri, "test.obj"); } } private void createFileInSelectedDir(Uri treeUri, String fileName) { DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri); if (pickedDir != null && pickedDir.canWrite()) { DocumentFile file = pickedDir.createFile("application/octet-stream", fileName); if (file != null && file.canWrite()) { try (OutputStream os = getContentResolver().openOutputStream(file.getUri())) { os.write("Hello SAF!".getBytes()); } catch (IOException e) { Log.e("SAF", "Failed to write file", e); } } } }
⚠️ 重要注意事项:
-
MANAGE_EXTERNAL_STORAGE已被 Google Play 严格限制:仅限文件管理器、防病毒、备份类应用可申请,且需提交详细理由审核;普通应用上架将被拒。 -
ACTION_OPEN_DOCUMENT_TREE支持所有文件类型:包括.obj,.atlas,.pack,.pfx等非媒体文件——SAF 不区分文件类型,只管理目录层级权限。 -
不要尝试
Environment.getExternalStoragePublicDirectory():该 API 自 Android 10 起已废弃,在 Android 13 上调用会直接抛出RuntimeException。 - 模拟器调试建议:部分旧版模拟器(如 Android Studio 默认)未完整实现 SAF UI。请务必在真实 Android 13+ 设备或使用最新版 Android Emulator 33.1.20+ 测试。
? 进阶提示:若需默认访问 Downloads/my_app_folder,可在用户首次选择后自动创建子目录并缓存其 DocumentFile 引用,后续操作均基于该 URI 进行,完全规避权限弹窗重复触发。
总结:Android 13 的文件访问不是“变难了”,而是转向更隐私友好的模型。坚持使用 SAF + ACTION_OPEN_DOCUMENT_TREE + takePersistableUriPermission 组合,是你兼顾合规性、兼容性与功能完整性的唯一推荐路径。










