这几个月有点忙,一年一篇的适配文章来的有点晚了。但其实也还好,因为我们项目也是下半年才适配。我这边也是提前调研踩坑,评估一下工作量。这个时间点也完全跟得上Google Play的审核要求(11月1号),对于国内这边更是超前了。。。废话不多说,开始了。
首先先说一些影响在Android 12 上运行应用的行为变更。这部分会直接影响在Android 12上运行的所有应用,所以即使你不适配,也需要重点关注。
应用启动画面
从 Android 12 开始,SplashScreen API 可为所有Android 12或更高版本的设备上运行的应用启用新的应用启动动画。这包括启动时的进入应用动画、显示应用图标的启动画面,以及向应用本身的过渡。
先看一下启动画面示例:
启动画面的元素由Android清单中的XML资源文件定义。每个元素都有浅色模式和深色模式版本。
启动画面的可自定义元素包括应用图标、图标背景和窗口背景:
①应用图标应该是矢量可绘制对象(AVD XML),它可以是静态或动画形式。虽然动画的时长可以不受限制,但我们建议不超过1,000 毫秒。默认情况下,使用启动器图标。
②可以选择添加图标背景;在图标与窗口背景之间需要更高的对比度时图标背景很有用。如果您使用一个自适应图标,当该图标与窗口背景之间的对比度足够高时,就会显示其背景。
③与自适应图标一样,前景的三分之一被遮盖。
④窗口背景由不透明的单色组成。如果窗口背景已设置且为纯色,则未设置相应的属性时默认使用该背景。
其实这个实际上可以不适配。。。比如我的vivo手机其实在开发者模式可以开关这一选项,而且默认是关闭的。对于使用非原生系统的用户来说,算是个感知不到的变化。但最终适配与否还是看产品是否可以接受了。
不过需要注意:默认情况下,SplashScreen 使用主题的windowBackground (如果它是单色)和启动器图标。 所以windowBackground 的颜色和logo的颜色如果对比度比较低,实际显示效果会比较糟糕,我觉得这块可以适当处理一下。
这里就不花费篇幅来说明了,有适配需求的可以参考:将现有的启动画面实现迁移到 Android 12 及更高版本
拉伸滚动效果
在搭载 Android 12 及更高版本的设备上,滚动事件的视觉行为发生了变化。
在 Android 11 及更低版本中,滑动到边界是会出现弧形的阴影遮罩。在 Android 12 及更高版本中,发生拖动事件时,视觉元素会拉伸和反弹;发生快速滑动事件时,它们会快速滑动和反弹。
定位权限:大概位置
如果您的应用请求ACCESS_COARSE_LOCATION 但未请求ACCESS_FINE_LOCATION ,则此变更不会影响您的应用。 如果您的应用请求ACCESS_FINE_LOCATION 运行时权限,您还应请求ACCESS_COARSE_LOCATION 权限,以便处理用户授予应用大致位置访问权限的情形。
可能是出于兼容的目的。实际我在我的vivo手机测试发现,只请求ACCESS_FINE_LOCATION 权限,也可以正常弹出,并选择位置精度。但是保险起见,实际适配时还是按照官方文档的要求来。
如果用户首次选择了大致位置,二次请求权限时会提示用户是否更改为精确位置。这点可以注意一下。
Activity生命周期
Android 12 更改了在按下“返回”按钮时系统对为其任务根部的启动器activity的默认处理方式。在以前的版本中,系统会在按下“返回”按钮时finish 这些 activity。在 Android 12 中,现在系统会将 activity 及其任务移到后台,而不是finish activity。这一新行为与使用HOME按钮行为一致。
注意:系统仅会将新行为应用于为其任务根部的启动器 activity,即使用 ACTION_MAIN 和 CATEGORY_LAUNCHER 声明 intent 过滤器的 activity。对于其他 activity,在按下“返回”按钮时,系统会像以前一样finish activity。
对于大多数应用而言,此变更意味着使用“返回”按钮退出应用的用户可以更快地从温状态恢复应用,而不必从冷状态完全重启应用。
建议您针对此变更测试您的应用。如果您的应用目前重写 onBackPressed() 来处理返回导航并finish Activity,请更新您的实现来调用 super.onBackPressed() 而不是finish 。调用 super.onBackPressed() 可在适当时将 activity 及其任务移至后台,并可为不同应用中的用户提供更一致的导航体验。
另请注意,通常,我们建议您使用 AndroidX Activity API 提供的自定义返回导航,而不是替换 onBackPressed() 。如果没有组件拦截系统按下“返回”按钮,AndroidX Activity API 会自动遵循适当的系统行为。
还有许多的行为变更,以上我只选了几条重要的。其他变更可以参见:Android 12行为变更:所有应用
接下来是以Android 12为目标平台的应用行为变更。首先就需要我们将targetSdkVersion 升至31。
自定义通知
Android 12 更改了自定义通知的外观和行为。以前,自定义通知能够使用整个通知区域并定义自己的布局和样式。因此会在不同设备上引发布局兼容性问题,使用户感到困惑。
对于以 Android 12 为目标平台的应用,自定义布局的通知将不再使用完整通知区域;相反,系统会使用标准模板。此模板可确保自定义通知在所有状态下都与其他通知相同,例如,在收起状态下的通知图标和展开功能,以及在展开状态下的通知图标、应用名称和收起功能。此行为与 Notification.DecoratedCustomViewStyle 的行为几乎完全相同。 下面展示了在收起状态和展开状态下呈现的自定义通知:
此变更会影响某些定义了 Notification.Style 子类的应用,或使用 Notification.Builder 中的 setCustomContentView(RemoteViews) 、setCustomBigContentView(RemoteViews) 、setContent(RemoteViews) 和 setCustomHeadsUpContentView(RemoteViews) 方法的应用。
具体适配时,需要注意以下几点:
- 自定义视图的尺寸有变更。一般来说,提供给自定义通知的高度比之前小。在收起状态下,自定义内容的最大高度已从 106dp 减少到 48dp。此外,水平空间也减小了。
- 对于以 Android 12 为目标平台的应用,所有通知都是可展开的。通常,这意味着,如果您使用的是
setCustomContentView ,则还需要使用 setBigCustomContentView ,以确保收起状态和展开状态保持一致。 - 为了确保“浮动通知”(Heads Up)状态看起来符合您的预期,请勿忘记将通知渠道的重要性(setPriority)提升至“高”(在屏幕中弹出)。
更安全的组件导出
如果您的应用以 Android 12 或更高版本为目标平台,且包含使用 intent 过滤器的 activity、服务或广播接收器,您必须为这些应用组件显式声明 android:exported 属性。否则应用无法安装。
所以我们可以在AndroidManifest.xml 搜索<intent-filter> ,为这些组件显式声明 android:exported 属性。
android:exported 属性表示是否能被其他应用隐式调用。所以如果应用组件包含 LAUNCHER 类别,请将 android:exported 设置为 true 。在大多数其他情况下,请将 android:exported 设置为 false 。例如:
<service android:name="com.example.app.backgroundService"
android:exported="false">
<intent-filter>
<action android:name="com.example.app.START_BACKGROUND" />
</intent-filter>
</service>
对于三方sdk,具体根据相应文档要求配置。如果sdk没有此适配,可以参考这篇文章:Android 12 自动适配 exported 深入解析避坑
PendingIntent可变性
如果您的应用程序以Android 12为目标平台,您必须为应用创建的每个 PendingIntent 对象指定可变性。这项额外的要求可提高应用的安全性。因为三方app可以通过劫持PendingIntent ,然后改写里面的action、category、data等,造成重定向攻击。
所以适配的具体就是在创建 PendingIntent 时,使用 PendingIntent.FLAG_MUTABLE 或 PendingIntent.FLAG_IMMUTABLE 标志。否则运行时会报IllegalArgumentException :
java.lang.IllegalArgumentException: com.dailyyoga.inc: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
我们可以检查代码中使用PendingIntent 的地方,比如PendingIntent.getActivity() ,PendingIntent.getService() , PendingIntent.getBroadcast() 方法。指定可变性标志:
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
一般情况下,尽可能使用FLAG_IMMUTABLE 创建不可变的 ?PendingIntent? 对象。如果需要创建可变的?PendingIntent? ,那么一定注意要使用显式 intent 并设置?ComponentName?。
我们项目中因为使用到了androidx.work 、google-exo 库,它内部在创建 PendingIntent 对象时没有指定可变性,导致有兼容性问题,所以需要升级版本至2.7.0 和2.16.0 以上。
应用休眠
Android 11中引入了权限自动重置机制。如果您的应用以 Android 12 为目标平台,并且用户有几个月未与您的应用互动,则系统会自动重置授予的所有权限并将您的应用置于休眠状态。
休眠状态的应用程序具有以下特征:
- 应用无法从后台运行作业(jobs)或警报(alerts)。
- 应用无法接收推送通知,包括通过Firebase Cloud Messaging发送的高优先级消息。
- 应用程序的缓存文件都将被删除。
当用户下一次与您的应用程序交互时(包括与小组件),您的应用程序将退出休眠,它可以再次创建作业、提醒和通知。
但是,系统不会为您的应用程序执行以下操作:
- 重新授予你的应用的运行时权限。用户必须重新授予您的应用程序这些权限。
- 不会恢复作业、提醒和通知。你需要重新计划在应用程序进入休眠状态之前计划的任何作业、警报和通知。
此行为类似于用户手动从系统设置中强制停止您的应用程序。要更轻松地支持此工作流程,请使用WorkManager 。您还可以在ACTION_BOOT_COMPLETED广播接收器中添加重新计划逻辑,当您的应用退出休眠状态并在设备启动后被调用。
对于系统级应用,会免于休眠。如果您的应用程序中的核心用例会受到休眠的影响,您可以向用户请求免除应用程序休眠。具体详见官方文档,这里就不过多介绍了。
前台服务启动限制
针对Android 12或更高版本的应用在后台运行时无法启动前台服务,除了几个特殊情况外。如果一个应用程序试图在后台运行时启动前台服务,而前台服务不满足其中一种异常情况,系统将引发ForegroundServiceStartNotAllowedException .
你可以执行以下ADB命令,检查您的应用程序是否执行后台启动:
adb shell device_config put activity_manager \
default_fgs_starts_restriction_notification_enabled true
一旦发现后台运行时启动前台服务,就会在通知栏推送一条提醒。
谷歌建议的适配方法就是使用WorkManager ,应用在后台运行时,使用 WorkManager 来计划和启动加急任务(expedited job) 。从WorkManager 2.7.0开始,可以调用setExpedited() 来声明Worker使用加急任务。
通知 trampoline 限制
当用户与通知交互时,某些应用程序会通过启动应用程序组件来响应通知点击,该组件最终会启动用户最终看到并与之交互的Activity。 此应用程序组件称为通知中介。
也就是说,如果你点击了通知,启动了一个广播或者服务,然后在这个广播或服务中调用了startActivity() 启动activity。此时系统会阻止该 activity 启动,并在 Logcat 中显示以下消息:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \
this should be avoided for performance reasons.
我们可以执行下面的命令识别哪些应用组件充当通知 trampoline:
adb shell dumpsys activity service \
com.android.systemui/.dump.SystemUIAuxiliaryDumpService
执行后,点击你的通知,如果是通知 trampoline。Log会输出的包含文本“NotifInteractionLog”的日志。此部分包含识别因点按通知而启动的组件所需的信息。
具体适配方法就是使用PendingIntent 直接启动目标Activity。
蓝牙权限
如果您的应用程序面向Android 12或更高版本,使用蓝牙功能时请在应用程序的清单文件中声明以下权限:
BLUETOOTH_SCAN :允许蓝牙设备扫描。BLUETOOTH_CONNECT :允许蓝牙设备连接。BLUETOOTH_ADVERTISE :允许当前蓝牙设备可以被其他蓝牙设备发现。
如果不申请新的权限,扫描时会报如下错误:
java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@8825e139: GattService registerScanner
对于以前的与蓝牙相关的权限声明,设置android:maxSdkVersion 到30。此应用兼容性步骤可帮助系统仅授予您的应用在运行Android 12 的设备上安装时所需的蓝牙权限。
<manifest>
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
如果您的应用程序使用蓝牙扫描结果来获取物理位置,请声明ACCESS_FINE_LOCATION 。以前版本中(6.0 ~ 11)是必须申请定位权限,才可以进行蓝牙扫描。
在Android 12上,如果你的应用程序不使用蓝牙扫描结果来获取物理位置。可以添加android:usesPermissionFlags 属性到您的BLUETOOTH_SCAN 权限声明,并将该属性的值设置为neverForLocation .
<manifest>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
其他
- 默认toast样式发生变化,文字上限为两行,且在文字旁边会显示应用图标。
- 为了保护私有应用数据,Android 12 变更了
adb backup 命令的默认行为。对于以 Android 12或更高版本为目标平台的应用,用户运行 adb backup 命令时,从设备导出的任何其他系统数据都不包含应用数据。 如果您的测试或开发工作流程依赖于使用 adb backup 的应用数据,现在您可以选择通过在应用的清单文件中将 android:debuggable 设置为 true 来导出应用数据。
有关Android 12的适配内容主要就这么多,更多的变更还是需要我们去阅读官方文档。链接我也贴到了文末。
关于Android的适配,我从Android 6.0写到了12,其中也包含早期4.4,5.0的个别特性适配。这些内容都在我的Android 适配专栏里,希望可以帮到你。
最后,期待你的收藏点赞,给作者一个支持。好让我继续坚持每年为大家带来我的实际适配心得,感谢!
参考
|