1. 准备工作
老规矩,首先将我们项目中的 targetSdkVersion 改为 30。或者使用兼容性调试工具,后面我会说到。
2. 存储机制更新
Scoped Storage(分区存储)
具体适配方法和去年的Android 10
适配攻略攻略中的没有太大区别。
不过需要注意的是,应用targetSdkVersion >= 30,强制执行分区存储机制。之前在AndroidManifest.xml中添加
android:requestLegacyExternalStorage="true"的适配方式已不起作用。
还有一个变化:Android 11 允许使用除 MediaStore API 之外的 API 通过文件路径直接访问共享存储空间中的媒体文件。其中包括:
- File API。
- 原生库,例如 fopen()。
如果你之前没有适配Android 10,这一点对你来说是个好消息。Android 10在AndroidManifest.xml中添加
android:requestLegacyExternalStorage="true"来适配,Android 11上直接使用File
API访问媒体文件。不得不说,等等党的胜利?
不过,使用原始文件路径直接访问共享存储空间中的媒体文件会重定向到 MediaStore
API,这次重定向会造成性能影响(随机读写慢一倍左右)。而且直接使用原始文件路径,并不会比使用 MediaStore API
有更多优势,因此官方强烈建议直接使用 MediaStore API。
MANAGE_EXTERNAL_STORAGE
当然还有一种简单粗暴的适配方法,获取外部存储管理权限。如果你的应用是手机管家、文件管理器这类需要访问大量文件的app,可以申请MANAGE_EXTERNAL_STORAGE权限,将用户引导至系统设置页面开启。代码如下:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
public static void checkStorageManagerPermission(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } }
需要注意的是即使你有了MANAGE_EXTERNAL_STORAGE权限,也无法访问Android/data/ 目录下的文件。
对于MANAGE_EXTERNAL_STORAGE权限,国内使用应该没有什么影响。但是在Google
Play上需要说明为什么已有的SAF或MediaStore不满足你的应用需求,审核通过才允许上架使用。所以一般情况下,我个人不推荐你为了适配简单,直接申请使用MANAGE_EXTERNAL_STORAGE权限。
其他细节变更见文档:Android 11
中的存储机制更新。
相关api变更及使用推荐郭霖大神的这篇:Android 11新特性,Scoped
Storage又有了新花样。
存储访问框架 (SAF)变更
Android 11对SAF添加以下限制:
- 使用 ACTION_OPEN_DOCUMENT_TREE 或 ACTION_OPEN_DOCUMENT,无法浏览到Android/data/ 和 Android/obb/ 目录及其所有子目录。
- 使用 ACTION_OPEN_DOCUMENT_TREE无法授权访问存储根目录、Download文件夹。
REQUEST_INSTALL_PACKAGES
在8.0的适配中,我们安装apk包之前需要申请“安装未知来源应用”的权限。一般来说首次是跳转到授权页面让用户手动开启,然后返回app进行安装。
在Android 11中当用户开启“安装未知来源应用”的权限,app就会被杀死。该行为与强制分区存储有关,因为持有
REQUEST_INSTALL_PACKAGES 权限的应用可以访问其他应用的Android/obb 目录。
好在用户授予权限之后,虽然app会被杀死,但是 安装页面依然会弹出 。
目前对于这一变更我没有发现可以适配处理的方式,详细介绍见:Android
11特性调整:安装外部来源应用需要重启APP
这里补充一下,因为其他应用无法访问应用的Android/data/ 和
Android/obb/目录及其所有子目录。所以需要注意保存在这里面的文件是否会被其他程序访问。
比如我在用系统的裁切功能时,因为设置的MediaStore.EXTRA_OUTPUT文件是私有目录下的,导致裁剪后的图片无法正确生成。所以需要针对android
11进行适配:
String fileName = System.currentTimeMillis() + ".jpg"; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // 裁剪无法访问App的私有目录,所以可以保存至公有目录 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Crop"); Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); } else { ... }
或者保存至Android/media共享文件目录,这样不用适配版本。
String fileName = System.currentTimeMillis() + ".jpg"; File file = new File(this.getExternalMediaDirs()[0].getAbsolutePath() + File.separator + fileName); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
当然如果你是自己实现的裁剪功能,那么不受影响。
3.权限变化
单次权限授权
从 Android 11
开始,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
单次权限授权的应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:
- 当应用的 Activity 可见时,应用可以访问相关数据。
- 如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
- 如果您在 Activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。
- 如果用户撤消单次授权(例如在系统设置中撤消),无论您是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。
当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。
如果你之前就是使用权限时才请求相关权限,那么这一变更对于你的应用没有影响。
请求位置权限
这部分在Android 10的适配有过调整,当时规则如下:
请求ACCESS_FINE_LOCATION或
ACCESS_COARSE_LOCATION权限表示在前台时拥有访问设备位置信息的权限。在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。
在Android 11中,请求弹框中取消了“始终允许”这一选项。也就是说 默认不会授予你后台访问设备位置信息的权限
。如果尝试请求ACCESS_BACKGROUND_LOCATION权限的同时请求任何其他权限,系统会抛出异常,不会向应用授予其中的任一权限。
官方给出的适配建议及原因如下:
建议应用对位置权限执行递增请求,先请求前台位置信息访问权限,再请求后台位置信息访问权限。执行递增请求可以为用户提供更大的控制权和透明度,因为他们可以更好地了解应用中的哪些功能需要后台位置信息访问权限。
总结一下得出两点:
- 先请求前台位置信息访问权限,再请求后台位置信息访问权限。
- 单独请求后台位置信息访问权限,不要与其他权限一同请求。
这里还需要注意不同目标平台应用在Android 11上的表现:
- Android 10 为目标平台的应用 允许同时访问前后台的位置信息权限,但同样不会有“始终允许”这一选项。
1.没有前后台的位置信息权限时:
2.有前台的位置信息权限时:
- Android 11 为目标平台的应用
1.没有前后台的位置信息权限时,只能先请求前台的位置信息权限:
2.有前台的位置信息权限,请求后台的位置信息时系统会跳转到下面的设置页面。
选择“始终允许”表示具有前后台位置信息访问权限,如果用户拒绝两次应用定位访问请求(直接返回等),后面请求相同权限都会被直接提示请求失败。(这里就需要我们给用户以引导了)
这里解释一下“拒绝两次”,这是Android 11
上添加的权限对话框的可见性,以前我们点击了“不再询问”表示拒绝授权。现在还包含类似上面这种转到系统设置,然后点返回按钮,也算是拒绝授权。当然,用户按返回按钮关闭权限对话框,此操作不算。
总结一下,与Android 10的区别就是将后台权限的申请分离了出来,增加了用户“拒绝”的条件,避免了应用重复请求用户已拒绝的权限。