提到Proguard,做Android的小伙伴想必是耳熟能详的,它虽然不是由Google开发维护的,Google却将其内置在了Android开发的SDK包中,在编译过程中起到了压缩、优化、混淆Android代码的作用,可以说是Android开发必不可少的一个工具。
Proguard做了什么
如下图所示,在Android应用源码的编译过程中,Proguard将Java bytecode
转化为了Optimized Java bytecode
,也就是说,Proguard起到了优化Java字节码的作用 。
Proguard优化Java字节码的过程可以分为这四个步骤:shrink(压缩)
、optimize(优化)
、obfuscate(混淆)
、preverify(预校验)
,如下图所示。
- Shrink(压缩)
- 根据设置的
EntryPoint(入口点)
,遍历每个Java字节码文件,确定哪些类及类成员会被程序使用到,不会被使用的则直接丢弃。 EntryPoint(入口点)
是Proguard中非常关键的一个概念,它定义了Proguard整个优化流程的入口,通常是由-keep
系列的配置来指定的,Shrink、Optimize、obfuscate操作都与EntryPoint
紧密关联。- Proguard只会丢弃不被使用的Java字节码,开发者通常还会搭配Android Gradle插件提供的
shrinkResources
功能来对不被使用的资源文件进行丢弃。
- 根据设置的
- Optimize(优化)
- Optimize时深入到Java字节码命令的层次进行优化。
- class维度的优化,必要时增加
final
标记、做枚举类拆箱(转为整数常量)、做类合并。 - field维度的优化,必要时增加
private
标记、移除write-only字段、方法中直接传递字段值。 - method维度的优化,必要时增加
private
、static
、final
标记、去除synchronized
标记、移除没用到的参数、直接传递参数的值而不是引用、直接返回结果值而不是引用、将方法(较短的方法、或调用次数少的方法)内联到调用方中、尾部递归简化。 - code维度的优化,必要时合并不同分支下相同的代码块,使用窥孔优化对变量、属性的存储加载、算术指令、类型转换、分支指令、常量字符、对象实例化进行优化。基于控制流和数据流分析,移除无效的代码、空捕捉的异常、内存占用等等。
- Obfuscator(混淆)
- Obfuscator时会对非
EntryPoint
的类、类成员做重命名,默认情况下它们会被命名为简短无意义的单个或多个的英文字母。
- Obfuscator时会对非
- Preverify(预校验)
- 对于Android开发场景,该步骤可以直接跳过。
Proguard与R8
R8的出现
2019年Google在发布的Android Gradle插件3.3.0版本中,提到了可以替代Proguard进行代码压缩和混淆的新工具R8,开发者可以通过在grale.properties
文件中添加以下配置启用R8。
android.enableR8 = true
如下图所示,在使用R8时,Android代码的编译步骤四步变为了三步,R8将Proguard和D8做的事情合并为了一步,因此很大程度上可以认为R8相当于Proguard + D8。
下图是R8与Proguard在处理时间、产出包大小上的对比图,可以看出使用R8后,从Java字节码到Dex字节码的构建时间有了较大提升,在产出包的体积方面则和使用Proguard相差不大。
在后来发布Android Gradle插件3.4.0版本中,R8变为默认启用状态,也就是说,开发者只要升级Android Gradle插件到3.4.0,在打包时使用的优化器就不再是Proguard而是R8了,当然,google也给开发者保留了继续使用Proguard的方法,在grale.properties
文件中,根据自身需要,选择添加以下