11 Android 分区适配 安卓11分区存储_1024程序员节

本文仅仅是接口说明,对于具体的使用方式,后续会说明。

为了让用户更好地管理文件并减少混乱,Android 10(API 级别 29)引入了分区存储。分区存储是应用只能看到本应用特定的目录下的文件(通过 Context.getExternalFilesDir() 访问),公共目录下的媒体文件(通过MediaStore访问),以及存储访问框架返回的文件,不能像以前为所欲为了。

Android10(API级别29)没有强制要求使用分区存储,但是在Android11(API级别30)强制使用分区存储。

下表是分区存储情况下的文件访问方式以及卸载后的行为:

11 Android 分区适配 安卓11分区存储_1024程序员节_02

存储位置分类

  1. 内存存储的/data/data/App包名/,App的内部存储,App的特定目录
  2. 外部存储的/sdcard/Android/data/App包名/,App的外部存储的位置,App特定目录。Android8之后不允许其他应用直接访问。
  3. 外部存储的/sdcard/Android/media/App包名/,App的外部存储的位置,App特定目录。
  4. 外部存储的/sdcard/公共目录(Music/,DCIM/等等),App的外部存储的公共部分,分区存储还可以访问。
  5. 除2,3,4目录外,sdcard其他目录,分区存储主要的关注点就是这里。分区存储中,可以通过存储访问框架(SAF)访问。

Android10之前目录接口

Context获取应用特定目录

通过Context获取的目录是应用的特定目录,一旦卸载应用,文件就不复存在。Android10,Android11没有受到影响。

Context.getFilesDir
/data/user/0/com.kuaima.storage/files
Context.getDataDir()
/data/user/0/com.kuaima.storage
Context.getCacheDir()
/data/user/0/com.kuaima.storage/cache
Context.getExternalCacheDir()
Context.getExternalMediaDirs()
/storage/emulated/0/Android/media/com.kuaima.storage//此目录可以被MediaStore扫描到,在Android11接口被废弃,但仍可使用
Context.getExternalCacheDir()
/storage/emulated/0/Android/data/com.kuaima.storage/cache
Context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)
/storage/emulated/0/Android/data/com.kuaima.storage/files/Music
Context.getExternalFilesDirs(Environment.DIRECTORY_MUSIC)
/storage/emulated/0/Android/data/com.kuaima.storage/files/Music

通过Environment访问

当应用卸载时候,这部分数据不会被删除。公共目录部分(DCIM等等目录)还可以在分区存储模型中访问,但基本只能是媒体文件。而非公共部分,Android10不开启分区存储还可继续使用,但Android11强制不让使用,我们适配的关键也就是这部分内容。

Environment.getExternalStorageDirectory()
/storage/emulated/0
Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DCIM
)
/storage/emulated/0/DCIM//公共目录还可以在Android10/Android11中使用,但只能访问媒体文件。

分区存储的目录访问

分区存储一般分为两部分,一部分是应用的特定目录,一部分是公共目录的访问。但在Android11中申请MANAGE_EXTERNAL_STORAGE 了权限,我们的App也可以访问所有目录。

访问应用特有目录

应用的特定目录包含两部分,一部分是内部存储,一部分是外部存储,一旦卸载应用,文件就不复存在。我们可以通过File直接访问。

内部存储访问

Context.getFilesDir()
/data/user/0/com.kuaima.storage/files
Context.getCacheDir()
/data/user/0/com.kuaima.storage/cache

通过外部存储访问:

Context.getExternalCacheDir()
/storage/emulated/0/Android/data/com.kuaima.storage/cache
Context.getExternalCacheDir()
/storage/emulated/0/Android/data/com.kuaima.storage/cache
Context.getExternalMediaDirs()
/storage/emulated/0/Android/media/com.kuaima.storage
//此目录可以被MediaStore扫描到,在Android11接口被废弃,但仍可使用

访问共享存储目录

共享目录是Android10之前通过Environment.getExternalStoragePublicDirectory(type)访问的目录。

type可以是

• Environment.DIRECTORY_MUSIC -> Music/

• 
Environment.DIRECTORY_PODCASTS -> Podcasts/

• 
Environment.DIRECTORY_RINGTONES -> Ringtones/

• 
Environment.DIRECTORY_ALARMS -> Alarms/

• 
Environment.DIRECTORY_NOTIFICATIONS -> Notifications/

• 
Environment.DIRECTORY_PICTURES -> Pictures/

• 
Environment.DIRECTORY_MOVIES -> Movies/

• 
Environment.DIRECTORY_DOWNLOADS -> Download/

• 
Environment.DIRECTORY_DCIM -> DCIM/

• 
Environment.DIRECTORY_DOCUMENTS -> Documents

我们在目标版本为Android11和Android10的时候可以使用如下的方式访问:

  • 通过MediaStore
  • 通过File的方式访问。Android 10 不支持
  • 通过存储访问框架 (SAF)

MediaStore

MediaStore基本用于访问媒体文件。MediaStore这个是媒体内容提供者与应用程序之间的协作类,包含受支持的URI和列的定义。

系统会自动扫描外部存储卷,并将媒体文件添加到以下明确定义的集合中:

  • 图片(包括照片和屏幕截图),存储在 DCIM/Pictures/ 目录中。系统将这些文件添加到 MediaStore.Images 表格中。
  • 视频,存储在 DCIM/Movies/Pictures/ 目录中。系统将这些文件添加到 MediaStore.Video 表格中。
  • 音频文件,存储在 Alarms/Audiobooks/Music/Notifications/Podcasts/Ringtones/ 目录中,以及位于 Music/Movies/ 目录中的音频播放列表中。系统将这些文件添加到 MediaStore.Audio 表格中。
  • 下载的文件,存储在 Download/ 目录中。在搭载 Android 10(API 级别 29)及更高版本的设备上,这些文件存储在 MediaStore.Downloads 表格中。此表格在 Android 9(API 级别 28)及更低版本中不可用。

媒体库还包含一个名为 MediaStore.Files 的集合。其内容取决于您的应用是否使用分区存储(适用于以 Android 10 或更高版本为目标平台的应用):

  • 如果启用了分区存储,集合只会显示您的应用创建的照片、视频和音频文件。
  • 如果分区存储不可用或未使用,集合将显示所有类型的媒体文件。

通过MediaStore我们就可以获取图片,视频,音频等格式的文件信息。

Android11中新增了以下接口:

  1. createWriteRequest() 用户向应用授予对指定媒体文件组的写入访问权限的请求。
  2. createFavoriteRequest()用户将设备上指定的媒体文件标记为“收藏”的请求。对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为“收藏”。
  3. createTrashRequest() 用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容会在系统定义的时间段后被永久删除。
  4. createDeleteRequest() 用户立即永久删除指定的媒体文件(而不是先将其放入垃圾箱)的

直接使用File方式

为了帮助应用更顺畅地使用第三方媒体库,Android 11 允许使用除 MediaStore API 之外的 API 访问共享存储空间中的媒体文件,不过可以转而选择使用以下任一 API 直接访问媒体文件:

  • File API。
  • 原生库,例如 fopen()

比如我们可以直接使用File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test.jpg")进行访问文件。

注意:

比如DCIM下的txt文件,应用会崩给你看,如果访问jpeg格式的文件就没问题。所有公共目录注意支持的格式。更有趣的事在Download目录下,你自己的应用可以写入任意格式,然后自己的应用也可以读取,但是卸载后然后再安装就只能读取到媒体文件,比如txt是读不到的。其他应用写入的也是如此,另一个应用读取也是如此。

存储访问框架(SAF)

应用可以使用存储访问框架与包括外部存储和云端存储空间在内的文档提供器互动。此框架支持用户与系统选择器互动,从而选择文档提供器以及的应用创建、打开或修改的特定文档和其他文件。

系统的选择器如下:

11 Android 分区适配 安卓11分区存储_分区存储_03

使用方式:

  1. 创建Intent,Intent的action支持如下的方式
  1. ACTION_CREATE_DOCUMENT支持用户将文件保存在特定位置。
  2. ACTION_OPEN_DOCUMENT 支持用户选择要打开的特定文档或文件。
  3. 授予对目录内容的访问权限 ACTION_OPEN_DOCUMENT_TREE intent 操作在 Android 5.0(API 级别 21)及更高版本中提供,支持用户选择特定目录,授予应用对该目录中所有文件和子目录的访问权限。
  1. 用户看到一个系统选择器,用于选择文件或者目录。
  2. 应用获得用户所选文件或者目录 URI 的读写访问权限。利用该 URI,应用可以在选择的文件或者目录进行操作。

注意:SAF也可以访问SDcard下的非公共目录。

访问所有文件目录

Android11中新增了MANAGE_EXTERNAL_STORAGE , 申请了此权限,可以访问所有文件目录。

基本是有如下情况使用,可以使用MANAGE_EXTERNAL_STORAGE

  • 备份和恢复
  • 防病毒应用
  • 文档管理应用
//声明权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

//去设置页打开权限
 val intent= Intent()
 intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
 startActivity(intent)

//判断是否获取MANAGE_EXTERNAL_STORAGE权限:
 val isHasStoragePermission= Environment.isExternalStorageManager()

其他接口

  1. Android10新增requestLegacyExternalStorage ,此标志为在Android10系统中是否开启分区存储,如果在manifest中配置成requestLegacyExternalStorage = true ,Android10将不使用分区存储
  2. Android 11新增了preserveLegacyExternalStorage。大多数应用都不需要使用 preserveLegacyExternalStorage。此标记仅适用于这样一种情况:应用数据需要迁移SDcard的数据到分区存储目录下,此时希望更新应用时保留对数据的访问权限。
    如果使用了 preserveLegacyExternalStorage,旧版存储模型只在用户卸载应用之前保持有效。如果用户在搭载 Android 11 的设备上安装或重新安装您的应用,那么无论 preserveLegacyExternalStorage 的值是什么,应用都无法停用分区存储模型。