1. 分区存储背景与适配目标
1.1 分区存储的引入背景
Android 10(API 29)正式引入分区存储(Scoped Storage)机制。在此之前,应用通过 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 权限即可访问设备外部存储中的任意文件。这种宽松的模型导致用户隐私泄露、垃圾文件泛滥。分区存储将存储空间划分为多个隔离区域,应用只能直接访问自己专属的私有目录,访问其他应用文件或公共目录必须通过 MediaStore、SAF(Storage Access Framework)或特定的媒体集合 API。
1.2 适配范围与影响
- 强制版本:targetSdkVersion ≥ 29 的应用默认启用分区存储;targetSdkVersion 28 及以下仍可暂时使用旧模式(但新应用提交必须 ≥ 29)。
- 受影响区域:外部存储的公共目录(DCIM、Pictures、Download、Documents、Movies 等)。内部存储私有目录(
getFilesDir()、getCacheDir())不受影响。 - 特殊例外:系统级应用、文件管理器、备份恢复应用可申请
MANAGE_EXTERNAL_STORAGE全权限。
1.3 主要文件类型与访问方式
| 文件类型 | 访问方式 | 权限需求 |
|---|---|---|
| 媒体文件(图片、视频、音频) | MediaStore API(ContentResolver) | READ_MEDIA_IMAGES / VIDEO / AUDIO (Android 13+) 或 READ_EXTERNAL_STORAGE |
| 文档 & 下载文件 | SAF(ACTION_OPEN_DOCUMENT / ACTION_CREATE_DOCUMENT)或 MediaStore.Downloads | 无需额外权限(用户主动选择) |
| 应用私有文件 | Context.getFilesDir(), getCacheDir(), getExternalFilesDir() | 不需要存储权限 |
| 应用专属外部文件 | Context.getExternalFilesDir(type) | 不需要存储权限 |
关键提示: 从 Android 11 起,分区存储不可逆退出。即使申请
MANAGE_EXTERNAL_STORAGE,普通应用也无法绕过 MediaStore 直接访问公共目录。
1.4 适配目标总览
适配的核心目标:将对外部存储公共目录的直接文件路径操作迁移为基于 ContentResolver 的 URI 操作,并充分利用 Android 提供的 MediaStore、SAF 和 FileProvider 等组件。
典型的适配流程包括:
- 更新 AndroidManifest 权限声明(区分 Android 13 新细粒度媒体权限)。
- 重构文件读取/写入逻辑,使用 MediaStore URI 代替 File 路径。
- 集成 SAF 实现用户自定义目录的文件选择与保存。
- 测试不同 Android 版本行为差异。
1.5 分区存储架构图
建议: 在开始适配前,先确认应用当前的 targetSdkVersion 和 minSdkVersion。对于 targetSdkVersion < 29 的应用,可以逐步提升版本并逐步测试。
注意: 分区存储是强制行为,不能通过清单属性
requestLegacyExternalStorage 在 Android 11+ 上绕过。建议尽早适配,以免应用在高版本设备上崩溃或功能受限。
1.6 典型适配 API 示例
以下示例展示如何通过 MediaStore 向公共 Pictures 目录写入一张图片:
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "my_photo.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Images.Media.IS_PENDING, 1);
}
Uri uri = context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream out = context.getContentResolver().openOutputStream(uri)) {
// 将图片数据写入 out
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
context.getContentResolver().update(uri, values, null, null);
}
1.7 适配清单检查表
- ☐ 升级 targetSdkVersion 至 29 或更高。
- ☐ 移除
requestLegacyExternalStorage(Android 11+ 无效)。 - ☐ 检查所有直接文件路径访问,替换为 MediaStore / SAF / FileProvider。
- ☐ 申请细粒度媒体权限(Android 13+ 使用
READ_MEDIA_IMAGES等)。 - ☐ 处理多版本兼容(minSdk < 29 的应用需保留旧路径逻辑)。
以上适配目标为后续实现奠定基础。