为什么写这个系列
主要是看了 芦大和 P 大的文章,受益颇多,虽然有很多不解的地方,但是大体上已经有了学习的脉络,刚好看到很多人都觉得这几篇文章读起来很吃力,其实我也一样。 要搞懂大佬们的文章,主要是要对 r8 和 art 有一定理解, art 实验起来颇为不易,我们暂且跳过(后面我慢慢补,其实现在我也没太懂,等我学一下再说)
r8对于大部分 app 开发者而言,看起来应该不会有太多阻碍,所以先开坑 r8吧
r8相关部分主要参考jakewharton的 blog
因为 r8 的版本不同,所以部分结果可能和 jw 的原文有出入,大家自行分辨即可
准备工作
准备好独立的 r8 编译环境 具体可以参考官方文档谷歌 r8
不过大部分人肯定会报错, 在构建 r8 之前,我们需要下载一个特殊的工具
bash
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
然后加入到环境变量中
bash
export PATH="$PATH:$HOME/depot_tools"
最后按照 google 文档 就可以了
shell
$ git clone https://r8.googlesource.com/r8
$ cd r8
$ tools/gradle.py r8
一阵漫长的等待
这个 r8.jar 就是我们的目标产物了,

整个仓库最终大概最终有个 20gb 左右,首次编译我差不多 2 小时才好

不过这里有一个疑问,我在 maven 上直接下载的 r8.jar 是无法独立运行的,不知道和这个构建出来的 r8.jar 具体有啥区别, 有知道的大佬可以指教下
这条路打通以后, 对我们研究 r8 是很有好处的,毕竟可以自己 build 出r8 不用等 agp 升级了。
准备 2 个命令 把 java 和 kotlin 的代码打成 jar 包,方便我们用 jadx 反编译直接看
shell
# 指定哪个类是 main 方法的类
jar cvfe main.jar org.example.main *
shell
# 指定哪个类是 main 方法的类
kotlinc src/main/kotlin/Main.kt -d main.jar
静态化优化
看下代码:
kotlin
package org.example
class Greeter(val greeting:String){
fun greet(name: String) = "$greeting,$name"
companion object{
fun hello() = Greeter("hello")
fun random() = Greeter("${Math.random()}")
}
}
fun main() {
println(Greeter.hello().greet("world"))
println(Greeter.random().greet("world"))
}
混淆文件
arduino
-keepclasseswithmembers class * {
public static void main(java.lang.String[]);
}
-dontwarn org.jetbrains.annotations.NotNull
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-dontobfuscate
我们用 kotlinc build 出一个 jar 包
然后放到 jadx 中反编译一下
看下具体的指令:

如果看不懂的话也没关系,我们可以看下代码

其实是符合预期的对吧,
用 r8 处理一下
shell
java -cp r8.jar com.android.tools.r8.R8 --release --classfile --output ouput.jar --pg-conf /Users/xxx/Desktop/company_code/R8Test/rules.txt --lib /Users/xxx/Library/Android/sdk/platforms/android-33/android.jar /Users/xxx/Desktop/company_code/R8Test/main.jar
然后再用 jadx 反编译一下
还是先看指令

再看代码

经过 r8 处理过的代码,其 伴生类 被抹除,等于少了一个类的加载
如果我们把代码改的简单点,其优化效果会更加明显
kotlin
package org.example
class Greeter(val greeting:String){
fun greet(name: String) = "$greeting,$name"
companion object{
fun hello() = Greeter("hello")
fun random() = Greeter("${Math.random()}")
}
}
fun main() {
println(Greeter.hello().greet("world"))
}

这次直接啥都不要了 直接输出对应的字符串


实际上不止是伴生对象,object 单例 也是有类似的优化
修改下我们的代码
kotlin
package org.example
class Greeter(val greeting:String){
fun greet(name: String) = "$greeting,$name"
companion object{
fun hello() = Greeter("hello")
fun random() = Greeter("${Math.random()}")
}
}
object HelloGreeter{
fun hello() = Greeter("hello")
}
fun main() {
println(HelloGreeter.hello())
}
未使用 R8 的时候
还需要取 HelloGreeter 的实例


使用 r8 的时候
优化效果惊人:


这种优化 不仅仅是针对 kotlin,java 也是一样
有兴趣的可以自行做下实验
java
package org.example;
public class Thing {
public static final Thing INSTANCE = new Thing();
private Thing() {}
public void doThing() {
if (Math.random() > 0.5) {
System.out.println(">0.5");
}
}
}
总结一下,R8 的代码优化工作 会帮助我们减少类的加载