0%

ProGuard、D8 和 R8简介

ProGuard

ProGuard 是一个免费的 Java 字节码的压缩,优化,混淆器。它删除没有用的类,字段,方法与属性。使字节码最大程度地优化,使用简短且无意义的名字来重命名类、字段和方法。AndroidStudio 在 3.4.0 版本之前默认使用 ProGuard 来优化字节码。开启方法大家应该也比较熟悉,在 build.gradle 中添加 minifyEnabled true 即可,一个新建项目的配置文件看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}

SDK 里有'proguard-android.txtproguard-android-optimize.txt这两个默认的配置文件,区别是后者内有优化相关配置。proguard-rules.pro 则是项目中的一个空文件,可以配置自己的混淆和优化规则。

D8

相比 ProGuard,D8 可能很多人都不太熟悉,因为不需要直接和它打交道。了解 D8 要先从 Dalvik 虚拟机说起,早期的移动设备电池容量、内存的大小有限,存储空间也很有限,所以 Google 没有使用标准的 Java 字节码和虚拟机,而是采用了 Dex 格式和 Dalvik 虚拟机。Java 字节码是以栈为基础的(所有的变量都存储在栈中),而 Dex 格式的字节码是是寄存器为基础的(所有的变量都存储在寄存器中)。后者更加高效并且需要更少的空间。运行 Dex 字节码的 Android 虚拟机被称为 Dalvik。后来 Google 又推出了 ART 虚拟机。ART 与 Dalvik 的主要区别是,它不是在运行时进行解释和 JIT 编译,而是直接运行的提前编译好的 .oat 文件,因此获得了更好更快的运行速度,ART 虚拟机依旧运行 Dex 代码。

Dex 字节码的方案看起来不错,但是像硬币都有两面,Dex 方案的问题在于基本只能完整地支持Java6 SE,Java 后续新版本引入的语言特性并不能直接就能用在 Android 开发中。为了让我们能使用上 Java 8 的特性,Google 使用了 Transformation 来增加了一步编译过程叫 desugaring(脱糖),就是将我们代码里使用的 Java 8 新特性翻译为 Dalvik/ART 能够识别的 Java 6 字节码,但是这也导致一个新的问题:编译花费的时间变得更长。

desugaring

D8 是一种命令行工具,Android Studio 和 Android Gradle 插件使用该工具来将项目的 Java 字节码编译为在 Android 设备上运行的 DEX 字节码,该工具支持您在应用的代码中使用 Java 8 语言功能。

D8 还作为独立工具纳入了 Android 构建工具 28.0.1 及更高版本中:android_sdk/build-tools/version/

D8

Google 自己的基准测试中,D8 相比之前的 DX 方案,编译时间缩短了20%,而且.dex文件更小。

Jakewharton 的这篇博客有比较详细的 D8 支持 Java 8 特性的一些细节:Android’s Java 8 Support。里面还提到了对于纯 Kotlin 的项目,kotlinc 使用了和兼容 Java8 差不多的原理来实现 Kotlin 语言特性的支持。Android 编译工具和 ART 虚拟机对新版本 Java 语言特性的兼容带来的改动也会使 Kotlin 生成的字节码更好。

R8

在 Jakewharton 的博文《R8 Optimization: Staticization》 中是这么介绍 R8 的:

R8 is a version of D8 that also performs optimization. It’s not a separate tool or codebase, just the same tool operating in a more advanced mode. Where D8 first parses Java bytecode into its own intermediate representation (IR) and then writes out the Dalvik bytecode, R8 adds optimization passes over the IR before its written out.

简单说,R8 就是 D8 的拓展,是同一个 codebase,但是包含了和 ProGuard 相似的优化功能。毕竟 ProGuard 是一个非 Google 主导的开源项目,Google 还是像把 Android 相关工具的都自己主导吧。使用 R8 后的 Dex 生成过程示意如下图所示:

R8

在使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件默认使用 R8 编译器而不是 ProGuard 来执行编译时代码优化。在 gradle.properties 文件中可以通过开关来切换:

1
2
android.enableR8=false
android.enableR8.libraries=false

R8 依旧使用 ProGuard 规则文件来进行配置,所以几乎可以无痛得在 ProGuard 和 R8 之间进行切换。当然,两者在规则集和效果上还是有差异的。ProGuard 维护者 Guardsquare 公司自己的博客在 2018 和 2019 分别写过一次两者的对比:

R8 针对 Kotlin 做了一些优化,ProGuard 后续也计划跟上。第二篇文章总的结论就是两者非常相似,但是 ProGuard 已经有15年历史,而 R8 还很年轻。两者各有千秋,例如R8 在内联函数优化上做得更好,ProGuard 则在枚举和后续步骤的优化上更胜一筹。

目前看,Google 将 R8 设置成默认编译器,针对大多数场景,R8 已经够用了,性能和 ProGuard 也差不多。如果对后向兼容或者特殊地混淆优化规则有需求,则可以继续使用 ProGuard。

参考: