「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
为了减小安卓应用的大小,Google官方在Android Gradle插件中提供了几种不同的优化编译器:Proguard,D8,R8。它们的主要作用包括:
- 代码缩减(Code Shrinking):从App应用及其依赖中检测并安全地移除不使用的类、字段、方法和属性;
- 资源缩减(Resource Shrinking):从封装应用和应用库依赖项中移除不使用的资源;
- 混淆(Obfuscation):缩短类和成员的名称,从而减小 DEX 文件的大小;
- 优化(Optimization):检查并重写代码,以进一步减小应用的 DEX 文件的大小。
Proguard
Proguard是Guardsquare公司最先推出的一个免费的,可以提供缩减和优化处理的编译器,其工作流程为:
可以看到,首先.java文件会被Java编译器编译成.class文件,之后Proguard则会对.class文件进行缩减和优化,再通过Android运行环境中的Dalvik虚拟机将其编译成可以运行的.dex文件。也就是:
SourceCode(.java) → javac → Java Bytecode(.class) → Proguard → Optimized Java bytecode(.class) → Dex → *Dalvik Optimized Bytecode(.dex)
在这之后,Google推出了Jack&Jill编译器,它可以将以上步骤缩减成一步,从.java文件直接编译成.dex文件。但是这个编译器的效果并不理想,于是2017年Google决定重新使用之前的这套Proguard工作流程,但这次,Google将dx编译器进行了优化,产生了一个新的编译器: D8。
D8
D8相对于Proguard而言,最大的变化就是Google优化了dx编译器,参考Google自己的基准测试,D8相比之前的dx编译器,编译时间缩短了20%,而且编译产生的.dex文件更小。在JakeWharton的Android’s Java 8 Support中有对于D8的详细介绍,其工作流程为:
此时虽然工作流程得到了简化,但是由于Kotlin语言的出现和普及,使得Google不得不再次对于D8编译器进行改进和优化,于是在D8的基础上又出现了新的编译器:R8。
R8
R8是目前Gradle 3.4.0及以上版本的默认编译器,其使用方法和Proguard完美通用,都是通过proguard-rules.pro
这个文件来声明编译规则并执行,其工作流程为:
可以看出R8同时支持Java和Kotlin代码,通过Google提供的关于Proguard和R8的对比测试可以看出,R8比不仅在编译时间上比Proguard快了将近一半,在生成的apk文件大小上也稍小于Proguard。但有意思的是,GuardSquare公司自己也出过相同关于Proguard和R8的对比测试,所以这个就是见仁见智了。
R8编译器的使用
启用R8编译器
启用R8编译器需要在project根目录下的build.gradle
加入:
android {
buildTypes {
release {
// 对于release版本是否启用代码缩减,混淆处理和优化
minifyEnabled true
// 是否启用资源缩减(基于Android Gradle plugin)
shrinkResources true
// 根据proguard-android-optimize.txt和proguard-rules.pro文件定义处理规则
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}
复制代码
“proguard-android-optimize.txt”是Gradle PlugIn里面默认的处理规则文件,而”proguard-rules.pro“是处理规则文件,创建新项目时Android Studio也会自动生成。当需要添加一系列自定义规则时,可以在”proguard-rules.pro“中进行添加。
添加单独模块的proguard规则文件
对于一个多模块的项目,各个模块可以在模块根目录下分别定义自己独立的proguard规则文件proguard-rules.pro
,然后在模块根目录的build.gradle
中加入如下内容:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
// Using module ProGuard rules files and add it into any module which
// is dependent on this module
consumerProguardFiles 'proguard-rules.pro'
}
}
...
}
复制代码
简单来说,如果一个Library Module被App Module所依赖,那么通过在library module中声明consumerProguardFiles
属性,app module就会将自己根目录下的proguard-rules.pro
和library module的proguard-rules.pro
合并作为代码缩减,混淆等处理的规则来运行。
添加自定义Proguard-rules.pro规则
keep规则
主要用于规定对于类(class)和类成员(members)是否要进行缩减,混淆的操作。默认状态缩减和混淆都是开启的,也就是:
此时,R8会对这个类进行缩减(remove unused code)和混淆(rename things)操作。
-keep class com.foo.library.** { *; }
禁止R8对类和类成员的所有操作:
要注意这种情况非常不推荐,因为它禁止了所有对于这个类的操作。而事实上在大部分情况中总是可以选择性的进行一些优化操作的。
-keepclassmembers com.foo.library.** { *; }
禁止R8对类成员的操作,但允许对类本身进行缩减和混淆:
如果这个类本身没有被使用,它会被删掉;如果它被使用了,则会将其重命名(混淆处理),而对于其中的类成员没有任何操作。
-keepnames com.foo.library.** { *; }
只检查是否有没有被使用的类或类成员,如果有的话则删掉它们。但不进行任何重命名混淆操作。
-keepclassmembernames com.foo.library.** { *; }
检查没有被使用的类和类成员,删掉没有用的,然后对类名进行重命名,但保留类成员的名字不变。
其他常用规则命令
-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
:同理,和keep规则的唯一区别也是它只作用于存在类成员的类;
-printusage[filename]
:在standard output或者文件中打印出所有被缩减的内容;
-keepattributes[attribute_filter]
:禁止重命名class中的参数(attributes),比如使用第三方library时要禁止混淆Exceptions, InnerClasses和Signature;打印stack trace的时候要禁止混淆SourceFiles和LineNumberTable等;
-dontusemixedcaseclassnames
:进行混淆时不同时使用大小写来重命名。
总结
这里只是简单介绍了一下Proguard和R8代码优化需要了解的一些内容,对于日常安卓开发来说,只要了解这些常用的规则和如何使用即可。对于R8编译器更深层的介绍在此不作展开了,希望能对你有所帮助。
参考文章
Android Journey: Proguard, D8, R8 what are they?
Jake Wharton – Android’s Java 8 Support
Distinguishing between the different ProGuard “-keep” directives