Android 11适配攻略

Android 11适配攻略

首语

分享一个Github小技巧。不用下载任何软件,也不需要装任何的浏览器插件,你只用在Github的网址中,gitHub后面添加1s,回车就可以在Vscode界面访问项目代码了。来个例子。
原始地址:https://github.com/hujuny/CommunityLibrary
Vscode界面地址:https://github1s.com/hujuny/CommunityLibrary

接下来进入今天的主题👉 Android 11。
Android 12预览版从2021年2月开始启动,目前已经发布了Beta Release版本,Final Relase版本预计在今年年底发布。是时候适配一波Android11了,为后面项目适配铺平道路。

介绍

2020年6月11日,Google正式推送了Android 11 Beta版本,同年年9月9日正式发布。系统主要增强了聊天气泡,安全性和隐私性的保护,电源菜单,可以更好的支持瀑布屏,折叠屏,双屏和 Vulkan 扩展程序等。
首先我们项目中的 targetSdkVersion 改为30。开始适配Android 11。

隐私变更

强制执行分区存储机制

为了避免存储空间的读写权限被滥用,手机中存在着大量不明文件,且应用卸载后也没有删除掉。Android 10 中提出了分区存储(Scoped Storage)这一概念。通过添加外部存储访问机制来更好的管理文件。
外部存储使用getExternalStorageDirectory()方法来获取路径存储文件。外部存储访问机制将外部存储空间划分为三部分:

  • 特定于应用的目录。使用getExternalFilesDir()getExternalCacheDir()getExternalMediaDirs()方法访问。无需权限,且卸载应用时会自动删除(在AndroidManifest.xml文件中声明android:hasFragileUserData="true",用户可以选择是否保留)。
  • 媒体集合。照片、视频、音频这类媒体文件。使用MediaStore 访问,无法直接使用File。访问其他应用的媒体文件时需要READ_EXTERNAL_STORAGE权限。
  • 其它目录。使用存储访问框架SAF(Storage Access Framwork)。
    简单粗暴的适配办法就是在AndroidManifest.xml文件中添加如下代码来使用旧的存储模式。
 android:requestLegacyExternalStorage="true"

但是这个适配方法在Android 11中已经失效,必须按照存储机制来乖乖适配,不一样的是Android 11允许使用File来访问媒体集合,但是,使用原始文件路径直接访问还是会重定向到MediaStore API,而且会造成性能影响,因为推荐直接使用MediaStore API
在 Android 11 上,应用无法再访问外部存储中任何其他应用的专用应用特定目录中的文件。
其它细节详见以下文档:
Google介绍Scoped Storage的中字视频:https://www.bilibili.com/video/av77198618
Android 11 中的存储更新:https://developer.android.google.cn/about/versions/11/privacy/storage#manage-device-storage

单次授权

从 Android 11 开始,每当应用请求与位置信息、麦克风摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
单次授权

重置权限

如果用户在 Android 11 或更高版本上几个月未与应用互动,系统会自动重置应用的敏感权限。

位置权限

Android 10请求ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION权限表示在前台时拥有访问设备位置信息的权限。在请求弹框还能看到始终允许,Android 11中,取消了始终允许选项,默认不会授予后台访问设备位置信息的权限。
Android 11将后台获取设备位置信息抽离了出来,通过ACCESS_BACKGROUND_LOCATION权限后台访问设备位置信息的权限,需要注意的一点是,请求ACCESS_BACKGROUND_LOCATION的同时不能请求其它权限,否则系统会抛出异常(在没研究Android 11适配的时候,我就已经发现了这个bug,当时以为是系统的bug,还截了两张图,后面仔细查看,找资料才发现是Android 11的变更)。
后台位置权限
怎么处理上述问题呢,官方给出的建议是先请求前台位置信息访问权限,再请求后台位置信息访问权限;单独请求后台位置信息访问权限。

软件包可见性

为了最大限度的减少应用获取其它应用的信息、数据,避免数据泄露、病毒软件等不安全隐患,Google将已安装的应用列表视为个人和敏感用户数据,因此Android 11提出了一个新的特性👉软件包可见性。
软件包可见性会影响提供其他应用相关信息的方法的返回结果,如queryIntentActivities()getPackageInfo()getInstalledApplications()
需要注意的一点是startActivity 方法不受系统软件包可见性行为的影响,queryIntentActivities()查询为false,一样也可以跳转。
如何适配呢?在之前接入微信支付的时候,文档就有提示Android 11 第三方应用无法拉起应用适配,适配方案为在主工程的AndroidManifest.xml 中增加 <queries> 标签。对于其它应用一样,添加包名即可。

<manifest package="com.example.app"> 
  	...
  	// 在应用的AndroidManifest.xml添加如下<queries>标签
    <queries>
        <package android:name="com.tencent.mm" />   // 指定微信包名
        <package android:name="com.eg.android.AlipayGphone" />
    </queries>
  	...
</manifest>

除了直接添加包名的方式外,也可以按intent和provider来添加。

<manifest package="com.yhj.hualin">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>

        <provider android:authorities="com.yhj.settings.files" />
    </queries>
    ...
</manifest>

在极少数情况下,如果遇到 <queries> 元素无法提供适当的软件包可见性,您还可以使用 QUERY_ALL_PACKAGES 权限。
使用queries元素需要Android Gradle 插件版本是 4.1及以上,因为旧版本的插件并不兼容此元素,出现"Element queries is not allowed here" 的警告,也可能会导致manifest合并失败。

前台服务

Android 10中,在前台服务访问位置信息,需要在对应的service中添加 location 服务类型。
Android 11中,在前台服务访问摄像头或麦克风,需要在对应的service中添加camera或microphone 服务类型。

<manifest>
    ...
    <service ...
        android:foregroundServiceType="location|camera|microphone" />
</manifest>

当应用程序在后台运行启动前台服务时,前台服务有如下限制:

  • 除非用户已授予ACCESS_BACKGROUND_LOCATION权限,否则 前台服务无法访问位置。
  • 前台服务无法访问麦克风或摄像头。

除非在以下情况可以访问。

  • 该服务由系统组件启动。
  • 该服务通过与应用小部件交互启动。
  • 该服务通过与通知交互来启动。
  • 该服务作为PendingIntent
  • 从不同的可见应用程序发送的启动 。
  • 该服务由在设备所有者模式下运行的设备策略控制器(DPC)应用程序启动。
  • 该服务由提供VoiceInteractionService
  • 该服务由具有START_ACTIVITIES_FROM_BACKGROUND特权权限的应用程序启动 。

SYSTEM_ALERT_WINDOW权限

在 Android 11 中,向应用授予 SYSTEM_ALERT_WINDOW 权限的方式发生了一些变更。这些变更可以让权限的授予更有目的性,从而达到保护用户的目的。
根据请求自动向某些应用授予 SYSTEM_ALERT_WINDOW 权限。
系统会根据请求自动向某些类型的应用授予 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 开始将不再支持此功能,而是必须由用户先选择要授予或撤消哪些应用的权限。此变更可以让权限的授予更有目的性,从而达到保护用户的目的。

REQUEST_INSTALL_PACKAGES权限

在Android 11中当用户开启“安装未知来源应用”的权限,app就会被杀死。该行为与强制分区存储有关,因为持有 REQUEST_INSTALL_PACKAGES 权限的应用可以访问其他应用的Android/obb 目录。
好在用户授予权限之后,虽然app会被杀死,但是安装页面依然会弹出。

电话号码

应用在读取电话号码时,使用 READ_PHONE_STATE 权限。在Android 11中,通过以下电话号码API,必须请求 READ_PHONE_NUMBERS 权限,不再是 READ_PHONE_STATE 权限。

  • TelephonyManager 类和 TelecomManager 类中的 getLine1Number() 方法。
  • TelephonyManager 类中不受支持的 getMsisdn() 方法。
<manifest>
    <!-- 仅在运行 Android 10(API 级别 29)及更低版本的设备上授予 READ_PHONE_STATE 权限 -->
    <uses-permission android:name="READ_PHONE_STATE"
                     android:maxSdkVersion="29" />
    <uses-permission android:name="READ_PHONE_NUMBERS" />
</manifest>

永久SIM标识符

从Android 11开始,通过 getIccId() 方法访问不可重置的ICCID受到限制,该方法会返回一个非空的空字符串,要唯一标识设备上安装的SIM,改用 getSubscriptionId() 方法。除非设备恢复出厂设置,否则此标识符对于SIM是不变的。

Toast

从Android 11开始 ,从后台发送自定义view的Toast消息系统会进行屏蔽。前台使用不受影响。Toast相应的setViewgetView方法也已经废弃不建议使用。
如果要在后台使用,并且希望用户执行某项操作,请使用通知(Notification)。

新功能和API

状态栏

WindowMetrics 是Android 11新增的类,用于获取窗口边界,同样可以用来获取导航栏高度。

 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {

            val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager
            val windowMetrics=  wm.currentWindowMetrics
            val wi=windowMetrics.windowInsets
            val insets=wi.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() or WindowInsets.Type.displayCutout())
            Log.e("yhj", "${insets.top}") 
        }

应用签名

从Android 11开始,仅通过v1签名的应用无法在Android 11的设备上安装或更新,必须使用v2或更高版本进行签名。
Android 11 添加了对 APK 签名方案 v4 的支持。此方案会在单独的文件 (apk-name.apk.idsig) 中生成一种新的签名,但在其他方面与 v2 和 v3 类似。没有对 APK 进行任何更改。此方案支持 ADB 增量 APK 安装,这样会加快 APK 安装速度。

无线调试

Android 11开发者选项增加了一个无线调试功能,可以无需USB连接线连接真机进行日常开发调试工作。类似于使用的插件Android ADB WIFI

  • 使用方法

打开手机开发者选项,找到无线调试打开。选择使用配对码配对设备,记下设备上显示的配对码、IP 地址和端口号。Android Studio terminal 运行 adb pair ipaddr:port 。使用第 5 步中的 IP 地址和端口号,最后输入配对码连接。
需要注意的是,电脑和手机必须在同一网络。Platform Tools 版本需大于30.0。可使用 adb --version 查看。
我用小米手机打开无线调试直接提示”由于某个应用遮挡了权限请求界面,因此设置应用无法验证您的回应“,目前还没有解决,有知道解决的大佬留言哈。
无线调试

兼容性调试工具

以往我们做适配时,需要先将项目中的 targetSdkVersion 改为对应版本,这就导致适配过程中可能受到其它变更的影响,而新增的兼容性调试工具科技让我们在不升级targetSdkVersion的情况下,开启适配。

  • 使用方法

打开手机开发者选项,找到应用兼容性变更选项,点击进入找到需要调试的应用,在变更列表中,找到想要开启或关闭的变更。
兼容性变更

这些常量的详细含义参考官方文档:https://developer.android.google.cn/about/versions/11/reference/compat-framework-changes

相机

Android 11 添加了 API 以查询对同时使用多个摄像头(包括前置摄像头和后置摄像头)的支持。
如需在运行应用的设备上检查支持情况,请使用以下方法:

  • getConcurrentCameraIds()可返回摄像头 ID 组合 Set,这些组合可与有保证的数据流组合并发进行流式传输(如果它们是由同一应用进程配置的)。
  • isConcurrentSessionConfigurationSupported()可查询摄像头设备是否可以并发支持相应的会话配置。

AsyncTask

AsyncTask在Android 11已经不建议使用,建议迁移至Kotlin的协程。
此外 Handler未指定 Looper 的构造方法也已不建议使用。

Handler(Looper.getMainLooper())

总结

Android 11的变更还是比较多的,除了上述变更外,还有5G瀑布屏网络连接等其它变更,未列出的变更请参考官方文档:https://developer.android.google.cn/about/versions/11/
最后,展示一个Android 11小动画🔊。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/android11

Buy me a cup of coffee ☕.