
本文详解 Android 7.0(Nougat)及以上版本中跨应用访问外部存储(如 SD 卡)的限制与合规方案,重点说明为何直接读写其他应用的 Android/data/ 目录会失败,并介绍通过 Storage Access Framework(SAF)实现安全、用户授权的文件迁移方法。
本文详解 android 7.0(nougat)及以上版本中跨应用访问外部存储(如 sd 卡)的限制与合规方案,重点说明为何直接读写其他应用的 `android/data/` 目录会失败,并介绍通过 storage access framework(saf)实现安全、用户授权的文件迁移方法。
在 Android 7.0(API 24)及更高版本中,系统对应用外部存储的访问实施了严格的沙盒隔离机制。尽管 /storage/sdcard/Android/data/com.test.appA/files/ 和 /storage/sdcard/Android/data/com.test.appB/files/ 在文件系统路径上看似“可见”,但应用 A 默认无法直接读写应用 B 的专属目录——即使已声明 WRITE_EXTERNAL_STORAGE 权限并获得运行时授权。这是因为从 Android 7.0 开始,Android/data/ 下各应用子目录受 Scoped Storage(分区存储)雏形机制保护,每个应用仅对其自身包名对应的目录拥有完全读写权限。
⚠️ 注意:
WRITE_EXTERNAL_STORAGE权限在 Android 10+ 已被大幅弱化,在 Android 11+ 中对Android/data/和Android/obb/目录完全失效;即便在 Android 7–9,该权限也不授予跨包访问其他应用Android/data/目录的能力。
那么,为什么文件管理器类 App 能访问这些路径?它们调用的并非传统存储权限,而是 Storage Access Framework(SAF) —— 一种以用户为中心、基于 Intent 的交互式文件访问机制。
✅ 正确做法:使用 Storage Access Framework(SAF)
SAF 允许应用通过系统提供的文档选择器(DocumentPicker),由用户显式授予对特定目录(如 Android/data/ 下某子目录)的持久读写权限。其核心是 StorageVolume + DocumentFile + takePersistableUriPermission() 的组合。
示例:请求访问 App B 的专属目录
// 1. 启动 SAF 目录选择器,目标为 App B 的 files 目录
private void requestAppBDirectoryAccess() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
// 构造目标 URI(需先获取 Volume UUID 或使用 SAF 探索方式)
// 实际开发中,通常引导用户手动导航至 /Android/data/com.test.appB/files/
startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == RESULT_OK && data != null) {
Uri treeUri = data.getData();
// 2. 获取持久化权限(关键!否则下次重启后失效)
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
// 3. 使用 DocumentFile 操作文件
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
if (pickedDir != null && pickedDir.canWrite()) {
// 示例:列出文件
for (DocumentFile file : pickedDir.listFiles()) {
Log.d("SAF", "Found: " + file.getName());
}
// 示例:将 App A 的某个文件复制到此处(需先获取源文件 DocumentFile)
}
}
}
关键注意事项:
- ? 用户必须手动选择目录:SAF 不支持直接构造
com.test.appB/files的 URI,必须由用户在系统弹窗中导航并确认。 - ? 持久化权限需主动申请:调用
takePersistableUriPermission()是长期访问的前提,否则权限仅在本次会话有效。 - ? 目标目录需可写:
DocumentFile.canWrite()返回true才能执行写入;部分厂商定制系统可能限制对Android/data/的写入(需测试兼容性)。 - ? 无需声明
READ/WRITE_EXTERNAL_STORAGE:SAF 权限独立于传统存储权限,Android 11+ 应用甚至可完全移除这些危险权限。
✅ 替代方案(有限适用)
- Shared Preferences / ContentProvider / AIDL:适用于结构化数据共享,不适用于任意文件迁移。
-
公共媒体目录(如
Environment.DIRECTORY_DOWNLOADS):可自由读写,但需用户接受文件存放位置变更,且不保证隐私性。 -
MediaStoreAPI(Android 10+):适用于图片、音频、视频等媒体文件,对普通文档或应用专属文件支持有限。
总结
在 Android 7+ 上,跨应用迁移 Android/data/ 目录下的文件,唯一合规、稳定且面向未来的方式是 Storage Access Framework(SAF)。它虽需用户交互,却保障了隐私安全与系统一致性。开发者应摒弃“路径硬编码 + 权限强求”的旧思路,转而拥抱以用户授权为核心的现代存储模型。务必在 AndroidManifest.xml 中声明:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxsdkversion="28"></uses-permission><!-- Android 10+ 可省略,SAF 无需此权限 -->
遵循 SAF 范式,你的应用才能在持续收紧的 Android 存储策略中保持兼容性与用户体验。











