2. 传统存储权限模式回顾
2.1 权限行为:READ_EXTERNAL_STORAGE 与 WRITE_EXTERNAL_STORAGE
在 Android 10(API 29)之前,应用通过声明 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 两个危险权限,可以获取对整个外部存储(共享存储空间)的读写访问能力。用户安装或运行应用时,系统会弹出权限请求对话框,用户一旦授予,应用即可通过传统 File API 自由访问外部存储中的任何文件(包括其他应用创建的文件)。
- 权限声明位置:
<uses-permission>元素放在AndroidManifest.xml中。 - 运行时请求:针对 Android 6.0+,必须使用
ActivityCompat.requestPermissions()在运行时向用户请求,并且在onRequestPermissionsResult()回调中处理授权结果。 - 授权范围:一旦授予,应用可以读取或修改外部存储中任何非受保护的文件。
<!-- AndroidManifest.xml 声明 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
提示:Google 建议在
WRITE_EXTERNAL_STORAGE 中加入 android:maxSdkVersion="28",因为从 Android 10 开始该权限对于以 API 29+ 为 target 的应用已不再真实生效。
2.2 传统 File API 路径限制
传统模式下,开发者普遍使用 Environment.getExternalStorageDirectory()、Environment.getExternalStoragePublicDirectory() 以及 context.getExternalFilesDir() 等 API 获取外部存储路径,然后通过 File 对象直接执行文件的创建、删除、读写操作。
| API | 返回路径示例 | 访问范围 |
|---|---|---|
Environment.getExternalStorageDirectory() |
/storage/emulated/0 |
整个外部存储根目录(全部用户文件) |
Environment.getExternalStoragePublicDirectory(type) |
/storage/emulated/0/DCIM |
标准公共目录(如 DCIM、Download、Pictures) |
context.getExternalFilesDir(type) |
/storage/emulated/0/Android/data/<package>/files |
应用专属外部存储目录 |
关键限制:传统 File API 路径不能直接用于 Android 10+ 的分区存储环境。如果应用以 API 29 或更高版本为 target,通过上述路径访问非应用专属目录中的文件时,会抛出 FileNotFoundException 或返回空结果——除非使用 MediaStore 或 Storage Access Framework(SAF)进行适配。
// 传统方式(在 API 29+ 上会失败)
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File myPhoto = new File(dcimDir, "example.jpg");
if (myPhoto.exists()) {
// 在分区存储下,exists() 可能返回 false,即使文件真实存在
}
警告:Android 10 设备上,targetSdkVersion ≥ 29 的应用即使声明了
WRITE_EXTERNAL_STORAGE,也无法通过 File API 向外部存储公共目录写入新文件。应用只能通过 MediaStore 插入,或使用 SAF 让用户选择目录。
2.3 兼容性问题清单
核心兼容性问题:
- 权限降级:
WRITE_EXTERNAL_STORAGE在 Android 10+ 上不提供真实写入能力,仅保持向后兼容(当 targetSdkVersion ≤ 28 时仍然有效)。 - 路径失效:直接使用
getExternalStorageDirectory()构造的路径无法读取公共目录中的文件,必须改用MediaStore或DocumentFile。 - 应用专属目录限制放宽:通过
context.getExternalFilesDir()获取的路径在分区存储下仍然可用,但仅限应用自身创建的文件夹。 - Uri 权限传递问题:旧代码中通过
FileProvider. getUriForFile()共享文件的方式,在分区存储下仍然可行,但目标路径必须属于应用专属目录或已被MediaStore授权。
2.4 传统模式 vs 分区存储模式
| 对比维度 | 传统模式(Android 9-) | 分区存储模式(Android 10+) |
|---|---|---|
| 所需权限 | READ_EXTERNAL_STORAGE + WRITE_EXTERNAL_STORAGE |
按需申请 READ_EXTERNAL_STORAGE(同时使用 MediaStore / SAF) |
| 公共目录写入 | 通过 File API 直接创建文件 |
必须使用 MediaStore 插入记录,或通过 SAF 选择目录 |
| 文件路径访问 | 自由使用 Environment 和 File |
应用专属目录可用 File;其它公共文件只能通过 Uri + ContentResolver 操作 |
| 读取其他应用文件 | 只要权限授予即可任意读取 | 仅可通过 MediaStore 查询到媒体文件,且部分文件可能被隐藏 |
上图展示了传统存储模式下应用从权限声明到通过 File API 读写文件的完整流程。在分区存储环境中(Android 10+),最后一步“通过 File 类读写文件”将直接导致异常,必须切换到新的 MediaStore 或 Storage Access Framework 实现。