随着 Android 系统持续演进(最新已到 Android 16),存储体系已经从传统"文件路径访问模型"彻底转向"系统托管数据访问模型"。
本文从工程视角系统梳理:
- 存储权限变化
- File API 的真实边界
- MediaStore 标准用法
- SAF 文件访问机制
- targetSdkVersion=30 的现实行为
- Android 13~16 的趋势变化
一、核心变化:Scoped Storage(分区存储)
从 Android 10 开始引入,并在 Android 11 强制执行:
👉 Android 11
核心目标:
❗限制 App 对"整个文件系统"的自由访问能力
📦 三层存储模型(现代 Android)
1️⃣ App 私有存储(File 完全可用)
/data/data/包名//Android/data/包名/
特点:
- 不需要权限
- File API 完全可用
- 永久稳定
2️⃣ 媒体存储(MediaStore 管理)
- DCIM
- Pictures
- Movies
- Music
特点:
- 必须通过 MediaStore
- 由系统统一管理
- 可被相册识别
3️⃣ 任意文件(SAF 管理)
- Download
- Documents
- 用户自选文件
特点:
- 用户授权访问
- 无路径直接访问能力
- 用 Uri 操作
二、targetSdkVersion = 30 的真实情况
在 Android 11 环境(targetSdk=30):
✔️ 仍然存在:
xml
READ_EXTERNAL_STORAGE
但:
- 不再是"全盘访问权限"
- 仅控制媒体读取范围
❌ 已废弃:
- WRITE_EXTERNAL_STORAGE(无实际控制能力)
- legacy storage(requestLegacyExternalStorage)
三、File API 可用边界(非常关键)
✔️ 1. App 私有目录(完全安全)
java
context.getFilesDir()
context.getExternalFilesDir()
特点:
- 永远可用
- 不依赖权限
- Android 10~16 稳定
⚠️ 2. 公共存储(DCIM / Download)
java
new File("/sdcard/DCIM/xxx.jpg")
现状:
| Android版本 | 行为 |
|---|---|
| Android 10 | ⚠️ 可能可用 |
| Android 11 | ⚠️ 不保证 |
| Android 13+ | ❌ 严格限制 |
| Android 16 | ❌ 不可靠行为 |
👉 本质:非规范访问方式
四、DCIM 写入的标准方式(MediaStore)
✔️ 正确方式:写入相册
java
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "img.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/MyApp");
values.put(MediaStore.Images.Media.IS_PENDING, 1);
ContentResolver resolver = context.getContentResolver();
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
OutputStream os = resolver.openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(uri, values, null, null);
}
✔️ 特点:
- 自动进入相册
- 不需要存储权限
- Android 10~16 完全稳定
- 系统统一索引管理
五、读取文件方式演进
1️⃣ 读取相册(标准)
java
Cursor cursor = resolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
MediaStore.Images.Media.DATE_ADDED + " DESC"
);
读取:
java
InputStream is = resolver.openInputStream(uri);
2️⃣ 任意文件读取(SAF)
java
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
startActivityForResult(intent, 100);
3️⃣ File 直接读取公共目录(不推荐)
java
new File("/sdcard/Download/a.json")
现状:
- ⚠️ 部分设备可用
- ❌ Android 13+ 不可靠
- ❌ Android 16 不保证行为
六、为什么"File 写 DCIM 仍然能成功"
在现实设备中仍可能看到成功现象:
1️⃣ MediaScanner 兜底机制
系统扫描媒体目录并索引文件
2️⃣ 厂商兼容策略
例如:
- Xiaomi
- OPPO
- Vivo
可能仍保留旧行为兼容
3️⃣ targetSdk=30 的兼容窗口
旧行为尚未完全收紧
👉 关键结论:
❗"能写成功 ≠ 官方支持方案 ≠ 长期可用"
七、Android 13+ 权限模型变化
在 Android 13:
权限拆分为:
text
READ_MEDIA_IMAGES
READ_MEDIA_VIDEO
READ_MEDIA_AUDIO
趋势:
- 减少统一存储权限
- 增强用户控制
- 推广 Photo Picker(无权限访问)
八、工程对照表(完整版本)
📊 存储访问全对照表
| 场景 | File API | MediaStore | SAF | 权限 | 稳定性 | Android 16趋势 |
|---|---|---|---|---|---|---|
| App内部存储 | ✔️ | ❌ | ❌ | ❌ | ⭐⭐⭐⭐⭐ | 不变 |
| App外部私有 | ✔️ | ❌ | ❌ | ❌ | ⭐⭐⭐⭐⭐ | 不变 |
| 写入DCIM | ⚠️ | ✔️ | ❌ | ❌ | ⭐⭐⭐ | 强制MediaStore |
| 读取DCIM | ⚠️ | ✔️ | ❌ | READ_MEDIA_* | ⭐⭐⭐ | MediaStore |
| 写视频 | ⚠️ | ✔️ | ❌ | ❌ | ⭐⭐⭐ | MediaStore |
| 下载目录写入 | ⚠️ | ✔️ | ✔️ | ❌ | ⭐⭐ | SAF优先 |
| 下载目录读取 | ⚠️ | ❌ | ✔️ | SAF | ⭐⭐ | SAF |
| 任意文件 | ❌ | ❌ | ✔️ | ❌ | ⭐⭐⭐⭐⭐ | SAF唯一 |
九、工程迁移策略
🟢 可不改
- App内部 File
- cache / log / json
🟡 建议改
- DCIM读取 → MediaStore
- Download读取 → SAF
🔴 必须改
- 写入相册
- 媒体生成
- 公共文件写入
十、核心总结
Android 存储体系已经形成三大模型:
📦 1. File(仅限私有空间)
- 永久稳定
- 不依赖权限
📷 2. MediaStore(媒体系统)
- 相册 / 视频 / 音频
- 系统托管
📁 3. SAF(用户文件)
- 任意文件访问
- 用户授权控制
十一、最终结论
Android 存储体系已经从"文件路径访问模型"演进为"系统托管数据模型 + 用户授权模型"。File API 仅在 App 私有空间稳定有效,而公共存储在 Android 11 起逐步收紧,在 Android 16 已完全转向 MediaStore 与 SAF 主导的体系。