大半夜在这儿解决编译问题可真行。
最近稍微有时间来接着看看电表。以前提到过,电表算是我用来练手各种 Android 开发技术的试验田,因此从 IDE 到三方库的绝大部分依赖版本用得都相当激进。
以前升级依赖都还算平滑,按照 IDE 的提示一路点完就是了。但这次 Android Studio 升级到 Flamingo(2022.2.1)版本,连带 AGP 升级到 8.0 之后,问题就比较头疼了,开始出现了大把的编译问题。这里写一篇文章记录一下。
JDK 版本
默认版本提高
从 AGP 8.0 开始,Gradle 构建默认采用 JDK 17。记得去 IDE 的设置里把 Gradle JDK 版本改一下。
JDK 版本导致的编译问题
大多数时候我们会手动在 build.gradle
里指定 JVM 字节码的兼容性,比如说电表目前采用的是 Java 11:
compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = '11' }
这个选项在之前很好用,但根据 Stack Overflow 上这个问题的回答,由于 AGP 8.0 和 kapt 的兼容性问题, 最终会导致设置的 jvmTarget
只会应用到 Kotlin 编译上,kapt 任务不会采用这个而是使用默认值(对于 AGP 8.0 和 Flamingo,默认的 JDK 版本是 17)。这个时候编译就会出现报错:
Execution failed for task ':app:kaptGenerateStubsDebugKotlin'. > 'compileDebugJavaWithJavac' task (current target is 11) and 'kaptGenerateStubsDebugKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version. Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain
解决办法是手动指定一下 kapt 任务的 jvmTarget
:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KaptGenerateStubs).configureEach { kotlinOptions { jvmTarget = '11' } }
R8
默认优化等级的提高
从 AGP 8.0 开始,R8 默认会开启 Full Mode,在这个模式下的优化会更加激进。因此原先的 ProGuard 规则有可能不太够用。最典型的一个例子就是 Retrofit。在升级 AGP 8.0 之后,会出现以下报错:
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType at retrofit2.v.b(SourceFile:363) at p.a1.c(SourceFile:31) at retrofit2.y0.invoke(SourceFile:45) at java.lang.reflect.Proxy.invoke(Proxy.java:1006) at $Proxy8.c(Unknown Source)
反混淆之后是:
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType at retrofit2.HttpServiceMethod.retrofit2.HttpServiceMethod parseAnnotations(retrofit2.Retrofit,java.lang.reflect.Method,retrofit2.RequestFactory)(SourceFile:363) retrofit2.ServiceMethod retrofit2.ServiceMethod.parseAnnotations(retrofit2.Retrofit,java.lang.reflect.Method) at androidx.camera.camera2.internal.ZoomControl.retrofit2.ServiceMethod retrofit2.Retrofit.loadServiceMethod(java.lang.reflect.Method)(SourceFile:31) at retrofit2.Retrofit$1.java.lang.Object invoke(java.lang.Object,java.lang.reflect.Method,java.lang.Object[])(SourceFile:45) at java.lang.reflect.Proxy.invoke(Proxy.java:1006) at $Proxy8.c(Unknown Source)
上面的 issue 里提到需要增加一些新的 ProGuard 的规则。其实这个已经在去年就有了,不过 Retrofit 一直没有发版。所以目前只能手动在 Retrofit 里加入下列规则:
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). -keep,allowobfuscation,allowshrinking interface retrofit2.Call -keep,allowobfuscation,allowshrinking class retrofit2.Response # With R8 full mode generic signatures are stripped for classes that are not # kept. Suspend functions are wrapped in continuations where the type argument # is used. -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
AGP 8.0 默认参数导致的编译失败
在老版本 AGP 中,当 R8 在混淆时对于找不到的类会发出警告。而 8.0 开始则提高到了错误级别(即强制开启了 android.r8.failOnMissingClasses
选项),会卡编译流程。不过这个问题也比较好解决,AGP 会自动生成一个文件来记录这些类,按照提示将这个文件里的内容追加到 ProGuard 的混淆规则里即可。如果用到了 Okhttp 的话会遇到这个问题,官方在 issue 里回复已经修在了 5.0.0 版本上(不过这是个测试版,感觉 backport 回 4.x 应该也不是什么难事?)。
最后
除了上面这些之外,还有一个最重要的就是彻底移除了 Transform,不过我还没写过字节码插件,不是特别清楚这个。其他一些破坏性改动可以看看 Google 的官方文档,写得还是比较清楚的。