3. 分区存储核心原理:MediaStore 机制、沙箱目录结构、SAF 作用、直接文件路径限制

自 Android 10(API 29)起,分区存储成为强制访问模型。应用不再拥有对整个外部存储的完全文件路径访问权限,而是被限制在三个互不重叠的访问通道内。本章将从原理层剖析这四个核心概念,并给出对应的实现基线与边界条件。

3.1 MediaStore 机制:统一媒体文件入口

MediaStore 是 Android 系统提供的内容提供者(ContentProvider),对外暴露 content:// URI。应用通过 ContentResolver 操作 MediaStore 元数据表来读写共享媒体文件(图片、视频、音频)。

MediaStore 类型对应 URI说明
Imagescontent://media/external/imagesJPEG、PNG、WebP 等
Videocontent://media/external/videoMP4、3GP、MKV 等
Audiocontent://media/external/audioMP3、FLAC、WAV 等
Downloadscontent://media/external/downloads非媒体通用文件(Android 10+)
关键 API 示例:通过 MediaStore 插入图片并获取 URI。
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "photo_2024.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/AppAlbum");
Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
// 通过 uri 打开输出流写入文件数据
OutputStream os = contentResolver.openOutputStream(uri);

3.2 沙箱目录结构:应用专属隔离空间

每个应用在外部存储中拥有独立的沙箱目录,无需任何权限即可读写。该目录位于 Android/data/<package_name>/ 下,系统会在应用卸载时自动清除。

  • 通过 context.getExternalFilesDir(type) 获取专有目录,例如 getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)。
  • 通过 context.getExternalCacheDir() 获取缓存目录。
  • 沙箱目录 不可 通过 MediaStore 查询,只能使用 File API 直接读写。
  • Android 11 起,沙箱目录外的文件即使应用有 READ_EXTERNAL_STORAGE 也默认不可访问。
常见错误:在沙箱目录中使用 Environment.getExternalStorageDirectory() 拼接路径会触发 FileNotFoundException。应始终使用 context.getExternalFilesDir() 获取根路径。

3.3 SAF(Storage Access Framework)作用:跨沙箱文件选择

SAF 提供了一套标准的系统 UI 让用户选择文档或目录,应用获得用户授予的 URI 读写权限。它不依赖文件路径,完全基于 URI 和 DocumentFile 操作。

SAF 关键动作Intent Action返回 URI 类型
选择单个文件Intent.ACTION_OPEN_DOCUMENT单文件 URI
创建新文件Intent.ACTION_CREATE_DOCUMENT新文件 URI
选择目录(树)Intent.ACTION_OPEN_DOCUMENT_TREE树 URI
持久化权限(Take Persistable Permissions):获取返回的 URI 后调用 contentResolver.takePersistableUriPermission(uri, FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION) 可跨应用重启保留权限。

3.4 直接文件路径限制

自 Android 10 起,禁止使用 Environment.getExternalStorageDirectory() 和 /sdcard/ 硬编码路径访问共享存储中的任意文件。该限制使应用必须通过 MediaStore、SAF 或沙箱目录这三种合法通道操作文件。

  • 读取:只能通过 MediaStore URI 或 SAF URI 读取图片、视频、音频等。
  • 写入:只能通过 MediaStore 插入新记录或通过 SAF 创建文档。
  • 例外:沙箱目录(getExternalFilesDir)不受路径限制,但仅限应用自身数据。
兼容适配警告:强制使用旧路径的代码在 Android 10+ 上会触发 FileNotFoundException 或 SecurityException。建议使用 Environment.isExternalStorageLegacy() 判断并区分适配路径。
分区存储核心访问通道 MediaStore content:// URI 元数据操作 沙箱目录 Android/data/package/ SAF 用户选择文档/目录 直接文件路径 Environment.getExternalStorage() 核心适配原则 只有沙箱目录可使用 File API;共享媒体必须通过 MediaStore 或 SAF 直接文件路径在 Android 10+ 完全受限,需迁移至上述三个通道

3.5 三通道对比矩阵

特性MediaStore沙箱目录SAF
所需权限READ_EXTERNAL_STORAGE (Android 13 + 细粒度)无需权限无(用户交互授权)
访问范围共享媒体文件(图片、视频、音频)仅本应用私有数据用户选择的任意文件/目录
文件操作方式ContentResolver openInputStream/openOutputStream标准 Java File APIDocumentFile + ParcelFileDescriptor
持久化访问URI 长期可用(除非记录被删除)应用卸载即清除需调用 takePersistableUriPermission
是否支持路径操作否(仅 URI)是(真实文件路径)否(仅 URI)

3.6 性能与稳定性注意点

  • MediaStore 批量操作:使用 ContentProviderOperation 批量插入/更新可提升效率,避免逐行事务。
  • SAF 树 URI 递归:遍历大量文件时应使用 DocumentFile.listFiles() 并在后台线程执行,防止 UI 卡顿。
  • 沙箱目录空间管理:应用应主动清理无用文件,避免占用过多外存触发系统清理弹窗。
  • 直接路径检测:在旧代码中搜索 /sdcard、Environment.getExternalStorageDirectory() 等模式,尽早替换。
调试建议:在 Android 10+ 模拟器或真机上用 adb shell dumpsys media 查看 MediaStore 数据库状态;使用 adb shell ls -la /sdcard/Android/data/<package>/ 验证沙箱目录。