1. 分区存储背景与政策演进
1.1 引入动机:为什么需要 Scoped Storage?
在 Android 10(API 29)之前,应用只要声明了 READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE 权限,就可以访问外部存储(/sdcard)上的几乎所有文件。这种“全盘访问”模式带来三个严重问题:
- 隐私泄露风险:恶意应用可遍历用户的照片、文档、下载目录,收集敏感数据。
- 文件碎片化:应用卸载后残留大量无主文件,污染用户存储空间。
- 权限滥用:大量应用申请存储权限仅为了读取自己包名下的缓存文件,却获得整个空间的访问权限。
Google 推出 Scoped Storage(分区存储),核心目的是将应用的访问范围限定在:自己的专属目录(Context.getExternalFilesDir)以及通过 MediaStore / SAF(Storage Access Framework) 授予的媒体文件。这一设计在 Android 10(可退出)→ Android 11(强制)→ Android 13(细化通知权限)逐步收紧,成为权限模型演进的里程碑。
关键痛点:旧模式下应用可以长期持有 READ_EXTERNAL_STORAGE 权限,且用户无法限制其对特定文件夹的读取。Scoped Storage 使权限变成“按需授予”,用户可以在系统界面中撤销对媒体库中某些文件的访问。
1.2 政策演进:Android 10 / 11 / 13 行为差异
下表总结了三个主要版本在分区存储上的关键行为变化:
| 版本 | API 级别 | 关键变化 | 可退出标志 |
|---|---|---|---|
| Android 10 | 29 | 首次引入分区存储,默认启用;应用仍可通过 requestLegacyExternalStorage 退回到旧模式 |
android:requestLegacyExternalStorage="true" |
| Android 11 | 30 | 分区存储变为强制,requestLegacyExternalStorage 不再生效;新增 MANAGE_EXTERNAL_STORAGE 权限用于管理目录 |
不可退出;需通过 MediaStore.setRequireOriginal() 等 API 适配 |
| Android 13 | 33 | 新增细分的媒体权限:READ_MEDIA_IMAGES / READ_MEDIA_VIDEO / READ_MEDIA_AUDIO,弃用 READ_EXTERNAL_STORAGE;通知权限也被分离 |
— |
适配陷阱:如果应用以 targetSdkVersion=29 并设置了 requestLegacyExternalStorage,在 Android 10 设备上可以正常访问旧路径,但在升级到 Android 11 后该标志失效,必须迁移到 MediaStore + SAF 方案。运行时检测版本是必要步骤。
1.3 目标用户影响范围
分区存储影响所有涉及文件读写的应用,以下三类是重灾区:
- 文件管理类应用(如 ES 文件浏览器、MT 管理器)—— 需要
MANAGE_EXTERNAL_STORAGE权限,并通过 Google Play 的特殊审核。 - 媒体编辑/备份类应用(如相册、录音、网盘)—— 必须使用
MediaStoreAPI 写入自有媒体,否则文件无法被系统媒体库索引。 - 旧版工具类应用(targetSdk < 29 并依赖文件路径硬编码)—— 在 Android 11+ 上即使被授予存储权限,访问
/sdcard/Downloads等公共目录也会抛出FileNotFoundException。
从用户视角看,最直观的变化是:
- 应用不能再“静默”读取所有照片,用户需要在系统弹窗中逐项授权。
- 卸载应用后,其专属目录外的文件不会自动清理,但应用也无法再“扔”垃圾文件到公共目录。
- 对系统相册的访问需要用户明确选择图片或视频,避免批量扫描。
兼容策略:如果你的应用仍需要读取公共目录中的文件(如 PDF 解析器、下载管理器),最佳实践是在 AndroidManifest.xml 中声明 MANAGE_EXTERNAL_STORAGE,并在运行时引导用户跳转到系统设置页授予“所有文件访问权限”。但该权限在 Google Play 上会受到严格审核,只有在应用核心功能确实需要时才能通过。
1.4 前置知识:沙箱 vs 公共存储
理解分区存储前必须分清两个存储区域:
- 内部存储专属目录(沙箱):通过
Context.getFilesDir()、Context.getCacheDir()获取,应用无需任何权限即可读写,卸载后自动清除。对应/data/data/<package>/ - 外部存储专属目录:
Context.getExternalFilesDir(),同样无需运行时权限,卸载后清除。对应/sdcard/Android/data/<package>/ - 公共媒体目录:
DCIM/、Pictures/、Movies/、Music/、Downloads/等,需要运行时权限并通过MediaStore或SAF操作。
最佳实践:面向 Android 10+ 开发时,所有用户可见的文件(如截图、导出文件)都应该通过 MediaStore 写入公共媒体目录,并正确设置 IS_PENDING 标志以避免文件被其他应用提前读取到不完整的内容。
1.5 章节小结
本章建立了分区存储的政策背景和版本分水岭:Android 10 为“过渡期”,Android 11 为“强制期”,Android 13 为“精细化期”。核心动机是保护用户隐私、减少文件碎片、规范权限模型。受影响的应用必须从路径依赖迁移到 MediaStore + SAF 架构,否则在 Android 11+ 上将面临文件访问失败、数据丢失甚至审核被拒的风险。接下来的章节将逐一深入实现流程、代码适配、调试工具和常见错误的解决方案。