ProGuard Introduction
介绍
ProGuard是一个Java类文件压缩器、优化器、混淆器和预校验器。在压缩步骤会发现并移除无用的类、字段、方法和属性。 在优化步骤会分析和优化Method(方法)的字节码。在混淆步骤会使用短且没有意义的名字来重命名剩余的类、字段和方法。 这些前面的步骤会使代码更小、更高效、更难反向工程。最后的预校验步骤会给这些类添加Java Micro版本需要的预校验信息或为Java 6减少启动时间的预校验信息。
这些步骤每一个都是可选的。例如,ProGuard也可以在一个程序中仅仅用来列出无效代码; 或用于在Java 6中预校验类文件提高效率。
Input jars | ||||||||
Shrunk code | ||||||||
Optim. code | Output jars | |||||||
- shrink → | - optimize → | - obfuscate → | Obfusc. code | - preverify → | ||||
Library jars | ------------------------------- (unchanged) -------------------------------→ | Library jars |
ProGuard 通常读取input jars(或 wars, ears, zips,或 目录)。然后压缩、优化、混淆和预校验它们。 压缩步骤之后,可以执行多个优化过程。ProGuard把这些处理过的结果生成一个或多个output jars (或 wars, ears, zips, 或 目录)。 输入可能包含资源文件,他们的名字和内容可以可选地被更新成反射混淆之后的类的名字。
ProGuard 要求input jars中的library jars (or wars, ears, zips, or directories) 被指明。这些在本质上是你编译代码要用到的库。ProGuard用它们来重构类的依赖,并在必要的时候做适当的处理。 这些library jars始终未被改变,你应该一直把它们放在你最终程序的类路径中。
入口点
为了决定哪些代码需要被维持原样和哪些代码可以被移除或混淆,你必须在你的代码中指定一个或多个入口点。 这些入口点通常是含有 main 方法的类、applets、midlets等等。
- 在 压缩步骤, ProGuard 从这些入口点启动并递归决定哪些类和类方法是有用的。而剩余的所有的其它类和类方法会被废弃。
- 在 优化步骤, ProGuard 进一步优化代码。不是入口点的类和方法可以被设置为private、static或final,无用的参数可能被移除,并且一些方法可能会被内联。
- 在 混淆步骤, 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
的实现可能需要被保留。你随后可以相应地修改你的配置。
为了产生合适的结果,你至少应该对你正在处理的代码有所了解。代码混淆进行了大量的反射,可能会产生失败和错误,尤其是没有内部代码的必要信息。
Copyright © 2002-2011 Eric Lafortune 翻译: 大坤.