
本文详解 Android 中通过 ACTION_OPEN_DOCUMENT_TREE 获取的目录访问权限为何在应用重启后失效,并系统性讲解如何调用 takePersistableUriPermission() 实现跨进程、跨生命周期的持久化文件访问,同时结合 Android 权限演进趋势给出合规适配建议。
本文详解 android 中通过 `action_open_document_tree` 获取的目录访问权限为何在应用重启后失效,并系统性讲解如何调用 `takepersistableuripermission()` 实现跨进程、跨生命周期的持久化文件访问,同时结合 android 权限演进趋势给出合规适配建议。
在 Android 开发中,使用 Intent.ACTION_OPEN_DOCUMENT_TREE 授权用户选择一个文件夹(如用于自动备份),是实现“作用域存储”下安全、可控外部存储访问的核心方式。但许多开发者会遇到这样一个典型问题:首次选择目录后功能正常,一旦强制关闭应用(Force Stop)或进程被系统回收,再次启动时 DocumentFile.fromTreeUri() 返回的对象中 findFile() 调用抛出 NullPointerException——根本原因在于:系统授予的 URI 访问权限默认是临时的,仅在当前任务栈存活期间有效;Force Stop 会彻底清除所有已授予的 URI 权限,且不会自动恢复。
✅ 正确做法:持久化 URI 权限(Persistable URI Permission)
Android 自 API 19(KitKat)起引入 ContentResolver.takePersistableUriPermission(),专为解决此问题而设计。它将用户一次授权的 content:// URI 权限“固化”到系统权限管理器中,使该 URI 在 App 重启、设备重 boot 后仍可安全访问(前提是未被用户手动撤销)。
你需要在 onActivityResult() 中、成功获取 URI 后立即调用该方法:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == AppConstants.GRANT_ACCESS_TO_AUTO_BACKUP_FILE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
// ✅ 关键步骤:持久化读写权限
final int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
getContentResolver().takePersistableUriPermission(uri, takeFlags);
// 保存 URI 字符串(推荐使用 SharedPreferences)
spEditor.putString("auto_backup_uri", uri.toString()).apply();
// 后续创建子目录逻辑保持不变...
DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri);
DocumentFile dfFinalDir = documentFile.findFile(getString(R.string.app_name_no_space));
if (dfFinalDir == null) {
dfFinalDir = documentFile.createDirectory(getString(R.string.app_name_no_space));
}
// ...
}
}
}⚠️ 注意事项:
takePersistableUriPermission()必须在onActivityResult()中、RESULT_OK状态下执行,不能延迟到异步线程或后续生命周期回调中;- 权限标志需与实际需求匹配:若仅读取,传
FLAG_GRANT_READ_URI_PERMISSION;若需写入(如本例创建/修改文件),必须同时传入FLAG_GRANT_WRITE_URI_PERMISSION;- 该操作无需用户二次确认,但仅对用户明确通过系统文件选择器授予的 URI 生效(即来自
ACTION_OPEN_DOCUMENT_TREE或ACTION_OPEN_DOCUMENT的结果);- 持久化权限可被用户在「设置 → 应用 → [你的App] → 权限 → 存储访问」中手动撤销,代码中应始终做好空值校验。
? 读取时的安全调用模式
在 writeDataToFile() 中,不应假设 uriString 一定有效。务必先校验 URI 是否仍具访问权,并在失败时引导用户重新授权:
private void writeDataToFile(Intent intent) {
String uriString = sp.getString("auto_backup_uri", "");
if (uriString.isEmpty()) {
// 引导用户重新选择目录
requestBackupDirectory();
return;
}
Uri uri = Uri.parse(uriString);
// ✅ 验证持久化权限是否仍然有效(推荐)
if (!isUriPermissionGranted(uri)) {
Log.w("TAG", "Persisted permission lost for: " + uri);
requestBackupDirectory();
return;
}
DocumentFile dfBeforeFinalDir = DocumentFile.fromTreeUri(this, uri);
DocumentFile dfFinalDir = dfBeforeFinalDir.findFile(getString(R.string.app_name_no_space));
if (dfFinalDir == null) {
// 目录可能被用户删除,需重建
dfFinalDir = dfBeforeFinalDir.createDirectory(getString(R.string.app_name_no_space));
}
DocumentFile dfFinalAutoBackupFile = dfFinalDir.findFile("notatki_a");
if (dfFinalAutoBackupFile == null) {
dfFinalAutoBackupFile = dfFinalDir.createFile("application/json", "notatki_a");
}
try (OutputStream os = getContentResolver().openOutputStream(dfFinalAutoBackupFile.getUri())) {
os.write(jsonNotes.getBytes(StandardCharsets.UTF_8));
intent.putExtra("auto_save_status", AppConstants.SUCCESS);
} catch (IOException e) {
doWhenException(e, "Failed to write backup file", intent);
}
}
// 辅助方法:检查 URI 权限是否仍存在
private boolean isUriPermissionGranted(Uri uri) {
int flags = getContentResolver().getPersistedUriPermissions().stream()
.filter(p -> p.getUri().equals(uri))
.mapToInt(p -> p.getModeFlags())
.findFirst()
.orElse(0);
return (flags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
== (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}? 与 Android 权限演进趋势的一致性
从 Android 6.0 运行时权限,到 Android 10+ 的作用域存储(Scoped Storage),再到 Android 14+ 的照片选择器(Photo Picker),Google 持续强化“最小权限原则”和“用户可控性”。ACTION_OPEN_DOCUMENT_TREE + takePersistableUriPermission() 正是这一理念下的标准、合规、面向未来的方案——它不依赖 MANAGE_EXTERNAL_STORAGE(已在 Android 11+ 被限制为特殊用途)、不请求全局存储权限,而是让用户精确控制“哪个文件夹、给谁用、用多久”。
✅ 最佳实践总结:
- 永远不要假设 URI 权限永久有效:每次使用前做存在性与有效性校验;
- 权限获取与持久化必须同步完成:
onActivityResult()是唯一可靠时机;- 优雅降级:权限丢失时提供清晰 UI 引导,而非崩溃;
- 避免滥用:仅对真正需要长期访问的目录(如备份根目录)持久化,普通临时文件用
getCacheDir()即可。
通过以上改造,你的应用即可在 Android 10–16+ 全系列版本中稳定实现“一次选择、长期可用”的目录访问能力,既符合平台规范,也大幅提升用户体验与数据可靠性。


















