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 访问流程 传统存储权限与 File API 访问流程 应用启动 声明权限(Manifest) 运行时请求权限 用户授权通过 获取 File API 路径 通过 File 类读写文件 Android 10+ 分区存储 = 直接访问失败 传统模式:用户授权后,应用可借助 File API 直接访问整个外部存储。

上图展示了传统存储模式下应用从权限声明到通过 File API 读写文件的完整流程。在分区存储环境中(Android 10+),最后一步“通过 File 类读写文件”将直接导致异常,必须切换到新的 MediaStore 或 Storage Access Framework 实现。