一:为什么设计分区存储
在Android 10(Q,Api 29)之前,访问sd卡(Shared Storage)上内容,需要申请WRITE(READ)_EXTERNAL_STORAGE权限。
在分区存储出现之前,sd卡上存储的内容会存在以下问题:
- sd卡上的应用专属目录(Android/data/包名),其他应用只要有读写权限也可以访问,不安全。
- sd卡上的共享目录,在应用卸载后还存在,导致空间越占越多,难以维护。
- 目前有一半以上的App都申请了存储权限去访问sd卡上内容,但是实际上这些应用并不需要这么宽泛的权限
为此Google在Android 10(Q)上引入了分区存储,把存储空间分区成为多个专属集,对权限场景进行细分,按需所取。在Android 10上提供了属性让开发者选择是否开启分区存储,但是在Android 11(R)上开始强制执行分区存储。
二:分区存储设计
Scoped Storage需要遵循以下三个原则:
- 更好的文件属性:这意味着系统应该知道哪些应用创建了哪些文件,这样方便系统更好管理文件,当然在应用卸载时,与应用相关的内容也会被删除。
- 保护应用数据:sd卡上的应用专属目录也不能被其他应用轻易访问。
- 保护用户数据:用户存储在sd卡上的共享目录,也不能让设备上拥有存储权限的所有应用访问。
遵循以上原则,在分区存储中增加以下功能:
- 每个应用可以不需要申请权限地访问自身的应用目录:包括内部和外部应用目录。
- 每个应用_可以不需要申请权限地向MediaStore集和Download目录写入文件。如果想要读取其他应用创建的媒体文件,则需要READ存储权限,且写入权限在下个Android版本中被取消。_
- 重新定义了存储权限,不再提供广泛的存储权限,只能访问整理好的媒体集。
- 媒体集里item的Exif元数据(例如包含照片拍摄的位置信息等),属于用户私密敏感信息,需要请求ACCESS_MEDIA_LOCATION权限。该权限在系统“设置”页面是看不到的,属于运行时权限,所以必须在manifest中声明。在运行时,需要同时请求该权限和READ存储权限。
- 若要访问非媒体集的文件,则必须通过Storage Access Framework(SAF)启动系统选择器来访问(即使拥有READ存储权限也无法读取),这样用户可以选择应用可以访问哪些目录,一旦用户选择某个文件,应用会得到该文件的全部权限。
由于让各App适配分区存储较难,为了给予各App更多时间去升级和适配,因此对于已经升级targetSdkVersion为Android 10的App,可以通过在manifest中设置某个开关来关闭分区存储功能。
<application
android:requestLegacyExternalStorage="true">
</application>
三:存储权限的重新定义
在未升级的App上,存储权限意味着广泛的存储权限;在升级的App上,存储权限意味着访问媒体集,这会使开发者迷惑。
Google通过更新权限UI来解决这个问题,当App请求存储权限时,权限弹窗上展示的字符串会根据App是否升级以及是否在使用分区存储而有所不同,这样可以方便用户理解此时申请的权限范围。
四:SAF框架的改动
为了读取非本应用创建的非媒体文件,需要通过SAF框架。但是Google发现部分使用了SAF框架希望获得宽泛的共享存储权限 ,例如某些App使用ACTION_OPEN_DOCUMENT_TREE启动时,会选择sd卡root目录的权限。因此Google限制了SAF让用户无法选取sd卡root、任何位于android/data目录下的文件、以及Downloads目录。但是用户仍然可以选择Downloads下的单个文件,只是无法选取整个目录而已。
但是Google也为那些真正需要共享存储宽泛权限的应用添加了特别应用访问权限,例如文件管理器、备份和存储应用,只有能够证明自身确实有这一需求的应用才能获取这个权限,并且需要通过Google Play Developer Console向Google Play提交一份声明表格来将自身应用加入到白名单中。
五:各版本手机存储目录的变化情况
需要注意的是,当一个应用被卸载时,MediaStore中的内容不会删除,并且新安装App如果要访问的话,需要权限。MediaStore中存放的应该是愿意和其他App共享的内容,若不共享则应该放在sd卡上的应用专属目录内。
六:MediaStore
MediaStore内支持的类型有:
- 照片:存储在MediaStore.Imges中。
- 视频:存储在MediaStore.Video中。
- 音频:存储在MediaStore.Audio中。
- MediaStore.Files,返回的集合包含Imags、Video、Audio。
当然本应用写入MediaStore中的内容,如果设置了IS_PENDING为true,则代表该item被隐藏,其他应用即使能获取到这些item,也不能访问item里面具体数据,只有这些item的所属App设置为false才可以被其他App访问。
七:具体例子
1、媒体播放软件:需要获取手机设备上所有视频文件,展示这些文件,并且在用户需要时播放这些文件。
开发使用到的Api如下:
- MediaStore:可以把所有媒体文件进行索引,这就改善了可发现性。并且使用ContentResolver来查询。
- 申请READ_EXTERNAL_STORAGE权限:因为你访问的是全部的媒体文件。
2、gmail应用
分为以下2中场景:
1、编写邮件,并从sd卡上选取文件作为附件发送出去。
开发使用到的Api如下:
- SAF框架:在选取文件时,需要使用ACTION_OPEN_DOCUMENT ;选取文件夹时,需要使用ACTION_OPEN_DOCUMENT_TREE。
2、将邮件中的附件保存在sd卡上,文件位置可以是用户选定的任何地方。
开发使用到的Api如下:
-
SAF框架,使用ACTION_CREATE_DOCUMENT。
参考网址:www.youtube.com/watch?v=UnJ… 2019开发者大会关于分区存储的讲解视频)