【CSDN 编者按】最近看了 Google 的 Android 11 API 变更。同时记录下来,方便日后快速适配,也供需要的人参考。
作者 | 唯鹿 责编 | 欧阳姝黎
出品 | CSDN博客
终于开始了Android 11的适配工作。记录一下,供需要的人参考。
准备工作
老规矩,首先将我们项目中的 targetSdkVersion 改为 30。或者使用兼容性调试工具,后面我会说到。
存储机制更新
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权限,将用户引导至系统设置页面开启。代码如下:
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 11 开始,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
单次权限授权的应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:
当应用的 Activity 可见时,应用可以访问相关数据。
如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
如果您在 Activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。
如果用户撤消单次授权(例如在系统设置中撤消),无论您是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。
当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。
如果你之前就是使用权限时才请求相关权限,那么这一变更对于你的应用没有影响。
请求位置权限
这部分在 Android 10 的适配有过调整,当时规则如下:
请求 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限表示在前台时拥有访问设备位置信息的权限。在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。
在Android 11中,请求弹框中取消了“始终允许”这一选项。也就是说默认不会授予你后台访问设备位置信息的权限。如果尝试请求 ACCESS_BACKGROUND_LOCATION 权限的同时请求任何其他权限,系统会抛出异常,不会向应用授予其中的任一权限。
官方给出的适配建议及原因如下:
建议应用对位置权限执行递增请求,先请求前台位置信息访问权限,再请求后台位置信息访问权限。执行递增请求可以为用户提供更大的控制权和透明度,因为他们可以更好地了解应用中的哪些功能需要后台位置信息访问权限。
总结一下得出两点:
先请求前台位置信息访问权限,再请求后台位置信息访问权限。
单独请求后台位置信息访问权限,不要与其他权限一同请求。
这里还需要注意不同目标平台应用在 Android 11上的表现:
Android 10 为目标平台的应用
允许同时访问前后台的位置信息权限,但同样不会有“始终允许”这一选项。
没有前后台的位置信息权限时:
有前台的位置信息权限时:
Android 11 为目标平台的应用
没有前后台的位置信息权限时,只能先请求前台的位置信息权限:
有前台的位置信息权限,请求后台的位置信息时系统会跳转到下面的设置页面。
选择“始终允许”表示具有前后台位置信息访问权限,如果用户拒绝两次应用定位访问请求(直接返回等),后面请求相同权限都会被直接提示请求失败。(这里就需要我们给用户以引导了)
这里解释一下“拒绝两次”,这是 Android 11 上添加的权限对话框的可见性,以前我们点击了“不再询问”表示拒绝授权。现在还包含类似上面这种转到系统设置,然后点返回按钮,也算是拒绝授权。当然,用户按返回按钮关闭权限对话框,此操作不算。
总结一下,与 Android 10 的区别就是将后台权限的申请分离了出来,增加了用户“拒绝”的条件,避免了应用重复请求用户已拒绝的权限。
软件包可见性
软件包可见性是 Android 11 上提升系统隐私安全性的一个新特性。它的作用是限制app随意获取其他app的信息和安装状态。避免病毒软件、间谍软件利用,引发网络钓鱼、用户安装信息泄露等安全事件。
获取自动可见应用的列表,可以执行命令 adb shell dumpsys package queries,找到 forceQueryable 部分。下面是在 vivo iqoo 手机的执行结果。
Queries:system apps queryable: falseforceQueryable:[com.android.BBKCrontab,com.vivo.fingerprint,com.vivo.epm,com.vivo.abe,com.vivo.fingerprintengineer,com.vivo.contentcatcher,com.vivo.floatingball,com.vivo.agent,com.vivo.nightpearl,android,com.wapi.wapicertmanage,com.vivo.vms,com.android.providers.settings,com.vivo.upslide,com.vivo.assistant,com.vivo.vivokaraoke,com.vivo.fingerprintui,com.android.wallpaperbackup,com.bbk.facewake,com.vivo.faceunlock,com.vivo.doubleinstance,com.vivo.audiofx,com.iqoo.powersaving,com.bbk.SuperPowerSave,com.vivo.vibrator4d,com.vivo.smartunlock,com.vivo.globalanimation,com.vivo.appfilter,com.vivo.voicewakeup,com.vivo.minscreen,com.android.bbklog,com.mobile.cos.iroaming,com.vivo.networkstate,com.vivo.daemonService,com.vivo.smartshot,com.vivo.vtouch,com.android.networkstack.tethering.inprocess,com.android.localtransport,com.vivo.pem,com.vivo.wifiengineermode,com.android.server.telecom,com.vivo.gamecube,com.vivo.aiengine,com.vivo.multinlp,com.vivo.smartmultiwindow,com.vivo.permissionmanager,com.qti.diagservices,com.vivo.bsptest,com.qti.snapdragon.qdcm_ff,com.vivo.dr,com.vivo.sps,com.android.dynsystem,com.vivo.setupwizard,com.vivo.gamewatch,com.android.keychain,com.vivo.faceui,com.android.networkstack.inprocess,com.android.location.fused,com.android.inputdevices,com.android.settings,com.iqoo.engineermode,com.vivo.fuelsummary][com.qualcomm.uimremoteserver,com.vivo.devicereg,com.qti.qualcomm.deviceinfo,com.volte.config,com.android.mms.service,com.android.ons,com.qualcomm.qcrilmsgtunnel,com.vivo.sim.contacts,com.qualcomm.qti.uimGbaApp,com.qualcomm.qti.modemtestmode,com.android.stk,com.android.vendors.bridge.softsim,com.qualcomm.uimremoteclient,com.qti.qualcomm.datastatusnotification,com.qualcomm.qti.uim,com.android.phone,com.qualcomm.qti.dynamicddsservice,com.qualcomm.qti.telephonyservice,com.android.cellbroadcastservice,com.android.providers.telephony,com.qti.dpmserviceapp,com.android.incallui][com.android.vivo.tws.vivotws,com.android.bluetooth]com.android.nfccom.android.secom.android.networkstack.permissionconfigcom.android.shellcom.android.providers.media.modulecom.android.wifi.resources.overlay.commoncom.android.theme.icon_pack.filled.themepickercom.android.theme.icon_pack.circular.themepickercom.android.server.telecom.overlay.common......
可以看到都是系统应用包名,所以我们的三方应用默认是不可见的。此项变更影响比较多的是分享支付一类需要与其他应用交互的功能。下面举一个简单的例子:
private static boolean hasActivity(Context context, Intent intent) {PackageManager packageManager = context.getPackageManager();return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;}public void test() {Intent intent = new Intent();intent.setClassName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI");Log.d("hasActivity:", hasActivity(this, intent) + "");}
hasActivity 方法中通过 queryIntentActivities 来判断此页面是否存在。但是在 targetSdkVersion >= 30 中,这些三方默认都是不可见的。所以都会返回 false。类似方法 getInstalledPackages、getPackageInfo 也受到相应的限制。
解决方法很简单,在 AndroidManifest.xml 中添加queries元素,里面添加需要可见的应用包名。
"com.example.app"><- 指定微信包名queries>...manifest>
我在适配中用到的还有下面的包名,我们可以按需添加:
queries>
除了直接添加包名的方式外,我们可以按 intent 和 provider 来添加:
intent>queries>...manifest>
具体的规则参见:管理软件包可见性
当然,还有一种简单粗暴的方式,可以直接申请权限 QUERY_ALL_PACKAGES。如果你的应用需要上架 Google Play,那么可能要注意相关政策。为了尊重用户隐私,建议我们的应用按正常工作所需的最小软件包可见性来适配。
有一点需要说明一下,我们日常使用的 startActivity 方法不受系统软件包可见性行为的影响,即使 hasActivity 为 false,一样可以跳转。如果我们在做跳转前,进行类似 hasActivity 的判断,那么会受影响。
最后需要注意的是,使用 queries 元素需要 Android Gradle 插件版本是4.1及以上,因为旧版本的插件并不兼容此元素,出现合并 manifest 的错误。
前台服务类型
Android 10 中,在前台服务访问位置信息,需要在对应的 service 中添加 location 服务类型。
同样的,Android 11 中,在前台服务访问摄像头或麦克风,需要在对应的 service 中添加 camera 或 microphone 服务类型。
...android:name="MyService"android:foregroundServiceType="microphone|camera" />manifest>
这一限制的变更,使得程序无法在后台启动服务访问摄像头和麦克风。如需使用,只能是前台开启前台服务。除非有如下情况:
服务由系统组件启动。
服务是通过应用小部件启动。
服务是通过与通知交互启动的。
服务是 PendingIntent 启动的,它是从另一个可见的应用程序发送过来的。
服务由一个应用程序启动,该应用是一个 DPC,且在设备所有者模式下运行。
服务由一个提供 VoiceInteractionService 的应用启动。
服务由一个具有 START_ACTIVITIES_FROM_BACKGROUND 权限的应用启动。
权限自动重置
如果应用以 Android 11 或更高版本为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。如下图所示:
注意上图中有一个启动自动重置的开关。如果我们的应用有特殊需要,可以引导用户关闭它。示例代码如下:
public void checkAutoRevokePermission(Context context) {// 判断是否开启if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&!context.getPackageManager().isAutoRevokeWhitelisted()) {// 跳转设置页Intent intent = new Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setData(Uri.fromParts("package", context.getPackageName(), null));context.startActivity(intent);}}
SYSTEM_ALERT_WINDOW 权限
这部分我在适配中没有用到,直接照搬文档:
在 Android 11 中,系统会根据请求自动向某些类型的应用授予 SYSTEM_ALERT_WINDOW 权限:
系统会自动向具有 ROLE_CALL_SCREENING 且请求 SYSTEM_ALERT_WINDOW 的所有应用授予该权限。如果应用失去 ROLE_CALL_SCREENING,就会失去该权限。
系统会自动向通过 MediaProjection 截取屏幕且请求 SYSTEM_ALERT_WINDOW 的所有应用授予该权限,除非用户已明确拒绝向应用授予该权限。当应用停止截取屏幕时,就会失去该权限。此用例主要用于游戏直播应用。
这些应用无需发送 ACTION_MANAGE_OVERLAY_PERMISSION 以获取 SYSTEM_ALERT_WINDOW 权限,它们只需直接请求 SYSTEM_ALERT_WINDOW 即可。
MANAGE_OVERLAY_PERMISSION intent 始终会将用户转至系统权限屏幕
从 Android 11 开始,ACTION_MANAGE_OVERLAY_PERMISSION intent 始终会将用户转至顶级设置屏幕,用户可在其中授予或撤消应用的 SYSTEM_ALERT_WINDOW 权限。intent 中的任何 package: 数据都会被忽略。
在更低版本的 Android 中,ACTION_MANAGE_OVERLAY_PERMISSION intent 可以指定一个软件包,它会将用户转至应用专用屏幕以管理权限。从 Android 11 开始将不再支持此功能,而是必须由用户先选择要授予或撤消哪些应用的权限。此变更可以让权限的授予更有目的性,从而达到保护用户的目的。
读取手机号
如果你是通过TelecomManager的getLine1Number方法,或TelephonyManager的 getMsisdn 方法获取电话号码。那么在 Android 11 中需要增加 READ_PHONE_NUMBERS 权限。使用其他方法不受限。
android:maxSdkVersion="29" />manifest>
其他行为变更
自定义 view 的 Toast
Android 11 为目标平台的应用,从后台发送自定义view的Toast消息系统会进行屏蔽。前台使用不受影响。Toast相应的setView 和 getView也已经废弃不建议使用。
如果要在后台使用,推荐使用默认的toast或Snackbar替代。
APK签名
Android 11 为目标平台的应用,仅通过 v1 签名的应用无法在 Android 11 的设备上安装或更新。必须使用 v2 或更高版本进行签名。
同时Android 11 添加了对 APK 签名方案 v4 的支持。
AsyncTask
AsyncTask 在 Android 11 已经不建议使用,建议迁移至 kotlin 的协程。
此外 Handler 未指定 Looper 的构造方法也已不建议使用。
建议明确指定 Looper:
private Handler handler = new Handler(Looper.myLooper());// 或private Handler handler = new Handler(Looper.getMainLooper());
新增工具
兼容性调试工具
以往我们做适配的时候,需要先将我们项目中的 targetSdkVersion 修改为对应版本。这就导致你适配过程中有可能受到其他变更的影响,而这个新增的兼容性调试工具可以让你在不升级 targetSdkVersion 的情况下,针对每项变更逐个开启适配。
使用方法:
开发者选项中找到应用兼容性变更选项。
点击进入找到你需要调试的应用
在变更列表中,找到想要开启或关闭的变更,然后点击相应的开关。
上面第一行DEFAULT_SCOPED_STORAGE就是启用分区储存,这些常量详细的含义见:Android 11 变更列表。
对于兼容性调试工具详细的使用方法见:兼容性框架工具,这里限于篇幅就不展开说了。
无线调试
Android 11 的开发者选项中添加了一个无线调试的功能。类似于连接蓝牙耳机功能,可以无需 USB 连接线进行日常开发调试工作。(区别于以前的 Android WIFI ADB,这个是真无线,哈哈)
使用方法:
开发者选项中找到无线调试并打开。
首次配对需点击“使用配对码配对设备”
运行 adb pair ipaddr:port 后输入配对码进行连接。
注意事项:
保持电脑和手机在一个网络。
Platform Tools 版本需大于 30.0。可使用 adb --version 查看。
不过我自己体验下来,感觉连接不是很稳定,不知是 AS 的问题还是手机问题。同时锁屏后也会断开连接,体验不是很好。。。期待后续的优化吧。
本篇内容有点多。总结一下,Android 11 在权限上的变更比较多,但如果你一直遵守申请权限相关的最佳做法,那么基本上不需要额外的适配工作。
最后强调一下,对于单次授权,权限对话框的可见性,SYSTEM_ALERT_WINDOW 权限,安装apk这些变更只要在Android 11上就会生效,不论你是否适配 Android 11。对于其他变更和 API(相机、5G、瀑布屏、键盘等),因为我暂时没有遇到,也就没有列出,有需要的可以点击文末的官方文档链接查看。
截止发这篇博客时,我手机上只发现哔哩哔哩已经适配了 Android 11。大多数停留在 28、29,更有甚者还在 26(Android 8.0 国内上架的最低适配标准)。
所以我顺便附上之前写的 Android 9、10 的适配攻略:
Android 9.0 适配指南:https://blog.csdn.net/qq_17766199/article/details/98336225?spm=1001.2014.3001.5501
Android 10 适配攻略:https://blog.csdn.net/qq_17766199/article/details/104513170?spm=1001.2014.3001.5501
CSDN 问答上线《冲榜分奖金》活动!每周采纳榜前五名的答主可获得现金和会员卡,剩余用户会随机抽取送出幸运礼物!后台回复关键词“冲榜”可获取当前排名和活动规则
☞“UNIX的名字是我起的”——对话UNIX开发者Brian W. Kernighan ☞因财务造假,贾跃亭被罚 2.41 亿元;华为徐直军重申不造车:但将推子品牌汽车;Gradle 7.0 发布|极客头条 ☞“炸毁 70% 的互联网!”德州一男子欲袭击亚马逊数据中心被捕
热门跟贴