提到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
文件中,根据自身需要,选择添加以下配置中的一条则可以继续使用Proguard。
# Disables R8 for Android Library modules only.
android.enableR8.libraries = false
# Disables R8 for all modules.
android.enableR8 = false
Proguard VS R8
如果仅看上面,R8似乎各方面都比Proguard要好,可能有人会想,既然如此那还学proguard作甚,直接R8就完事儿了呗!但事实真的是这样吗?这里简单的对比一下Proguard与R8各自的优缺点。
-
就应用的构建速度来看,R8更快。
- R8的构建时间之所以比Proguard更短,最重要的原因是因为它合并了优化、Dex编译两个步骤,免去了中间重新对所有文件遍历读写的过程。如果只是比较编译AAR包(不含D8步骤)的时间,R8将丧失它的优势。
-
就产出包的压缩率来看,R8略优。
-
就支持的优化项来看,Proguard支持的更多。
- 想详细了解的小伙伴,可以看这篇文章:https://www.guardsquare.com/en/blog/comparison-proguard-vs-r8-october-2019-edition。
-
就稳定性来看,Proguard更可靠。
- 毕竟Proguard已经走过了15年,在数百万的应用和开发者中形成了一个良性的反馈。而R8自开放以来也就1年多,并且还在进行大量的开发和功能扩展工作。
-
在使用文档方面,Google目前只在AndroidStudio的官方使用文档中提及到了R8的一些理念和使用方法,至于具体的配置项,R8完全基于Proguard的配置规则,文档也是完全依赖Proguard官方的配置文档。
-
就受众来说,R8的受众只是Android开发者,而Proguard在设计时的受众是所有Java、Kotlin开发者,只不过它因为内置在Android SDK包中,被众多Android开发者所熟知。
由于R8的配置项几乎完全套用了Proguar中定义的规范,因此Proguard与R8之间的切换是非常顺滑的,作为一名Android开发者,不论你使用Proguard还是R8来做字节码优化,Proguard都是你必须学习和掌握的。
Proguard小知识
要想Proguard用的好,熟读配置少不了,关于配置项的知识建议大家直接去官方文档学习,这里只介绍一些比较重要的或者使用时的注意项。
keep配置项
keep可以说是Proguard配置文件中最常见的一个配置项了,使用keep配置时一定要选择合适的keep级别。
-keep
- 保护类和类成员,不被移除、不被重命名
-keepnames
- 保护类和类成员,不被重命名
-keepclassmembers
- 保护类成员,不被移除、不被重命名
-keepclassmembernames
- 保护类成员,不被重命名
-keepclasseswithmembers
- 保护含有指定成员的类和类成员,不被移除、不被重命名
-keepclasseswithmembernames
- 保护含有指定成员的类和类成员,不被重命名
配置文件合并
说到Proguard配置文件,首先想到的肯定是<module-dir>/proguard-rules.pro
这个文件,但其实这只是Android使用的所有Proguard配置的冰山一角罢了。为了Android开发者能更方便地使用Proguard,Android IDE会通过插件自动生成一些通用的Proguard配置放在其他配置文件中,最终在运行Proguard时,将所有这些配置文件中的配置项进行合并。
那除了<module-dir>/proguard-rules.pro
,实际上还有哪些Proguard配置文件呢,这里介绍一下:
-
由Android Gradle插件引入的
proguard-android.txt
或proguard-android-optimize.txt
。- 在新建一个module时,它的build.gradle文件中总会包含这样一段配置:
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
意思是将使用两个proguard规则文件,第一个配置文件是
getDefaultProguardFile('proguard-android-optimize.txt')
,如果进入getDefaultProguardFile
方法查看,会发现它指向了Android SDK目录下的tools/proguard/proguard-android-optimize.txt
文件,第二个配置文件其实就是上面提到的<module-dir>/proguard-rules.pro
。public File getDefaultProguardFile(String name) { File sdkDir = sdkHandler.getAndCheckSdkFolder(); return new File(sdkDir, SdkConstants.FD_TOOLS + File.separatorChar + SdkConstants.FD_PROGUARD + File.separatorChar + name); }
proguard-android.txt
与proguard-android-optimize.txt
两个文件中都包含了对大多数 Android 项目有用的规则,proguard-android-optimize.txt
只是在proguard-android.txt
的基础上打开了Optimize功能并配置了一些Optimize规则,因此如果要使用Optimize功能请使用proguard-android-optimize.txt
。
-
由依赖的aar包引入的
<library-dir>/proguard.txt
、由依赖的jar包引入的<library-dir>/META-INF/proguard/
- 依赖库中包含的proguard配置文件的内容,实际上是开发Library时,在
consumerProguardFiles
指定的文件中定义的,用来帮助开发者在使用该库时自动处理该库的proguard配置。
android { defaultConfig { minSdkVersion 14 targetSdkVersion 29 versionCode 1 versionName "1.0" //在consumer-rules.pro文件中设置,依赖该库时会自动应用的proguard配置 consumerProguardFiles 'consumer-rules.pro' } }
- 依赖库中包含的proguard配置文件的内容,实际上是开发Library时,在
-
AAPT2工具生成的
<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
- aapt2主要负责在构建时解析资源文件,在解析的同时也会生成Proguard配置文件。例如解析AndroidManifest.xml文件时,将其中注册的四大组件全部生成keep配置,解析res/layout下的布局文件时,将其中用到的view组件生成keep配置等等。
-
开发者通过
<module-dir>/build.gradle
中的proguardFile
设置增加的自定义配置文件,如下所示,增加了一个aaa.txt配置文件。
buildTypes {
release {
minifyEnabled true
//增加一个aaa.txt作为proguard配置文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'aaa.txt'
}
}
技巧与建议
-
为了达到最好的优化效果,建议只配置最小的keep维度,不要过度keep。
-
aidl中的所有方法,都要记得做好keep。
-
所有运行时动态加载相关的代码,如反射,都要记得做好keep。
- proguard的官网提到了proguard能够自动识别和处理一些反射相关的代码场景,不需要人为配置,但试用下来感觉并不全面,建议大家还是手动配置最好。
-
在线上应用出错时,往往需要把线上收集的混淆过的错误栈信息,对照着mapping文件翻译为可读的栈信息,这个过程其实可以借助Proguard提供的retrace命令帮我们自动完成的。
- 假设应用抛出了以下异常信息
java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.y.a(MyApplication:188) at proguard.y.a(MyApplication:158) at proguard.y.a(MyApplication:136) at proguard.y.a(MyApplication:66) at proguard.ProGuard.c(MyApplication:218) at proguard.ProGuard.a(MyApplication:82) at proguard.ProGuard.main(MyApplication:538) Caused by: java.io.IOException: No such file or directory at proguard.d.q.a(MyApplication:50) at proguard.y.a(MyApplication:184) ... 6 more
- 可以将异常堆栈信息保存到stacktrace.txt文件中,运行以下命令(retrace命令在proguard包的bin目录下)
retrace mapping.txt stacktrace.txt
- 运行成功,可以看到输出了可读的堆栈信息
java.io.IOException: Can't read [dummy.jar] (No such file or directory) at proguard.InputReader.readInput(InputReader.java:188) at proguard.InputReader.readInput(InputReader.java:158) at proguard.InputReader.readInput(InputReader.java:136) at proguard.InputReader.execute(InputReader.java:66) at proguard.ProGuard.readInput(ProGuard.java:218) at proguard.ProGuard.execute(ProGuard.java:82) at proguard.ProGuard.main(ProGuard.java:538) Caused by: java.io.IOException: No such file or directory at proguard.io.DirectoryPump.pumpDataEntries(DirectoryPump.java:50) at proguard.InputReader.readInput(InputReader.java:184) ... 6 more
-
Proguard支持设置混淆时使用的字符字典,并不是只能混淆为’a’, ‘b’, 'c’这种。
# 设置用于混淆field,method名称的字典
-obfuscationdictionary Tibet.txt
# 设置用于混淆类名称的字典
-classobfuscationdictionary Tibet.txt
# 设置用于混淆包名的字典
-packageobfuscationdictionary Tibet.txt
例如,这里定义一个Tibet.txt字典文件,其中定义了一些藏语字符。

使用该字典的混淆效果如下所示

-
多Module工程中使用Proguard时的建议和注意事项
- 主Module中Proguard的开关会直接影响到子Module。只要主Module中的proguard开关是开的,那么即使子Module中的proguard开关是关闭的,在执行主Module的打包命令时,仍然会对子Module做Proguard操作。
- 建议各个Module自行维护自己的Proguard配置文件,避免所有Model全都配置在同一个文件中。
-
porguard-rules.pro文件 和 consumer-rules.pro文件
- proguard-rules.pro文件一般指的是新建module时
proguardFiles
指定的默认文件。(当然开发者可以换成别的文件名)
- consumer-rules.pro文件一般指的是一个在Library类型的Module中才会出现的配置文件,它是新建Library类型的Module时
consumerProguardFiles
指定的默认文件。(当然开发者也可以换成别的文件名)
- porguard-rules.pro文件 和 consumer-rules.pro的区别在于
- 在打包当前Module时,Proguard会用到该Module下的proguard-rules.pro文件中的配置项对其进行优化,打包完成后该文件不会出现在产出的包中。
- consumer-rules.pro文件在Library Module打包成AAR或Jar时,仍然会在包中保留该文件。
- 在打包其他用到该Library Module(只能是Library类型)或AAR/Jar包的Module时,Proguard在处理该Library Module时,会用到该Library Module或AAR/Jar包提供的consumer-rules.pro文件中的配置项进行优化。
- proguard-rules.pro文件一般指的是新建module时
-
在开发Library时,尽量不要在consumer-rules.pro中设置如
dontoptimize
这样的配置项,因为Android在打包时会合并所有的配置文件,如果在Library中设置了关闭optimize,那么所有使用该Library的Module都将关闭optimize。如果Library一定要关闭optimize,请在Library下的proguard-rules.pro文件中设置,并将Library打包成AAR/Jar包后,再给其他Module使用。
Proguard总结
- Proguard在Android Application/Library的编译过程中,起到了Shrink(压缩)、Optimize(优化)、Obfuscate(混淆) Java字节码的作用。
- 虽然Google开发了R8来替代Proguard,但R8基本完全套用了Proguard的配置,因此Proguard仍然是Android开发者必须掌握的。
- Android IDE为了方便Android开发者使用Proguard做了很多自动化管理,开发者通常只需要关注与自身项目相关的Proguard配置即可。
- Proguard只会处理项目中的Java字节码部分,对于资源文件的优化,可以通过Android Gradle插件提供的
shrinkResources
配置移除没有用到的资源,通过AndResGuard Gradle插件对资源文件进行压缩和混淆。