ProGuard, D8, R8编译器介绍
在编译代码,生成Android APK文件时,为了缩减生成安装包apk文件的大小,Google官方在Android Gradle插件中提供了几种不同的优化方式:Proguard,D8,R8。它们主要用于对生成的apk文件进行代码缩减(Code shrinking),资源缩减(Resource shrinking),混淆处理(Obfuscation)和优化(Optimization)。这篇文章会首先介绍一下这些编译器的产生顺序以及原因,然后再介绍一下proguard-rules.pro
中规则的定义方法。
1. Proguard
Proguard是Google Android SDK中提供的优化代码和缩减apk文件的编译器,其工作流程是:
可以看到,首先.java文件会被Java编译器编译成.class文件,之后Proguard则会对.class文件进行缩减和优化,再通过Android运行环境中的Dalvik虚拟机将.class文件编译成可以运行的.dex文件。也就是:
SourceCode(.java) — javac → Java Bytecode(.class) — Proguard → Optimized Java bytecode(.class) — Dex → Dalvik Optimized Bytecode(.dex)
在这之后,Google决定将这一系列的步骤合并成一步,于是推出了Jack & Jill编译器,它可以将以上步骤缩减成一步,即:
SourceCode(.java) — Jack & Jill → Dalvik Optimized Bytecode(.dex)
然而Jack & Jill的效果并不理想,于是2017年Google决定重新使用之前的这套Proguard工作流程,但这次,Google将dx编译器进行了优化,产生了一个新的编译器: D8。从Android Studio 3.1开始,D8成为了默认的dex编译器。
2. D8
D8相对于Proguard而言,最大的变化就是Google优化了dx编译器,其工作流程是:
虽然工作流程得到了简化,但是此时由于新的编程语言:Kotlin的出现,使得Google不得不再次对于d8编译器进行改进和优化,于是就有了目前最主流常用的代码缩减优化编译器: R8。从Android Studio 3.4或Android Gradle 3.4.0开始,默认使用R8编译器进行缩减优化,其使用方法和proguard通用,都是通过proguard-rules.pro
这个文件来声明编译规则并执行。
3. R8
首先是R8的工作流程图:
R8同时支持Java和Kotlin代码,通过Google提供的一系列测试(https://android-developers.googleblog.com/2018/11/r8-new-code-shrinker-from-google-is.html)可以看出,R8比不仅在编译时间上远快于Proguard将近一半,在生成的apk文件大小上也稍小于Proguard。这也就是为什么Google现在将R8作为默认的编译器的原因。下面我们就来看一下关于R8编译器在使用上的一些说明。
4. How to use R8 complier
启用R8编译器
首先启用R8编译器,需要在project根目录下的build.gradle
加入:
1 | android { |
其中minifyEnabled
用于启用代码缩减,混淆处理和优化,shrinkResources
用于启用资源缩减, proguard-android-optimize.txt
是Gradle PlugIn里面默认的处理规则文件,而proguard-rules.pro
则是项目根目录下在创建时Android Studio自动生成的自定义处理规则文件。当需要添加一系列自定义规则时,只需要在项目根目录下的proguard-rules.pro
中添加即可。
添加单独模块的proguard规则文件
对于一个多模块的项目,各个模块可以声明自己独立的proguard规则文件proguard-rules.pro
在模块的根目录下,并在模块的build.gradle
中加入如下内容:
1 | android { |
简单来说,如果一个Library Module被App Module所依赖,那么通过在library module中声明consumerProguardFiles
属性,app module就会将自己根目录下的proguard-rules.pro
和library module的proguard-rules.pro
合并作为代码缩减,混淆等处理的规则来运行。
自定义Proguard-rules.pro规则
default默认(R8 complier已启用)
在proguard-rules.pro
文件中,最常用的是一系列-keep
相关的规则。它主要是用于规定对于类(class)和类的成员(members)是否要进行缩减,混淆的操作。当没有自定义规则,也就是默认状态下,缩减和混淆都是开启的,也就是:
此时,R8会对这个类进行缩减(remove unused code)和混淆(rename things)操作。
-keep class
当在proguard-rules.pro
中声明:-keep class com.foo.library.** { *; }
时,R8对类和类成员的所有操作都会被禁止。也就是:
要注意这种情况非常不推荐,因为它禁止了所有对于这个类的操作。事实上在实际情况中总是可以选择性的进行一些需要的操作的,而这些“选择性”就是由下面的特殊的-keep
规则来实现的。
-keepclassmembers
keepclassmembers
会禁止R8对类成员的操作,但允许对类本身进行缩减和混淆:
也就是说,如果这个类本身没有被使用,它会被删掉;如果它被使用了,则会将其重命名(混淆处理),而对于其中的类成员没有任何操作。
-keepnames
keepnames
的逻辑很简单:只检查是否有没有被使用的类或类成员,如果有的话则删掉它们。但不进行任何重命名混淆操作。
-keepclassmembernames
keepclassmembernames
的逻辑也很简单:检查没有被使用的类和类成员,删掉没有用的,然后对类名进行重命名,但保留类成员的名字不变。
其他常用规则命令
-verbose
:打印详细的混淆信息;
-dontnote com.foo.bar.**
:不打印foo.bar包内的notes信息**,例如typo或者missing useful options;
-dontwarn com.foo.bar.**
:不打印foo.bar包内的warning信息,轻易不推荐使用;
-dontpreverify
:不进行检查校验,主要针对使用Java Micro Edition或Java 6+版本的Java Library需要进行检查校验,对于安卓平台上运行的Library来说可以添加这个规则来加快编译速度;
-keepclasseswithmembers
:和-keep
基本一致,唯一的区别就是它只作用于存在类成员的类,例如keep所有含有main method
的Application Class;
-keepclasseswithmembersnames
:同理,和-keepnames
的唯一区别也是它只作用于存在类成员的类;
-printusage[filename]
:在standard output或者文件中打印出所有被缩减的内容;
-keepattributes[attribute_filter]
:禁止重命名class中的参数(attributes),比如使用第三方library时要禁止混淆Exceptions
, InnerClasses
和Signature
;打印stack trace的时候要禁止混淆SourceFiles
和LineNumberTable
等;
-dontusemixedcaseclassnames
:进行混淆时不同时使用大小写来重命名。
参考文章
Android Journey: Proguard, D8, R8 what are they?
Distinguishing between the different ProGuard “-keep” directives