本文字数:5037字
预计阅读时间:35分钟
分区存储概念
为了让用户更好地控制自己的文件并减少混乱,Android 10针对应用推出的一个新的存储范例,新的存储模型会让以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被赋予了对外部存储设备的分区访问权限,即分区存储(scoped storage)。分区存储改变了应用在设备的外部存储设备中存储和访问文件的方式。
从另一个角度来说,分区存储的推出更好的保护用户的隐私。默认情况下,对于以 Android 10 及更高版本为目标平台的应用,其访问权限范围限定为外部存储,即分区存储。此类应用可以查看外部存储设备内以下类型的文件,无需请求任何与存储相关的用户权限:
特定于应用的目录中的文件(使用 getExternalFilesDir() 访问)。
应用创建的照片、视频和音频片段(通过媒体库访问)。
意思是说,我们的app在外部存储设备(即SD卡)上存文件的时候,需要先想明白需要存的数据是属于app私有的还是需要分享的,如果是app私有的,存在getExternalFilesDir()返回的文件夹下,也就是Android/data/包名/files/文件夹;如果是需要分享的,需要采用媒体库(MediaStore)的方式来存取,后面会讲怎么存取。需要指出的是在分区存储模型下存取共享媒体文件是不需要存储权限的,而旧的存储模型是需要存储权限的。
下表总结了分区存储如何影响文件访问:
文件位置 | 所需权限 | 访问方法 (*) | 卸载应用时是否移除文件? |
---|---|---|---|
特定于应用的目录 | 无 | getExternalFilesDir() | 是 |
媒体集合(照片、视频、音频) | READ_EXTERNAL_STORAGE(仅当访问其他应用的文件时) | MediaStore | 否 |
下载内容(文档和电子书籍) | 无 | 存储访问框架(加载系统的文件选择器) | 否 |
适配分区存储
为什么要适配
在分区存储模型下,外部存储设备的公共区域是不让访问的,如果强行访问,会在创建或读写文件的api上报错,具体看分区存储模型下,访问SD卡公共区域错误举例。那么有没有办法关闭分区存储模型呢?
有两种办法,第一种是app的targetSdkVersion永远低于29,这个是不现实的;第二种办法是targetSdkVersion 29时覆盖安装和新安装能关闭,targetSdkVersion 30时覆盖安装能关闭,新安装是没有办法关闭的,具体看requestLegacyExternalStorage和preserveLegacyExternalStorage的理解。而且说不定,Android 12出来后,以Android 12为目标平台的app都是强制执行分区存储模型的。所以分区存储是一定需要适配的,而且越早适配越好。
怎么适配
适配分为两部分,新数据的存储和老数据的迁移,我们先说新数据的存储。
新数据的存储
把app所有需要存的数据梳理一遍,对于私有数据我们存到SD卡app私有目录下,对于需要共享的媒体数据我们通过MediaStore的方式。数据放到私有目录很简单我们不讲,主要讲怎么共享媒体数据,以视频为例,看下面的代码:
/**
* 保存共享媒体资源,必须使用先在MediaStore创建表示视频保存信息的Uri,然后通过Uri写入视频数据的方式。
* 在"分区存储"模型中,这是官方推荐的,因为在Android 10禁止通过File的方式访问媒体资源,Android 11又允许了
* 从Android 10开始默认是分区存储模型
*
*
* 说明:
* 此方法中MediaStore默认的保存目录是/storage/emulated/0/video
* 而Environment.DIRECTORY_MOVIES的目录是/storage/emulated/0/Movies
* @param context
* @return
*/
static Uri getSaveToGalleryVideoUri(Context context, String videoName, String mineType, String subDir) {
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, videoName);
values.put(MediaStore.Video.Media.MIME_TYPE, mineType);
values.put(MediaStore.Video.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + subDir);
}
Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
printMediaInfo(context, uri);
return uri;
}
需要保存视频的时候,其实就是先在MediaStore的Video表插入一条记录,获取一个Uri,然后把视频写入这Uri就行了。具体保存位置,我们不用操心,它其实是保存到了Sd卡的Movies文件夹下了,在Android 10以上系统提供RELATIVE_PATH字段用于创建子目录。
我们会问,高版本可以这样共享视频,那么低版本可以吗?如果可以的话,低版本的也用这种方式,一套方案解决。理论上是可以的,毕竟MediaStore从Android诞生就存在。可