
Android 在 Q 上引入了分区存储,对外部存储访问收拢了宽裕的权限,对存储数据文件的读写进行了限制,那么 Android R 上又有哪些变化呢 ?
本文就让我们一起来了解一下 , Let’s go ~
讲 R 上分区存储变化之前,我们先来回顾一下 Q 上相关内容
Android 10 / Q 先回顾
Android中存储可以分为两大类: 私有存储和共享存储
- 私有存储 (Private Storage) : 每个应用在内部存储种都拥有自己的私有目录(
/data/data/packageName
),其它应用看不到,彼此也无法访问到该目录
- 共享存储 (Shared Storage) : 除了私有存储以外,其他的一切都被认定是共享存储,比如媒体集 (
Media Collection
) 和SD卡外部应用存储目录
一、导火线
在分区存储之前,某些应用中,即使功能很简单,大部分都不需要这么宽泛的权限。
这就使得某些应用程序
1、乱占空间 :各种各样的文件散布在磁盘的各个地方,当用户卸载应用之后,这些被遗弃的 “孤儿“ 被滞留在原地,无家可归,占用了磁盘空间,最终结果就会导致磁盘不足
2、随意读取用户的数据
3、随意读取应用的数据
因此 —— 分区存储 Baby 诞生了 ~
它的降临 , 限制了过于宽泛的 存储权限
成长的同时, 在 Android 10 / Q 上它需要遵循三个原则 :
- 更好的文件属性 : 系统应用知道什么文件属于哪一个app,让用户更加容易管理他们自己的文件。当app被卸载了,被应用创建的内容,除非用户希望保留,否则不应该保留下来。
- 用户的数据安全 :当用户下载文件,比如敏感的电子邮件附件,这些文件对大多数应用程序都不应该可见
- 应用的数据安全 :当app将特定于应用程序的文件写入外部存储时,其他应用程序不应该可见这些文件
二、规则点
- 应用访问自己的应用目录不受限制 ( 包括
内部和外部
) 无需任何权限- 应用向媒体集和下载目录提供文件,如果您要想保存图片、视频、音频、文档,无需任何权限
- 不再提供宽泛的共享存储 (
Share Storage
), 读写存储权限只能访问提供的媒体集 (图片集
、视频集
、音频集
)- 位置元数据限制,获取图片上的位置等信息需要请求权限,如果不请求权限,读取图片的信息的时候,位置元数据将会被删除
- 读取PDF或其他类型的文件,需要调用系统的文件选择器 (
Storage Access Framerwork API
)- 在媒体集或应用目录之外,写任何文件都需要系统的文件选择器, 这样用户能选择并确认将文件存在哪里
- requestLegacyExternalStorage 开关值,在清单文件配置,如果开启了,存储权限就会像之前版本中的Android一样运作
三、详细说明
1. 媒体文件集
用于和其它应用分享媒体文件(图片、音频、视频文件)
Android 10 中,自己的App无需任何权限就能向媒体集添加文件,同时您也可以编辑和删除您自己添加的媒体文件。
如果你要读取并操作并非您的应用所创建的媒体文件,就需要读取外部存储权限,如果用户没有同意,您的应用将无法编辑和删除并非您的应用所创建的媒体文件。
这样一来,您的应用想编辑和删除媒体文件时,用户就能获得完整的控制权
2. 下载文件集
用于和其它应用分享非媒体文件(非图片、非音频、非视频文件)
下载文件集,和媒体文件集一样,您无需请求任何权限就能在这个集中添加、编辑、删除非媒体文件。
与媒体文件不同的是,即使有读取外部存储权限, 也是不允许您访问由其他应用创建添加的非媒体文件。想要取得权限,您需要通过调用 Storage Access Framerwork API,启动系统文件选择器,让用户可以进行选择可以访问哪些文件和目录。
如果用户允许您访问一个文件,那么这个权限将是完整的权限,你无需其他额外的任何权限,就可以任意的读取、编辑删除媒体文件和非媒体文件。
这样一来,用户就能获得完整的控制权,更好的管理应用在何时访问敏感的非媒体文件
3. 限制位置元数据
对媒体文件中敏感的元数据进行了访问权限的限制,主要限制位置元数据
可以在应用获取那些其他应用创建添加的媒体文件的时候,直接删除这些元数据
如果想要获取图片的位置信息,需要声明请求权限 ACCESS_MEDIA_LOCATION
,用户在Settings UI里看不到这个权限, 但是它属于运行时权限,所以必须要在Manifest里声明该权限 , 并在运行时同时请求该权限和读取外部存储权限
MediaStore.setRequireOriginal() : 获取当前磁盘的体积容量
4. 媒体文件访问路径
Android 10 锁定了公共目录文件路径的权限。
所有的应用都尽量继续使用MediaStore,因为后台文件路径 [ /sdcard/DCIM/xxx.JPG
] 请求或使用文件路径[ /sdcard/DCIM/xxx.JPG
]进行I/O流请求都是代理给 MediaStore 类的
不建议直接使用媒体文件访问路径 [ /sdcard/DCIM/xxx.JPG ] ,应用的性能会略有下降
5. MediaStore 的强制性
文件需要在适当的目录进行创建,
媒体文件需要媒体目录进行创建, 不能在图片目录创建音频文件,如果您想访问由其他应用创建的媒体文件,您就要请求外部存储读取权限。如果您没有获得媒体位置权限,就仍然会在读取由其他应用创建的媒体文件时,被系统拿掉位置信息。
当然,非媒体文件必须在Downloads下创建,所有Dowonloads目录下也只能创建像PDF或其它的非媒体文件
读取由其他应用创建的非媒体文件,也将需要 Storage Access Framerwork API
四、总结
- 特定于应用的目录 –> 无需权限 –> 访问方法 getExternalFilesDir() –> 卸载应用时移除文件
- 媒体集合(照片、视频、音频) –> 需要权限READ_EXTERNAL_STORAGE(仅当访问其他应用的文件时) –> 访问方法 MediaStore –> 卸载应用时不移除文件
- 下载内容(文档和电子书籍)–> 无需权限 –> 存储访问框架(加载系统的文件选择器)–>卸载应用时不移除文件
Android R 再出发
一、变化
1. 分区存储强制执行
requestLegacyExternalStorage 是为了给开发人员更多的测试时间,在Android 10上设置的一个开关值
在 Android 11 上发生了变更
- 目标版本
API < = 29 时
, 应用仍可请求 requestLegacyExternalStorage 属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。
- 目标版本
API > 29
,将无法使用 requestLegacyExternalStorage,而且也没有其他标记可以提供该停用功能。
2. 媒体文件访问权限
为了在保证用户隐私的同时可以更轻松地访问媒体,Android 11 增加了以下功能。
(1) 执行批量操作
MediaStore API 新增方法
方法 | 说明 |
---|---|
createWriteRequest (ContentResolver, Collection |
用户向应用授予对指定媒体文件组的写入访问权限的请求。 |
createFavoriteRequest (ContentResolver, Collection |
用户将设备上指定的媒体文件标记为“收藏”的请求。对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为“收藏”。 |
createTrashRequest (ContentResolver, Collection |
用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容在特定时间段(默认为 7 天)后会永久删除。 |
createDeleteRequest (ContentResolver, Collection |
用户立即永久删除指定的媒体文件(而不是先将其放入垃圾箱)的请求。 |
系统在调用以上任何一个方法后,会构建一个 PendingIntent 对象。应用调用此 intent 后,用户会看到一个对话框,请求用户同意应用更新或删除指定的媒体文件。
(2) 使用原始路径访问文件
从 Android 11 开始,具有 READ_EXTERNAL_STORAGE 权限的应用可以使用直接文件路径(如: /sdcard/DCIM/xxx.JPG` 的格式)和原生库来读取设备的媒体文件。通过这项新功能,应用可以更顺畅地使用第三方媒体库。
注意:使用直接路径和原生库保存媒体文件时,应用的性能会略有下降。请尽可能改用 MediaStore API。
(3) 测试原始文件路径访问
要激活此功能以进行测试,请执行以下操作:
- 打开系统设置。
- 导航到 系统 > 开发者选项 > 功能标记。
- 找到
settings_fuse
并启用。settings_fuse
下的说明现在应显示为true
。- 重启设备。
3. 文件和目录访问限制
以下是存储访问框架 ( SAF: Storage Access Framerwork API ) 相关的变更
(1) 访问目录
您无法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操作来请求访问以下目录:
- Downloads 根目录
- 设备制造商认为可靠的各个 SD 卡卷的根目录,无论该卡是模拟卡还是可移除的卡。
(2) 访问文件
您无法再使用 ACTION_OPEN_DOCUMENT_TREE 或 ACTION_OPEN_DOCUMENT intent 操作来请求用户从以下目录中选择单独的文件:
Android/data/
目录及其所有子目录。Android/obb/
目录及其所有子目录
(3) 测试变更
要测试此行为变更,请在应用的清单文件中将 requestLegacyExternalStorage
的值设置为 false
。您可通过执行以下操作来确认行为变更是否已对应用生效:
- 通过
ACTION_OPEN_DOCUMENT_TREE
操作调用 intent。检查Downloads
目录是否显示并呈灰显状态。- 通过
ACTION_OPEN_DOCUMENT
操作调用 intent。检查Android/data/
和Android/obb/
目录是否都不显示。
4. 权限
在 Android 11 上,您会看到两种权限请求的字符串,这个取决于应用的目标 SDK 版本, 如果目标版本在 Android 11,那么只请求在外部存储中访问媒体文件,如果是其他目标版本,那么将显示的是旧版本的权限请求字符串
(1) 以任何版本为目标平台
不管应用的目标 SDK 版本是什么,以下变更均会在 Android 11 中生效:
- 存储 运行时权限已重命名为 文件 和 媒体。
- 如果应用未选择停用分区存储,并且请求 READ_EXTERNAL_STORAGE 权限,则用户会看到不同于 Android 10 的对话框。该对话框会指示应用正在请求访问照片、视频、音频剪辑和文件。在系统设置的设置 > 隐私 > 权限管理器 > 文件和媒体页面中,如果已授予权限,应用会列在允许存储所有文件下。
(2) 以 Android 11 为目标平台
则 WRITE_EXTERNAL_STORAGE
权限和 WRITE_MEDIA_STORAGE
特许权限将不再提供任何其他访问权限。
5. 所有文件访问权限
像文件管理操作或备份和还原操作等需要访问大量的文件,通过执行以下操作,这些应用可以获得”所有文件访问权限”:
- 声明
MANAGE_EXTERNAL_STORAGE
权限- 将用户引导至系统设置页面,在该页面上,用户可以对应用启用授予所有文件的管理权限选项
简单说,像这种文件管理器、备份及存储类应用。你需要在 Google Play Developer Console上填写声明表格说明为什么需要MANAGE_EXTERNAL_STORAGE
权限,提交之后会被审核是否加入白名单,一旦加入成功以后,您的应用就可以向用户索要权限了,如果用户也同意您应用的访问权限请求,MadiaStore 访问将不再过滤,包括非媒体库文件。但是获得此权限的应用仍然无法访问这些目录在存储卷上显示为 Android/data/
的子目录,也就是属于其他应用的应用专属目录。
MANAGE_EXTERNAL_STORAGE
权限允许应用访问共享的存储空间中的潜在敏感数据。
二、示例
按照上面 Q ~ R 这一些列规则点,我们可以将示例进行分类如下
- 按照系统版本分类:Android Q 、 Android R
- 按照分区存储分类:媒体文件集 、下载文件集、App应用程序专属特定目录
- 按照文件归属分类:自己应用和其他应用
- 按照文件类型分类:媒体集(图片、视频、音频 .etc)、非媒体(PDF,DOC,XLS .etc)
- 按照操作行为分类:增、删、改、查
那么接下来示例,我将会按照以上的5大分类
,结合两个版本代码简单说明
然后呢?我们该怎么做?
其实逻辑代码我们可以简化理解这么去定义
- 一个
Media
操作类:包括了(图片、视频、音频)他们3类分别的增、删、改、查- 一个
Download
操作类:包括了非媒体的增、删、改、查- 一个
Assets
操作类:负责从asset下拷贝文件到本地演示
开发环境:
- Android Studio 4.1 canary 2
- Android R 模拟器 / 真机
Media 类
[MyMediaCollection.kt]
1 | class MyMediaCollection { |
Download 类
[ MyDownloadCollection.kt ]
1 | class MyDownloadCollection { |
Assets 帮助类
[ AssetHelper.kt ]
1 | class AssetHelper { |
MainActivity
[ MainActivity.]
1 | class MainActivity : AppCompatActivity() { |
build.gradle
1 | minSdkVersion 'R' |
AndroidManifest.xml
1 | <!-- Android 11 不再需要 WRITE_EXTERNAL_STORAGE --> |
后记
以上的示例大部分基于Android Q 开始,少部分介绍了Android R 新方法。
更多API的使用 、更多应用场景、更多差异测试,请自己进行参照扩展测试。有什么问题,欢迎在评论处留言。
[相关文献]
- 1. Android 官网 - Storage updates in Android 11
- 2. Modern User Storage on Android
- 3. Scoped Storage on Android 11
- 4. Working with Scoped Storage
最后 , 感谢您的阅读 。
本文对Android Q 上分区存储回顾,Android R 上分区存储变化的简单总结。 如果不足之处,欢迎指正。