众所周知,在 Android 中,文件的存储有多个路径可供存储,也提供了多个 Api 使用,那这些 Api 到底是用来是哪个目录,又有什么区别呢。

内部存储和外部存储

首先,要先知道 Android 存储中分为内部存储(Internal storage)和外部存储(External storage)

下面用 com.application.id 作为我们的 applicationId 来举例

内部存储

  • 内部存储指的是 App 私有的目录,即 /data/data/com.application.id/

    有些手机的目录是 /data/user/0/com.application.id/
    实际上是同一个目录,从下图可见,/data/user/0 目录是一个软连接,其实际指向的目录即 /data/data

存储在这个目录下的文件是 App 私有的,其他 App 无法读写(root 用户除外),目录会随着 App 的卸载而被删除

外部存储

外部存储包含私有外部存储和公共目录存储

私有外部存储

私有外部存储是指 /storage/emulated/0/Android/data/com.application.id

我们会在根目录里看到 /sdcard/mnt/sdcard/storage/emulated/self/primary 下的文件都跟上述的 /storage/emulated/0 中的文件一模一样,这不禁会让人感到疑惑,实际上,通过调研发现这些目录也都是软连接,可以看到其对应实际目录

/sdcard -> /storage/self/primary

/storage/self/primary -> /mnt/user/0/primary

/mnt/user/0/primary -> /storage/emulated/0

所以其实到最后,其目录指向的都是我们的 /storage/emulated/0 目录

在私有外部存储中,App 可以读写自己的目录(/storage/emulated/0/Android/data/com.application.id)下的文件,如果 Api 大于 19,不需要申请写权限。
如果需要读写其他 App 的私有外部存储目录,则需要声明读写权限,若高于 23,还需要动态进行权限申请。

私有外部存储的目录也会随着 App 的卸载而被删除

写权限 android.permission.WRITE_EXTERNAL_STORAGE

那么为什么会有这样的设计呢?这个 0 又代表什么
我的猜测是 Android 系统中可以有多用户,这个 0 代表了当前用户,如果有第二个用户,应该就会有 1 的出现,使用软连接的方式,会保证在使用 api 获取到相对应的路径时,指向正确的用户下的文件目录,避免多个用户之间的文件系统混乱
当然,这只是我的猜测,未曾验证

公共目录存储

是指 sdcard 中根目录中的公共目录,即 /storage/emulated/0,例如图片文件夹(/storage/emulated/0/DCIM),音乐文件(/storage/emulated/0/Music)

这部分的目录是共享的,所以如果 App 往这个目录下读写文件,需要申请读写权限,并且在 App 卸载后不会被删除。

那我们接着看 Api 的使用

获取内部存储目录

无需申请权限

  • Context.getFilesDir()

    获取内部存储中 files 目录

    /data/data/com.application.id/files

  • Context.getCacheDir()

    获取内部存储中 cache 目录

    /data/data/com.application.id/cache

  • Context.getDataDir()//Api >= 24

    获取内部存储的存储目录的绝对路径

    /data/data/com.application.id

获取外部私有存储目录

无需申请权限

  • Context.getExternalFilesDir(String type)

    获取外部私有存储中的 files 目录或其子文件夹

    /storage/emulated/0/Android/data/com.application.id/files

    or

    /storage/emulated/0/Android/data/com.application.id/files/type

  • Context.getExternalCacheDir()

    获取外部私有存储中的 cache 目录

    /storage/emulated/0/Android/data/com.application.id/cache

获取公有目录

读写需要权限
写入权限 android.Manifest.permission#WRITE_EXTERNAL_STORAGE
读取权限 android.Manifest.permission#READ_EXTERNAL_STORAGE

对应的 API

  • Environment.getExternalStorageDirectory()

    获取公有目录
    /storage/emulated/0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * type
    * #DIRECTORY_MUSIC
    * #DIRECTORY_PODCASTS
    * #DIRECTORY_RINGTONES
    * #DIRECTORY_ALARMS
    * #DIRECTORY_NOTIFICATIONS
    * #DIRECTORY_PICTURES
    * #DIRECTORY_MOVIES
    * #DIRECTORY_DOWNLOADS
    * #DIRECTORY_DCIM
    * #DIRECTORY_DOCUMENTS
    */
  • Environment.getExternalStoragePublicDirectory(String type)

    获取公有目录下对应的类型文件夹

    /storage/emulated/0/DCIM 等

Android 10 分区存储机制

然而,在 Android 10(Api 29) 上,我们发现通过 Environment 获取路径的 api 已经被标记为 Deprecated 的了

这…可咋整呢

其实,这对于 Android 用户来说,是一件好事来着。
随着 Android 的发展,Google 对用户的隐私越来越看重了,慢慢地收紧了开发者对用户设备 sdcard 的读写权限

从 Android 10 开始,对于 Target Api 为 29 的应用,根据官方文档所描述,其访问权限范围限定为外部存储,即分区存储(Scoped Storage)

简单来说,应用只能通过访问Context.getFilesDir() 等 api 访问自己的私有目录(/data/data/packagename/),以及通过Context.getExternalFilesDir("") 等 api 访问外部存储中自己应用的目录(/Android/data/packagename/),无需申请权限,这个行为同之前一样,没有变动。

Target api < 29 时,只要应用获取到了 WRITE_EXTERNAL_STORAGE 权限,就可以对整个 sdcard 目录进行读取,包括其他应用的 外部私有存储目录(/Android/data/otherAppPackageName/)

但是,在 Target Api >=29 后,在 Android 10 设备上全新安装的应用,即便应用获得了WRITE_EXTERNAL_STORAGE权限后,应用也无法直接通过 Java File Api (例如 Environment.getExternalStorageDirectory()) 对 sdcard 中的非自己应用创建的文件进行读写操作。

这里的全新安装加了着重提示,应用是从 Target Api = 28 覆盖安装升级到 Target Api 29 的话,即便是安装在 Android 10 的手机上,若获得了WRITE_EXTERNAL_STORAGE权限,通过 Java File Api 仍然能够对 sdcard 中的任意文件进行读写操作

那…问题来了,在 Target Api >= 29 上

  1. 应用自身需要将多媒体文件进行存储读取,该怎么做呢。
  2. 需要访问用户其他 APP 存储的文件(例如照片,视频),又该如何适配呢

在 Android 的规范中,如果用户需要保存多媒体文件到手机中,应保存到共享目录(Share storage)中,以便其他应用访问,例如音乐应用中用户下载的音乐,拍照应用用户拍摄的照片,视频等

下面的表格总结了以上的内容,而至于如何通过 MediaStore Api 和 Storage Access Framework 进行增删查改,我们下文再续

下文来啦:

Android MediaStore Api 使用

Android 存储访问框架 Storage Access Framework

-w1235

如有错误,望各位斧正

Google 官方文档:
Android 10 中的隐私权变更
将文件保存到外部存储
管理分区外部存储访问
Data and file storage overview
使用存储访问框架打开文件
Overview of shared storage

本文参考文章:

感谢各位大大的分享

Android Q 存储机制大变化
Android 存储使用参考
Android 10 分区存储介绍及百度APP适配实践