前言

戏接上文,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 扩展函数。

kotlin 和 r8 的量子纠缠 | 类加载机制偷鸡

这个是我经过jadx反编译出来的一个类的信息,咱们能够发现许多关键信息都存放在元数据中。其中假如元数据丢失了或许就会影响到的便是一些kotlin和java的互相调用,还有便是一些kotlin-reflect的调用。

可是咱们在release混淆包中,这部分kotlin 1.7.10生成出来的元数据竟然被R8代码优化掉了,导致了release包的部分功用反常。

Gradle中的类加载机制

这儿要展开这个或许会比较突兀哦,可是其实咱们能够继续向下看下去就知道了。

JVM类加载机制、双亲委派和SPI机制

面试中咱们常常被问到的一个问题便是类的生命周期,以前的时分我对于这个东西是没有什么概念的,由于毕竟没有什么实际的运用场景,可是这儿雀食是有的。

kotlin 和 r8 的量子纠缠 | 类加载机制偷鸡

上图便是类的生命周期了,类加载机制有个特性,假如当时的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.1R8就能修复这个反常了。

正常情况下咱们都是在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 库和运用 “减肥”