ProGuard是一个Java类文件压缩器、优化器、混淆器和预校验器。
压缩步骤检测并删除未使用的类、字段、方法和属性。优化步骤分析和优化方法的字节码。混淆步骤使用无意义的简短名称重命名其余的类、字段和方法。这些第一步使代码库更小、更高效,并且更难进行反向工程。最后一个预验证步骤将预验证信息添加到类中,这是Java微版本所必需的,或者可以缩短Java 6的启动时间。
这些步骤都是可选的。例如,ProGuard还可以仅用于列出应用程序中的死代码,或者用于预验证类文件,以便在Java 6中高效使用。
ProGuard通常读取输入jar(或war、ear、zip或目录)。然后它会收缩、优化、混淆和预先验证它们。可选地,可以执行多个优化传递,每个优化传递之后通常会执行另一个收缩步骤。ProGuard将处理后的结果写入一个或多个输出jar(或war、ear、zips或目录)。输入可能包含资源文件,其名称和内容可以选择性地进行更新,以反映混淆的类名。
ProGuard需要指定输入jar的库jar(或war、ear、zip或目录)。这些基本上是编译代码所需的库。ProGuard使用它们重构正确处理所需的类依赖项。库jar本身始终保持不变。您仍然应该将它们放在最终应用程序的类路径中。
入口点
为了确定哪些代码必须保留,哪些代码可以丢弃或混淆,必须为代码指定一个或多个入口点。这些入口点通常是带有主方法、applet、midlet等的类。
- 在收缩步骤中,ProGuard从这些种子开始,递归地确定使用哪些类和类成员。所有其他类和类成员都将被丢弃。
- 在优化步骤中,ProGuard进一步优化代码。在其他优化中,可以将非入口点的类和方法设置为私有的、静态的或最终的,可以删除未使用的参数,并且可以内联一些方法。
- 在混淆步骤中,ProGuard重命名不是入口点的类和类成员。在整个过程中,保留入口点可以确保仍然可以通过它们的原始名称访问它们。
- 预验证步骤是唯一不需要知道入口点的步骤。
本手册的使用部分描述了必要的-keep选项,示例部分提供了大量示例。
反射
对于任何代码的自动处理,反射和内省都会带来特定的问题。在ProGuard中,动态创建或调用(即按名称)的代码中的类或类成员也必须指定为入口点。例如,class . forname()构造可以在运行时引用任何类。通常不可能预知必须保留哪些类(使用它们的原始名称),因为类名可能从配置文件中读取,例如。因此,必须在ProGuard配置中使用相同的-keep选项指定它们。
但是,ProGuard已经为您检测并处理以下情况:
- Class.forName("SomeClass")
- SomeClass.class
- SomeClass.class.getField("someField")
- SomeClass.class.getDeclaredField("someField")
- SomeClass.class.getMethod("someMethod", new Class[] {})
- SomeClass.class.getMethod("someMethod", new Class[] { A.class })
- SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
- SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
- SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
- SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
- AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
- AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
- AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
类和类成员的名称当然可能不同,但是构造应该完全相同,这样ProGuard才能识别它们。引用的类和类成员保存在收缩阶段,字符串参数在混淆阶段正确更新。
此外,如果有必要保留一些类或类成员,ProGuard将提供一些建议。例如,ProGuard会注意到这样的构造:“(SomeClass)Class.forName(variable). newinstance()”。这可能表明类或接口的SomeClass和/或其实现可能需要保留。然后可以相应地调整配置。
要获得适当的结果,您至少应该对正在处理的代码有一定程度的熟悉。执行大量反射的混淆代码可能需要反复试验,特别是在没有关于代码内部结构的必要信息的情况下。
Keep 可选项
-keep [,modifier,...] class_specification
指定要保留为代码入口点的类和类成员(字段和方法)。例如,为了保留应用程序,可以指定主类及其主方法。为了处理库,您应该指定所有可公开访问的元素。
-keepclassmembers [,modifier,...] class_specification
指定要保留的类成员(如果它们的类也被保留的话)。例如,您可能希望保留实现Serializable接口的类的所有序列化字段和方法。
-keepclasseswithmembers [,modifier,...] class_specification
指定要保留的类和类成员,条件是所有指定的类成员都存在。例如,您可能希望保留所有具有 main 方法的应用程序,而不需要显式地列出它们。
-keepnames class_specification
-keep, allowclass_specification 的缩写
指定要保留其名称的类和类成员(如果在收缩阶段没有删除这些名称)。例如,您可能希望保留实现Serializable接口的类的所有类名,以便处理后的代码与任何最初序列化的类保持兼容。完全不使用的类仍然可以被删除。仅适用于模糊。
keepclassmembernames class_specification
- keepembers,allowshrinking class_specification 的缩写
指定要保留其名称(如果在收缩阶段没有删除这些名称)的类成员。例如,在处理JDK 1.2或更早版本编译的库时,您可能希望保留合成类$ methods的名称,这样在处理使用处理过的库的应用程序时,混淆器可以再次检测它(尽管ProGuard本身不需要这个)。仅适用于模糊。
-keepclasseswithmembernames class_specification
-keepclasseswithmembers,allowshrinking class_specification 的缩写
指定要保留其名称的类和类成员,条件是在收缩阶段之后所有指定的类成员都出现。例如,您可能希望保留所有 native 方法名称及其类的名称,以便处理后的代码仍然可以链接到本机库代码。完全不使用的本地方法仍然可以被删除。如果使用了类文件,但是没有使用它的本机方法,那么它的名称仍然是模糊的。仅适用于模糊。
-printseeds [filename]
指定详细列出由各种-keep选项匹配的类和类成员。列表被打印到标准输出或给定文件中。如果确实找到了想要的类成员,特别是在使用通配符的情况下,这个列表非常有用。例如,您可能想要列出您保存的所有应用程序或所有applet。
压缩 可选项
-dontshrink
指定不压缩输入类文件。默认情况下,应用是开启压缩的;所有类和类成员都被删除,除了各种-keep选项列出的类和它们直接或间接依赖的类。在每个优化步骤之后还应用收缩步骤,因为一些优化可能会删除更多的类和类成员。
-printusage [filename]
指定列出输入类文件的死代码。列表被打印到标准输出或给定文件中。例如,您可以列出应用程序未使用的代码。仅适用于开启压缩时。
-whyareyoukeeping class_specification
指定打印有关为什么在收缩步骤中保留给定类和类成员的详细信息。如果您想知道为什么输出中会出现某些给定的元素,那么这将非常有用。一般来说,有很多不同的原因。此选项为每个指定的类和类成员将最短的方法链打印到指定的种子或入口点。在当前实现中,打印出来的最短链有时可能包含循环扣除额——这些扣除额并不反映实际的收缩过程。如果指定-verbose选项,则跟踪包含完整的字段和方法签名。仅适用于开启压缩时。
优化 可选项
-dontoptimize
指定不优化输入类文件。默认情况下,优化是启用的;所有方法都在字节码级别上进行了优化。
-optimizations optimization_filter
在更细粒度的级别上指定要启用和禁用的优化。仅适用于优化时。这是一个专家的选择。
-optimizationpasses n
指定要执行的优化传递的次数。默认情况下,只执行一次传递。多次传递可能会导致进一步的改进。如果优化通过后没有发现任何改进,则优化结束。仅适用于优化时。
-assumenosideeffects class_specification
指定在处理过程中可以扩展类和类成员的访问修饰符。这可以改善优化步骤的结果。例如,在内联公共getter时,可能还需要将访问的字段也变为公共的。尽管Java的二进制兼容性规范在形式上不要求这样做(cfr)。Java语言规范,第二版,第13.4.6节),一些虚拟机将有问题的处理代码,否则。仅适用于优化时(以及使用-repackageclasses选项混淆时)。
反指示:在处理要用作库的代码时,可能不应该使用此选项,因为在API中设计为非公共的类和类成员可能会变成公共的。
-mergeinterfacesaggressively
指定可以合并接口,即使接口的实现类没有实现所有接口方法。这可以通过减少类的总数来减少输出的大小。注意,Java的二进制兼容性规范允许这样的构造(cfr)。Java语言规范,第二版,第13.5.3节),即使它们在Java语言中是不允许的(cfr)。Java语言规范,第二版,第8.1.4节)。仅适用于优化时。
反指示:设置此选项会降低某些jvm上处理的代码的性能,因为高级即时编译倾向于使用较少实现类的更多接口。更糟糕的是,一些jvm可能无法处理生成的代码。值得注意的是:
Sun的JRE 1.3在一个类中遇到超过256个Miranda方法(没有实现的接口方法)时,可能会抛出一个内部错误。
混淆 可选项
-dontobfuscate
指定不混淆输入类文件。默认情况下,应用混淆;类和类成员接收新的短随机名称,但不同的-keep选项列出的名称除外。删除对调试有用的内部属性,如源文件名、变量名和行号。
-printmapping [filename]
指定为已重命名的类和类成员打印从旧名称到新名称的映射。映射被打印到标准输出或给定文件中。例如,对于后续的增量混淆,或者如果您希望再次理解混淆的堆栈跟踪,就需要使用它。仅适用于开启混淆。
-applymapping filename
指定重用在前一次混淆运行ProGuard时打印出的给定名称映射。映射文件中列出的类和类成员接收与其一起指定的名称。未提及的类和类成员将接收新名称。映射可以引用输入类和库类。此选项对于增量混淆非常有用,即处理现有代码片段的外接程序或小补丁。在这种情况下,您应该考虑是否还需要选项—使用uniqueclassmembernames。只允许一个映射文件。仅适用于开启混淆。
-obfuscationdictionary filename
指定一个文本文件,其中所有有效单词都用作混淆字段和方法名称。默认情况下,短名称如“a”、“b”等用作混淆的名称。通过使用混淆字典,您可以指定保留的关键字列表,或者具有外部字符的标识符。空格、标点符号、重复单词和#符号后的注释将被忽略。请注意,混淆字典很难改善混淆。优秀的编译器可以自动替换它们,通过使用更简单的名称再次混淆,可以相当简单地恢复效果。最有用的应用程序是指定通常已经出现在类文件中的字符串(如“代码”),从而进一步减小类文件的大小。仅适用于开启混淆。
-classobfuscationdictionary filename
指定一个文本文件,其中所有有效单词都用作混淆类名。混淆字典类似于选项之一-混淆字典。仅适用于开启混淆。
-packageobfuscationdictionary filename
指定一个文本文件,其中所有有效的单词都用作混淆的包名。混淆字典类似于选项之一-混淆字典。仅适用于开启混淆。
-overloadaggressively
指定在混淆时应用主动重载。然后,多个字段和方法可以获得相同的名称,只要它们的参数和返回类型不同(不仅仅是它们的参数)。这个选项可以使处理后的代码更小(更难以理解)。仅适用于开启混淆。
反指示:生成的类文件属于Java字节码规范(cfr)。Java虚拟机规范,第二版,4.5节和4.6节的第一段),尽管这种重载在Java语言中是不允许的(cfr)。Java语言规范,第二版,8.3节和8.4.7节)。尽管如此,一些工具仍然存在问题。
-useuniqueclassmembernames
指定将相同的混淆名称分配给具有相同名称的类成员,将不同的混淆名称分配给具有不同名称的类成员(针对每个给定的类成员签名)。如果没有这个选项,可以将更多的类成员映射到相同的短名称,如“a”、“b”等。因此,该选项略微增加了生成的代码的大小,但它确保在后续的增量混淆步骤中始终尊重保存的混淆名称映射。
例如,考虑两个不同的接口,其中包含具有相同名称和签名的方法。如果没有此选项,这些方法可能在第一个混淆步骤中获得不同的混淆名称。如果随后添加了一个包含实现这两个接口的类的补丁,那么ProGuard将不得不在增量混淆步骤中为这两个方法强制使用相同的方法名。原始的混淆代码被更改,以保持生成的代码一致。在初始混淆步骤中使用此选项,将永远不需要进行此类重命名。
仅适用于开启混淆。事实上,如果您计划执行增量混淆,您可能希望避免完全收缩和优化,因为这些步骤可以删除或修改代码中对以后的添加非常重要的部分。
-dontusemixedcaseclassnames
指定在混淆时不要生成混合大小写的类名。默认情况下,混淆的类名可以包含大写字符和小写字符的混合。这将创建完全可接受和可用的jar。只有在使用不区分大小写的归档系统(例如Windows)在平台上解压缩jar时,解压缩工具才可能让名称类似的类文件彼此覆盖。解压时自毁的代码!真正想在Windows上解包jar的开发人员可以使用这个选项来关闭这个行为。请注意,混淆的jar将因此变得更大。仅适用于开启混淆。
-keeppackagenames [package_filter]
指定不混淆给定的包名称。可选过滤器是一个以逗号分隔的包名列表。包名可以包含?、和*通配符,它们的前面可以有!非元件。仅适用于开启混淆。
-flattenpackagehierarchy [package_name]
指定通过将重命名的所有包移动到给定的父包中来重新包装它们。如果没有参数或使用空字符串("),包将被移动到根包中。这个选项是进一步混淆包名称的一个例子。它可以使处理后的代码更小,更难以理解。仅适用于开启混淆。
-repackageclasses [package_name]
指定通过将重命名的所有类文件移动到单个给定包中来重新打包。如果没有参数或使用空字符串("),则该包将被完全删除。此选项覆盖-扁平化包层次结构选项。这是进一步混淆包名称的另一个例子。它可以使处理后的代码更小,更难以理解。它的弃用名称是-defaultpackage。仅适用于开启混淆。
反指示:如果将在包目录中查找资源文件的类转移到其他地方,它们将不再正常工作。如果有疑问,请不要使用此选项,以免影响包装。
-keepattributes [attribute_filter]
指定保存所保存的参数名称和方法类型。这个选项实际上保留了调试属性LocalVariableTable和LocalVariableTypeTable的精简版本。它在处理库时非常有用。一些ide可以使用这些信息来帮助使用这个库的开发人员,例如使用工具提示或自动完成。仅适用于开启混淆。
-renamesourcefileattribute [string]
指定要放入类文件的SourceFile属性(和SourceDir属性)中的常量字符串。注意,属性一开始就必须存在,因此还必须使用-keepattributes指令显式地保存它。例如,您可能希望处理后的库和应用程序生成有用的模糊堆栈跟踪。仅适用于开启混淆。
-adaptclassstrings [class_filter]
指定与类名对应的字符串常量也应该混淆。如果没有过滤器,所有与类名对应的字符串常量都会被调整。对于筛选器,只有匹配筛选器的类中的字符串常量才会被调整。例如,如果您的代码包含大量引用类的硬编码字符串,并且您不希望保留它们的名称,那么您可能希望使用这个选项。主要适用于混淆时,虽然相应的类也会自动保存在收缩步骤中。
-adaptresourcefilenames [file_filter]
根据相应类文件(如果有)的混淆名称,指定要重命名的资源文件。如果没有筛选器,与类文件对应的所有资源文件都将被重命名。使用筛选器,只重命名匹配的文件。例如,请参见处理资源文件。仅适用于开启混淆。
-adaptresourcefilecontents [file_filter]
指定要更新其内容的资源文件。资源文件中提到的任何类名都将根据相应类(如果有的话)的混淆名称进行重命名。如果没有筛选器,则更新所有资源文件的内容。使用筛选器,只更新匹配的文件。资源文件使用平台的默认字符集进行解析和编写。您可以通过设置环境变量LANG或Java系统属性file.encoding来更改这个默认字符集。例如,请参见处理资源文件。仅适用于开启混淆。
预校验 选项
-dontpreverify
指定不预验证处理后的类文件。默认情况下,如果类文件的目标是Java微版本或Java 6或更高版本,则会预先验证它们。对于Java微版本,需要预验证,因此如果指定此选项,则需要对处理后的代码运行外部预验证器。对于Java 6,不需要(目前)预先验证,但是它提高了Java虚拟机中类加载的效率。
-microedition
指定处理后的类文件以Java微版本为目标。然后,preverifier将添加适当的StackMap属性,这与Java标准版的默认StackMapTable属性不同。例如,如果您正在处理midlet,则需要此选项。
一般 可选项
-verbose
指定在处理过程中写入更多信息。如果程序以异常终止,该选项将打印出整个堆栈跟踪,而不仅仅是异常消息。
-dontnote [class_filter]
指定不打印有关配置中可能出现的错误或遗漏的注释,如类名中的拼写错误,或可能有用的选项缺失。可选过滤器是一个正则表达式;ProGuard不打印具有匹配名称的类的注释。
-dontwarn [class_filter]
指定根本不警告未解析的引用和其他重要问题。可选过滤器是一个正则表达式;ProGuard不会打印关于具有匹配名称的类的警告。忽视警告是危险的。例如,如果处理确实需要未解析的类或类成员,那么处理后的代码将不能正常工作。只有当你知道你在做什么时才使用这个选项!
-ignorewarnings
指定打印关于未解析引用和其他重要问题的警告,但在任何情况下都要继续处理。忽视警告是危险的。例如,如果处理确实需要未解析的类或类成员,那么处理后的代码将不能正常工作。只有当你知道你在做什么时才使用这个选项!
-printconfiguration [filename]
指定用包含的文件和替换的变量写出已解析的整个配置。结构被打印到标准输出或给定文件中。有时,这对于调试配置或将XML配置转换为更具可读性的格式非常有用。
-dump [filename]
指定在任何处理之后写出类文件的内部结构。结构被打印到标准输出或给定文件中。例如,您可能想要写出给定jar文件的内容,而根本不需要处理它。