什么是ProGuard
ProGuard是一款免费的Java class文件收缩器、优化器、混淆器和预校验器。
ProGuard 已集成到 Android 构建系统,它通过移除无用的代码以及使用语义隐晦的名称来重命名类、字段和方法,从而达到压缩、优化和混淆代码的目的。最终您将获得一个较小的 .apk 文件,此文件更难于进行反向工程。由于 ProGuard 会使应用更难于进行反向工程,因此当应用使用对安全性要求极高的功能时(例如,当您向应用授予许可时),您必须使用此工具。当您在调试模式下构建应用时,就无需处理混淆后的代码。是否运行 ProGuard 完全由您决定。
Proguard通过删除代码中在运行期间不是绝对必要的信息,来防止静态分析。相比于把代码按照它原来的样子呈现出来,这已经前进了一大步,但它仍然是一种被动的方法。
ProGuard的提供者同时提供了另外两种针对Android平台的更高级的工具:Dexguard和Dexguard Enterprise,能够抵御静态分析和动态分析。它们兼容ProGuard的配置,并为额外的功能提供了额外的选项。如果你需要更高的安全性来防止你的代码被逆向分析,可以尝试这两个工具,不过它们是收费的。
三者的对比如下:
工作流程
ProGuard的功能包含收缩(shrink)、优化、混淆、预校验4个步骤,每一步的具体工作如下:
- 收缩(shrink):检测和删除未使用的类、字段、方法、属性。ProGuard从这些种子开始递归决定哪些类以及类成员是被使用的。所有其他的类和类成员都被丢弃。
- 优化(optimization):分析和优化各个方法的字节码。ProGuard更进一步优化代码。除了其他的一些优化操作外,还把不是入口点的类和方法变成private、static或者final,把未使用过的参数删除,把某些方法变成内联的(inline)。
- 混淆(obfuscation):把剩下(即非入口点)的类、字段、方法重命名为简短而无意义的名字。在整个处理流程中,保持(keeping)入口点可以确保它们仍然能够被用原来的名字访问。
- 预校验(preverification):向class中添加预校验信息,这对Java Micro Edition来说是必须的,同时也能够提高代码在Jave 6上的启动速度。预校验步骤是唯一不需要知道哪些是入口点的一个步骤。
其中收缩、优化、混淆这3步使得代码体积更小、更高效、更难以被逆向工程。4个步骤中的每一步都是可选的。
入口点(Entry points)
从上面的描述可以知道,为了确定哪些代码需要保留,哪些代码可以丢弃或者混淆,就必须为你的代码指定一个或多个入口点(Entry point)。这些入口点通常是包含main方法的类、applet或者midlet等等。
反射(Reflection)
反射(reflection)和内省(introspection)对于任何对代码的自动处理都会表现出特别的问题。在ProGuard中,你的代码中被动态(也就是通过名字)创建或调用的类和类成员也都必须被指定为入口点。
比如,Class.forName()在运行时可能指向任何类。一般不可能预见到哪些类必须被以它们原来的名字保留,因为这些类的名字可能是从一个配置文件中读取到的。因此你必须在你的ProGuard配置中用简单的-keep
选项来指定它们。
不过Proguard会自动为你检测并处理下面这些情况:
1 | ● Class.forName("SomeClass") |
被引用的类和类成员在收缩阶段(shrinking phase)会被保留,字符串参数在混淆阶段(obfuscation phase)会被合理地更新。
除此之外,如果keeping某些类或类成员看起来是必要的,ProGuard就会提供一些建议。比如,ProGuard会对(SomeClass)Class.forName(variable).newInstance()
这样的情况进行提示,指出这里的SomeClass类或接口以及它的实现类需要被原样保留。然后你就可以据此对你的ProGuard文件进行修改。
在Android Studio中使用ProGuard
在Android Studio中,如果Build Type通过设置minifyEnabled属性来配置运行ProGuard,那么ProGuard插件(ProGuard plugin)就会自动被Android插件(Android plugin)使用,并自动创建task。配置方法如下:
1 | android { |
在Android SDK中(具体路径是${sdk.dir}/tools/proguard/)存在2个默认的规则文件(rules files):
- proguard-android.txt
- proguard-android-optimize.txt
使用getDefaultProguardFile()
会返回文件的完整路径。这2个文件除了是否开启优化(optimization)上不同,其他都是完全相同的。这2个默认文件(实际使用中只选择其中一个)定义了 ProGuard 会如何优化和混淆代码,默认的配置文件只涵盖一般的使用情形,针对自己具体的项目ProGuard的配置是需要实际情况进行定制的。
Android Studio在新建项目时,会在module的根目录下自动生成proguard-rules.pro文件,如果你需要自定义Proguard的配置,不需要去修改sdk下的那2个默认配置文件,只需要修改你的module根目录下的proguard-rules.pro就可以了。
配置 ProGuard
在某些情况下,proguard.cfg 文件中的默认配置足以满足您的需求。不过,在很多情况下,ProGuard 很难做出正确分析,因此可能会移除它认为无用而实际上您的应用却需要的代码。部分示例如下:
- 一个只在 AndroidManifest.xml 文件中引用的类
- 一个通过 JNI 调用的方法
- 动态引用的字段和方法
默认的 proguard.cfg 文件旨在涵盖一般的使用情形,但您可能会遇到异常情况,例如 ClassNotFoundException(此异常情况会在 ProGuard 删除您的应用调用的整个类时发生)。
您可以通过在 proguard.cfg 文件中添加一个-keep
行,来修复因 ProGuard 在删除代码而造成的错误。例如:
1 | -keep public class <MyClass> |
在使用 -keep 选项时,您既有许多选择也有不少需要注意的方面,因此我们强烈建议您阅读 ProGuard 手册,详细了解如何自定义您的配置文件。该手册中的“Keep 选项概述”和“示例”部分尤其有用;问题排查部分则概述了在 ProGuard 删除代码后您可能会遇到的其他常见问题。
混淆结果
ProGuard 在运行后会输出以下文件:
①dump.txt
描述 .apk 文件中所有类文件的内部结构。一个dump文件的示例片段如下:
②mapping.txt
列出原始与混淆后的类、方法和字段名称之间的对应关系。如果您从发布版本收到问题报告,则必须使用此文件,因为通过它可将混淆后的堆栈跟踪信息转换为原始的类、方法和成员名称。有关详情,请参阅解码混淆后的堆栈跟踪信息。
一个mapping.txt的片段如下:
从图中可以看出,类和类成员的名字都被替换成了无意义的字母。
③seeds.txt
列出未混淆的类和成员。一个示例文件片段如下:
④usage.txt
列出从 .apk 删除的代码。一个示例文件片段如下:
这些文件都位于以下目录中:
1 | <project_root>/<module_root>/build/outputs/mapping/ |
注意:每当您在发布模式下构建版本时,这些文件都会被 ProGuard 最新生成的文件覆盖。请在每次发布应用时为这些文件保存一份副本,以便反混淆来自发布版本的问题报告。
每次向用户发布应用时,都请保存所发布版本的 mapping.txt 文件。这样一来,如果用户遇到问题,并向您提交混淆后的堆栈跟踪信息,您就可以利用为每个发布版本保存的 mapping.txt 文件副本调试问题。每当您构建发布版本时,项目的 mapping.txt 文件都会被覆盖,因此您必须谨慎保存所需的版本。
例如,假设您发布了某个应用,并继续开发该应用的新功能,以便将来发布新版本。之后不久您使用 ProGuard 构建发布版本。此版本覆盖了之前的 mapping.txt 文件。之后,某位用户提交了问题报告,其中包含来自当前已发布的应用的堆栈跟踪信息。但您已无法调试该用户的堆栈跟踪信息,因为与该用户设备上的版本相关联的 mapping.txt 文件已被覆盖。除此之外,其他一些情况也可能会导致您的 mapping.txt 文件被覆盖。因此,如果您预计需要进行调试,请务必在每次发布应用时都保存一份副本。
如何保存 mapping.txt 文件由您自行决定。例如,您可以将其重命名以使其名称中包含版本号,也可以对其(连同源代码一起)进行版本管理。
解码混淆后的堆栈跟踪信息
当混淆后的代码输出堆栈跟踪信息时,方法名称会被混淆,即便仍能进行调试,难度也会很大。幸运的是,ProGuard 在每次运行时都会输出一个
Windows 上的 retrace.bat 脚本以及 Linux 或 Mac OS X 上的 retrace.sh 脚本可以将混淆后的堆栈跟踪信息转换成可读文件,此文件位于
1 | retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>] |
例如:
1 | retrace.bat -verbose mapping.txt obfuscated_trace.txt |
如果您不为“
其他选项(options)
Proguard拥有很多可用的选项,可以通过Proguard规则文件进行配置。
最常用的是keep选项,如果需要显示指定某个类要保留,可以使用keep来实现,比如:
1 | -keep class com.android.support.** {*;} |
如果你的代码需要打印堆栈信息,而混淆后的代码产生的堆栈信息可能无法正确获取行号和类信息,如果你需要这些信息来进行分析,可以强制保留这些信息:
1 | -keepattributes SourceFile,LineNumberTable |
当你引用第三方库时,往往要同时使用keep和dontwarn,比如:
1 | -libraryjars libs/log4j.jar |
将-dontwarn和-keep 结合使用,意思是保持包里面的所有类和所有方法而不混淆,接着还叫ProGuard不要警告找不到这个包里面的类的相关引用。
ProGuard有数量众多的options可用,如果需要查询各个option的用法,可以参考ProGuard-Usage。