1、背景

最近对我的运用做了一些小的改动,准备上架 Google Play. 本以为小事一桩,没想到要做十分多的改动。其间一个改动是谷歌要求 billing 库晋级到 4.0 及以上版别。此外,另一个比较大的改动是要求晋级方针版别到 31.

升级 Android 目标版本到 31(S) 居然这么多坑

我之前从 29 晋级到 30 那次改动现已十分大了。这次应该不会太多改动,没想到总归还是 too young too simple, sometimes naive. 晋级方针版别到 31 也不是那么简单。下面是谷歌官方供给的两个文章,别离具体列举了晋级到版别的变更以及晋级到 Android 12 的具体的变更:

符合 Google Play 的方针 API 等级要求

行为变更:以 Android 12 为方针平台的运用

谷歌官方现已给了具体的说明,这儿我共享我在适配过程中遇到的问题和处理思路。

2、exported 特点

本次适配需求做的最显着的一个变更是修正 exported 特点。这个特点是之前就存在。我之前只在单个几个 Service 特点中运用了它。在 31 上开始要求开发者清晰指定组件的 exported 特点。

关于没有声明 exported 特点的运用,在发动的过程中就会抛出如下反常,

升级 Android 目标版本到 31(S) 居然这么多坑

关于 exported 特点,你能够检查谷歌官方文档的具体解释:android:exported

适配这个特点并不难,只需求在 manifest 中清晰指定每个组件的 exported 特点即可。一般来说,遵循如下准则:假如组件中运用了 intent-filter 等特点,那么它大概率是需求对外露出的,此时需求将 exported 特点直为 true,其他状况下置为 false 即可。

关于引证的三方类库中的 xml 特点也能够经过覆写声明办法添加 exported 以兼容处理,

<activity android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
    android:exported="false"/>

3、PendingIntent 的变化

这是一个躲藏的变化,十分坑又不像 exported 特点那样容易被发觉。这边变化首要是要求开发者指定在创立 PendingIntent 的时分传入的 flags 参数的可变性。

升级 Android 目标版本到 31(S) 居然这么多坑

这能够经过在之前的 flags 基础上添加 FLAG_MUTABLEFLAG_IMMUTABLE 两个特点来完成。比方,之前我的 flags 是,PendingIntent.FLAG_CANCEL_CURRENT,当我想将其修正为不可变的时分,就能够运用如下办法进行修正:

val flags = PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE

它或许是需求改动最多的一个变化,根据我在项目中修正的状况来看,以下几个场景需求排查:

  • 桌面小控件 Appwidget
  • 通知 Notification
  • 桌面快捷办法 Shortcut

一起从几个方向来检索项目中需求改动的地方:

  • 直接检索 PendingIntent 的 flags 的调用,比方 FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENTFLAG_UPDATE_CURRENT 等,建议检查源码之后进行检索
  • PendingIntent 的静态办法工厂,比方 PendingIntent.getBroadcast()PendingIntent.getActivity() 等。由于,获取 PendingIntent 的时分需求指定 flags 参数。

那么,另一个问题来了,终究什么时分该挑选 FLAG_MUTABLE,什么时分该挑选 FLAG_IMMUTABLE 呢?

它的注释是这么说的,

Flag indicating that the created PendingIntent should be immutable. This means that the additional intent argument passed to the send methods to fill in unpopulated properties of this intent will be ignored. FLAG_IMMUTABLE only limits the ability to alter the semantics of the intent that is sent by send by the invoker of send. The creator of the PendingIntent can always update the PendingIntent itself via FLAG_UPDATE_CURRENT.

也就是说,FLAG_IMMUTABLE 的“不可变”指的是,当 PendingIntent 设置了 flags 为“不可变”之后,调用它的 send 办法时传入的 Intent 将会被忽略。

这儿举一个具体的场景,比方在列表类的 Appwidget 里,我们会运用 PendingIntent 设置列表的某一项的点击事件。考虑到列表量比较大,为每一个列表条目都声明一个 PendingIntent 明显开支太大。所以,Android 的处理机制是,

val views = RemoteViews(context.packageName, R.layout.layout_appwidget_note_list)
val i = Intent(context, MainActivity::class.java)
i.action = ACTION_APPWIDGET_NOTE_CLICK
val pi = PendingIntent.getActivity(context, 0, i, FLAG_CANCEL_CURRENT_MUTABLE)
views.setPendingIntentTemplate(R.id.lv, pi)

如上所示,首要定一个一个 PendingIntent,并调用 RemoteViews 的 setPendingIntentTemplate 办法传入,作为一个模版。然后在 RemoteViewsFactory 的 getViewAt 办法中为每个列表项设置点击时的 Intent,

val row = RemoteViews(context.packageName, R.layout.item_appwidget_note)
val i = Intent().putExtras(extras)
row.setOnClickFillInIntent(R.id.root, i)

当用户触发了点击事件的时分,体系会在调用 PendingIntent 的 send 办法时将 Intent 传入并引发组件。此时,假如我们将 PendingIntent 的 flags 设置为 FLAG_IMMUTABLE,那么这儿发送时传入的 Intent 参数将被忽略,因而或许导致尽管引发了其他组件,但是参数丢掉的状况。而关于那种,声明 PendingIntent 时就传入了 Intent 的时分,一般来说不需求设置为 FLAG_MUTABLE 的。

以上是 PendingIntent 的改动,刚好在我的项目里两种状况都有遇到,所以具体分析了一下。

4、构建项目 JDK 需求晋级

当将项目的 targetSdkVersion 晋级到了 31 之后,构建项目的时分或许会遇到如下反常,

升级 Android 目标版本到 31(S) 居然这么多坑

当然你也或许不会遇到这个问题。那首要的原因是,你的 Android Studio 里 Gradle 构建时用到的版别现已是 Java 11 的了。能够经过 Preference->Build->Gradle 检查当前 Android Studio 中运用的 JDK 版别,

升级 Android 目标版本到 31(S) 居然这么多坑

Gradle JDK 处修正构建时用的 JDK 版别即可。

以上是针对 Android Studio 构建时的状况。但当我们运用脚本或者命令行构建项目的时分需求用到的就不是 Android Studio 的 JDK 版别了。此时,能够经过 java --version 检查环境变量中配置的 JDK 版别。

升级 Android 目标版本到 31(S) 居然这么多坑

我们不能直接修正环境变量中的 JDK 版别处理上述编译问题。由于究竟除了开发,我们或许还有很多其他运用在运用 JDK 环境。此时,我们能够经过 Gradle 构建时的命令来指定构建时运用的 JDK。

gradlew -Dorg.gradle.java.home=你的 JDK 途径

关于打包脚本的修正我也更新到了项目 autopackage 中。

总结

以上是个人晋级项目的方针版别到 31 过程中遇到的一些典型的问题。此外,在适配的过程中或许还有许多其他细节需求修正,对此,参阅文首的几个链接了解即可。晋级 31 尽管没有晋级到 30 改动多,但是对大型的项目来说,需求改动的点应该还是挺多的。

如有疑问,欢迎交流!