Android 11 应用兼容性适配指导
本文非原创-若侵犯到原创利益,请联系删除
原文 https://open.oppomobile.com/wiki/doc#id=1072
一、隐私更新
2.1 存储
2.1.1 分区存储
1.1. 背景
Android 11 进一步增强了平台功能,为外部存储设备上的应用和用户数据提供了更好的保护。作为这项工作的一部分,平台引入了进一步的改进,以简化向分区存储的转换。
为了让用户更好地控制自己的文件,保护用户隐私数据,并限制文件混乱情况,Android 11在分区存储基础上限制了应用访问其他应用的文件。
分区存储将存储空间分为两部分:
● 公共目录:Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones等
■ 公共目录的文件在App卸载后,不会删除
■ 可以通过SAF、MediaStore接口访问
■ 拥有权限,也能通过路径直接访问
● 应用专属目录
■ 应用专属目录只能自己直接访问
■ App卸载,数据会清除。
1.2. 兼容影响
当您将应用更新为以 Android 11 为目标平台后,您将无法使用requestLegacyExternalStorage,而且也没有其他标记可以提供停用分区存储。
分区存储对于App访问存储方式、App数据存放以及App间数据共享,都产生很大影响。
而Environment.getExternalStorageDirectory() 在 API Level 29 开始已被弃用,开发者应迁移至 Context#getExternalFilesDir(String), MediaStore, 或Intent#ACTION_OPEN_DOCUMENT。
1.3. 适配
1 应用targetSdkVersion
应用targetSdkVersion >= 30,都会强制打开分区存储,同时requestLegacyExternalStorage将会无效。
如果您需要对已安装的应用进行适配分区存储的数据迁移,则可以在应用更新到目标平台为Android 11版本后仍暂时保留原有的存储模式。请在应用的manifest中设置preserveLegacyExternalStorage属性为true,应用更新到android 11可以保留存储继承模式。
2 应用私有目录访问
对于运行在Android 11的应用,无论targetSdkVersion是什么都无法访问Emulated存储中的其他应用私有目录(Android/data)。SAF(Storage Access Framework)同样也禁止访问应用私有目录。
某些应用的核心用例需要访问大量的文件,如文件管理操作或备份和恢复操作。这些应用可通过执行以下操作获取“所有文件访问权限”:
● 声明 MANAGE_EXTERNAL_STORAGE 权限。
● 使用 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面,在该页面上,用户可以为您的应用启用以下选项:授予所有文件的管理权限。
● 注意:获得此权限的应用仍然无法访问属于其他应用的应用专用目录。这些目录在存储卷上显示为 Android/data/ 的子目录。
3 直接路径访问
注意:使用直接路径和原生库保存媒体文件时,应用的性能会略有下降。请尽可能改用MediaStore API。
具体适配参考:
https://developer.android.google.cn/training/data-storage#scoped-storage
https://developer.android.google.cn/preview/privacy/storage
1.3.1. 运行模式
1.3.1.1. App运行模式
在Android 11版本上,系统会根据App targetSdkVersion决定运行模式:
● App targetSdkVersion >= 30,默认为分区存储,并且无法取消。
● App targetSdkVersion < 29,默认为分区存储,可通过requestLegacyExternalStorage更改
应用可以通过AndroidManifest.xml设置requestLegacyExternalStorage, 选择对应的方式:
● App targetSdkVersion < 29,声明了READ_EXTERNAL_STORAGE,默认Legacy Mode
● App在下列条件都成立时
■ 声明 MANAGE_EXTERNAL_STORAGE 权限。
■ 使用 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面,在该页面上,用户可以为您的应用启用以下选项:授予所有文件的管理权限。
App拥有外置存储空间Read、Write权限。但是通过Environment.isExternalStorageLegacy接口判断,返回不一定是Legacy Mode。
1.3.1.2. 判断当前App运行模式
判断当前App运行什么模式,可以通过这个API判断:
Environment.isExternalStorageLegacy() (added in api 29);
1.3.2. 读写公共目录
App启动分区存储后,只能直接访问自身专属目录,所以Android 11,提供了两种访问公共目录的方法(特殊直接路径访问参考1.3.8. 直接路径访问):
1.3.2.1. 通过MediaStore定义的Uri
MediaStore提供了下列几种类型的访问Uri,通过查找对应Uri数据,达到访问的目的。
下列每种类型又分为三种Uri,Internal、External、可移动存储:
●Audio
■ Internal: MediaStore.Audio.Media.INTERNAL_CONTENT_URI
content://media/internal/audio/media。
■ External: MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
content://media/external/audio/media。
■ 可移动存储: MediaStore.Audio.Media.getContentUri
content://media/<volumeName>/audio/media。
● Video
■ Internal: MediaStore.Video.Media.INTERNAL_CONTENT_URI
content://media/internal/video/media。
■ External: MediaStore.Video.Media.EXTERNAL_CONTENT_URI
content://media/external/video/media。
■ 可移动存储: MediaStore.Video.Media.getContentUri
content://media/<volumeName>/video/media。
● Image
■ Internal: MediaStore.Images.Media.INTERNAL_CONTENT_URI
content://media/internal/images/media。
■ External: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
content://media/external/images/media。
■ 可移动存储: MediaStore.Images.Media.getContentUri
content://media/<volumeName>/images/media。
● File
■ MediaStore. Files.Media.getContentUri
content://media/<volumeName>/file。
● Downloads
■ Internal: MediaStore.Downloads.INTERNAL_CONTENT_URI
content://media/internal/downloads。
■ External: MediaStore.Downloads.EXTERNAL_CONTENT_URI
content://media/external/downloads。
■ 可移动存储: MediaStore.Downloads.getContentUri
content://media/<volumeName>/downloads。
1.3.2.1.1. 获取所有的Volume
对于前面描述的Uri中,getContentUri如何获取所有<volumeName>,可以通过下述方式:
1.3.2.1.2.Uri跟公共目录关系
MediaProvider对于App存放到公共目录文件,通过ContentResolver insert方法中Uri来确定,其中下表中<Uri路径>为相对路径,完整为:
content://media/<volumeName>/<Uri路径>。
Mine Type | Uri路径 | 一级目录 |
audio/* | images/media images/media/# |
Environment.DIRECTORY_ALARMS |
Environment.DIRECTORY_MUSIC | ||
Environment.DIRECTORY_NOTIFICATIONS | ||
Environment.DIRECTORY_PODCASTS | ||
Environment.DIRECTORY_RINGTONES | ||
image/* | audio/albumart audio/albumart/# |
Environment.DIRECTORY_MUSIC |
audio/playlists audio/playlists/# |
Environment.DIRECTORY_MUSIC | |
video/* | video/media video/media/# |
Environment.DIRECTORY_DCIM |
Environment.DIRECTORY_MOVIES | ||
image/* | images/media images/media/# |
Environment.DIRECTORY_DCIM |
Environment.DIRECTORY_PICTURES | ||
image/* | video/thumbnails video/thumbnails/# |
Environment.DIRECTORY_MOVIES |
image/* | images/thumbnails images/thumbnails/# |
Environment.DIRECTORY_PICTURES |
downloads downloads/# |
Environment.DIRECTORY_DOWNLOADS | |
file file/# |
Environment.DIRECTORY_DOWNLOADS | |
Environment.DIRECTORY_DOCUMENTS |
通过ContentResolver,根据不同的Uri查询不同的内容:1.3.2.1.4. 查询文件1.3.2.1.3. 权限
MediaStore通过不同Uri,为用户提供了增、删、改。
App对应的权限如下:
Audio | Image | Video | File | Downloads | |
WRITE_EXTERNAL_STORAGE | no-op | ||||
READ_EXTERNAL_STORAGE | 能读取所有App的多媒体文件 | 不能读取非多媒体文件 | |||
无 | 只能读取、修改自己新建的文件 |
1.3.2.1.4. 查询文件
通过ContentResolver,根据不同的Uri查询不同的内容:
1.3.2.1.5.读取文件
通过ContentResolver query接口,查找出来文件后如何读取,可以通过下面的方式:
● 通过ContentResolver openFileDescriptor接口,选择对应的打开方式
例如”r”表示读,”w”表示写,返回ParcelFileDescriptor类型FD。
● 访问Thumbnail,通过ContentResolver loadThumbnail接口
通过传递大小,MediaProvider返回指定大小的Thumbnail。
● Native代码访问文件
如果Native代码需要访问文件,可以参考下面方式:
■ 通过openFileDescriptor返回ParcelFileDescriptor
■ 通过ParcelFileDescriptor.detachFd()读取FD
■ 将FD传递给Native层代码
■ App需要负责通过close接口关闭FD
1.3.2.1.6.新建文件
如果需要新建文件存放到公共目录,需要通过ContentResolver insert接口,使用不同的Uri,选择存储到不同的目录。
1.3.2.1.7.修改文件
如果需要修改多媒体文件,需要通过ContentResolver query接口查找出来对应文件的Uri。
如果不是自己新建的文件,需要注意1.3.2.1.3. 权限中描述,需要catch RecoverableSecurityException,弹框给用户选择。
通过下列接口,获取需要修改文件的FD或者Output