前言
戏接上文,kotlin
晋级没想到啊还有一个大坑。咱们之前说了咱们运用的agp版别是7.0.3
,在这个版别的R8
竟然会出现kotlin
混淆的bug。
断更一个月,不更文的一个原因便是由于最近感觉太菜了,并没有文章资料了。
问题排查
接下来仍是一点点进行问题剖析,咱们先从kotlin
元数据开端讲这个问题。
元数据
咱们能够参考下官方的这篇文章R8 编译器: 为 Kotlin 库和运用 “减肥”。
kotlin
中的一部分类信息都会生成在Metadata注解中,(Metadata
便是kotlin元数据)。别的工程内有一部分代码运用了kotlin-reflect
的才能,而kotlin-reflect
许多才能都是经过读取元数据来完结的。
Kotlin 元数据 是存储在 Java 类文件的注解中的一些额定信息,它由 Kotlin JVM 编译器生成。元数据确定了类文件中的类和办法是由哪些 Kotlin 代码构成的。比方,Kotlin 元数据能够告知 Kotlin 编译器类文件中的一个办法实际上是Kotlin 扩展函数。
这个是我经过jadx反编译出来的一个类的信息,咱们能够发现许多关键信息都存放在元数据中。其中假如元数据丢失了或许就会影响到的便是一些kotlin和java的互相调用,还有便是一些kotlin-reflect
的调用。
可是咱们在release
混淆包中,这部分kotlin 1.7.10
生成出来的元数据竟然被R8
代码优化掉了,导致了release
包的部分功用反常。
Gradle中的类加载机制
这儿要展开这个或许会比较突兀哦,可是其实咱们能够继续向下看下去就知道了。
JVM类加载机制、双亲委派和SPI机制
面试中咱们常常被问到的一个问题便是类的生命周期,以前的时分我对于这个东西是没有什么概念的,由于毕竟没有什么实际的运用场景,可是这儿雀食是有的。
上图便是类的生命周期了,类加载机制有个特性,假如当时的ClassLoader
内现已加载过这个类则后续就会运用这个类去完结结构,当然假如不存在则会去挂载jar
,然后从jar
中去结构出。当然咱们一般在写安卓的时分很少会出现加载两个不同版别的jar
的情况,可是这个在Gradle
编译中是被允许的,所以先后加载jar
的顺序就决定了咱们会运用哪个版别的jar
。
咱们之前就写过一个很意思的bug,咱们在Settings
插件内先加载了低版别的AGP
,之后咱们即时在build.gradle
内界说了高版别的AGP
,由于类加载机制的原因,也会把AGP
锁定在一个低版别上,由于这个jar
现已被ClassLoader
优先加载了。
单独晋级R8
接下来咱们就需要偷偷的运用上面的办法,跳过AGP 7.0.3
中低版别的R8
,直接运用高版别AGP 7.2.1
的R8
就能修复这个反常了。
正常情况下咱们都是在build.gradle
内的buildscript
去界说AGP
版别的。这次咱们只需要把这个R8
的版别放到settings.gralde
中就能够处理这个问题了。
buildscript{
dependencies {
classpath("com.android.tools:r8:3.2.60")
classpath('com.google.guava:guava:30.1.1-jre')
}
}
当然大部分情况下其实我是不主张运用这种黑魔法的,由于常常会出现办法签名等等匹配不上的情况。而R8
由于了其中有中间层的特殊性,所以能够比较容易的被替换成别的一个版别。
总结
全TM是坑啊,其实还有好几个问题我都没说。只能说世事无常大肠包小肠。
别的由于咱们有一部分办法签名检查的a8
便是根据r8
开发的,所以后边就或许还有一篇吧。
我计划后续吹嘘下Gradle Enterprise
,试用阶段发现真的仍是挺好用的。
参考文献
Data class metadata is removed with proguard / R8 for Kotlin 1.6.0
R8 编译器: 为 Kotlin 库和运用 “减肥”