ProGuard是一个开源的Java类文件(.class)处理工具,它同时提供了命令行和图形界面。
ProGuard能够:
- 压缩(Shrunk ):检测未使用的类、字段、方法、属性,并移除
- 优化(Optimization):分析并优化方法的字节码,可以进行多步骤的优化
- 混淆(Obfuscation ):重命名类、字段、方法,使用更短且无意义的名字
- 预校验(Preverification ):为字节码添加预校验信息,预校验信息对Java 6+是需要的
上述功能可以单独使用,也可以在一次调用中按照下面的步骤一次性完成:
参数选项说明:
选项 | 说明 |
-injars classpath | 指定输入的压缩包或者目录 |
-outjars classpath | 指定输出的压缩包或者目录 |
-libraryjars classpath | 指定输入所依赖的库。 需要注意的是,程序使用的运行时库需要明确指定(例如rt.jar) |
-skipnonpubliclibraryclasses | 是否跳过依赖库中非public类,默认不跳过,跳过可以提升性能,因为库的非公共类一般与程序无关 |
-dontskipnonpubliclibraryclasses | 不跳过依赖库中的非public类 |
-dontskipnonpubliclibraryclassmembers | 不跳过依赖库中的非public类的成员,包括字段、方法 |
-keepdirectories filter | 指定要保留的目录。 默认情况下所有目录都被移除 |
-target version | 指定输出class的版本,默认保持和输入版本一致。 |
保留选项说明
选项 | 说明 |
-keepclassmembers | -keepclassmembers [,modifier,...] class_spec 指定要保留的类成员,如果它们所属的类被保留的话 |
-keepclasseswithmembers | -keepclasseswithmembers [,modifier,...] class_spec 指定需要保留的类及其成员,包括方法和变量 |
-keepnames class_spec | 指定需要保留的类和类成员,如果在压缩阶段这些类没有被删除的话。该选项仅用于混淆阶段 |
-keepclassmembernames | 指定名称要保留的类成员,如果在压缩阶段这些类没有被删除的话。该选项仅用于混淆阶段 |
-keepclasseswithmembernames | 指定要保留的类和类成员,如果所有指定的类成员在经历了压缩阶段还存在 |
-printseeds [filename] | 打印所有匹配-keep的类和类成员,默认打印到标准输出 |
支持通过配置文件提供选项,配置文件中的注释以#开始。选项中,文件名具有空格的,必须使用引号包含。
注意选项的命名规律:-keep*用于防止目标被移除或者重命名、-keep*names则仅仅用于防止重命名。
压缩选项
选项 | 说明 |
-dontshrink | 不压缩,默认压缩输入文件:除了匹配-keep的、以及它们直接、间接依赖的所有类、类成员,都被移除。压缩阶段可能随着优化阶段运行 |
-dontobfuscate | 是否进行混淆,默认是。除了匹配-keep系列选项的类、类成员的名字将被随机的改为短名。为了方便调试而保留的内部属性,例如源代码名称、变量名、行号,都将被移除 |
-printmapping [filename] | 打印混淆前后类名、类成员名的对照 |
-applymapping filename | 指定一个先前生成的混淆名对照表,本次依照该对照继续混淆,不在表中的成员生成新的名字 |
-obfuscationdictionary filename | 指定存放有效混淆后变量名的文件 |
-overloadaggressively | 混淆时支持激进的重载,允许多个字段、方法使用重复的名字,只要参数和返回值不同 |
-useuniqueclassmembernames | 让不同名的类成员具有不同的混淆后的名称,让同名的类成员混淆后的名称依旧相同 |
-dontusemixedcaseclassnames | 混淆后,不使用混合大小写的类名 |
-keeppackagenames [pkg_filter] | 指定不混淆的包的过滤器,过滤器支持* **和前导的! |
-flattenpackagehierarchy [pkg_name] | 对所有被重命名的包进行重新打包,如果不指定参数值,则打包到根目录 |
-repackageclasses [pkg_name] | 对所有被重命名的类进行重新打包,如果不指定参数值,则打包到根目录 |
-keepattributes [attr_filter] | 指定需要保留的所有可选属性,参数值是逗号分隔的,在处理库的时候,至少应该保留Exceptions, InnerClasses, Signature属性,如果程序依赖于注解,则应该保留 |
-keepparameternames | 保留方法参数的名称和类型。 注意,方法局部变量的名称依旧会混淆 |
-renamesourcefileattribute [string] | 设置SourceFile、SourceDir 属性为指定的常量值 |
-adaptclassstrings [class_filter] | 代表了类名的字符串常量值,是否也被混淆(与目标类的名字保持一致)。如果不指定参数值,所有代表类名的字符串常量都被混淆 |
-adaptresourcefilenames [file_filter] | 是否重命名资源文件,如果其文件名反映了一个被混淆的类的名字 |
-adaptresourcefilecontents [file_filter] | 是否修改资源文件中的类名,如果对应的类的名字已经被混淆。ProGuard使用平台默认字符集读取文件,如果需要改变这一行为,需要设置LANG环境变量或者JVM系统属性file.encoding |
优化选项
选项 | 说明 |
-dontoptimize | 不进行优化。默认情况下优化启用,所有方法都在字节码级别进行优化 |
-optimizations opt_filter | 在更细粒度上控制进行哪些优化 |
-optimizationpasses n | 优化的步骤数,默认1步,如果发现没有可优化的,后续步骤自动省略 |
-assumenosideeffects class_spec | 指定不具有副作用(不改变任何状态信息)的方法规格,如果这些方法的返回值没有被使用,那么这样的调用会清除 |
-allowaccessmodification | 是否允许放宽类、类成员的访问限定符。这可能有利于优化,例如对getter()进行内联,需要相应字段成为public的 |
-mergeinterfacesaggressively | 允许接口合并,甚至在实现类没有实现所有接口方法的情况下 |
混淆选项
选项 | 说明 |
-verbose | 在处理时打印冗长的信息 |
-dontnote [class_filter] | 不打印配置选项中,与正则式匹配的类相关的错误或者疏忽 |
-dontwarn [class_filter] | 不打印配置选项中,与正则式匹配的类相关的重要错误,例如unresolved references |
-ignorewarnings | 忽略所有警告,强制进行处理。这可能很危险 |
-printconfiguration [filename] | 打印解析后的配置信息到目标文件 |
-dump [filename] | 打印处理后的类文件的内部结构 |
预校验选项
选项 | 说明 |
-dontpreverify | 指定不进行字节码预校验,对于Java6+默认开启 |
-microedition | 只是目标类文件将在JME平台上运行 |
一般选项
类路径和过滤器
ProGuard支持通过Classpath指定输入文件、输出文件。Classpath的条目使用传统的分隔符(Unix:,Windows;),条目的顺序决定优先级,如果出现重复类,前面的生效。
每个输入条目可以是 :类/资源文件、apk、jar、war、aar、zip、目录;每个输出条目可以是apk、jar、war、aar、zip、目录。各种压缩包类型支持嵌套,每个输入条目输出到最接近它的输出条目中。
ProGuard支持对Classpath条目的内容进行过滤,下面是几个例子:
- rt.jar(java/**.class,javax/**.class) 匹配rt.jar中所有java/javax开头的包中的类
- input.jar(!**.gif,images/**)匹配input.jar中images/目录中所有文件,除了GIF
- input.war(lib/**.jar,support/**.jar;**.class,**.gif) 匹配input.war的lib、support目录下所有jar,以及所有类文件和GIF文件
?匹配文件名中的单个字符;通配符*匹配文件名中的多个字符;**匹配路径中的任意字符(包括多级目录层次);!表示取反。这种过滤器语法适用于配置选项的多个方面,例如文件、目录、类、包、属性、优化,等等。
类规格(class_spec)
类规格用于指定类、类成员的匹配规则,语法如下:
# [] 表示其中的内容是可选的
# ... 表示前面列表中的项的任意组合是支持的
# | 用于分割两个备选项
# class/interface/enum用于引用相应的Java类型
# classname为全限定的类名,内部类使用$界定,例如java.lang.Thread$State。类名支持* ** ?通配符
# extends/implements的作用引用继承或者实现的Java类型
# @用于限定类、类成员被指定的类型所注解,annotationtype的格式和class一致
# <init> <fields> <methods> *分别匹配任意构造器、任意字段、任意方法和任意类成员
# 字段名、方法名支持通配符?、*
# argumenttype fieldtype returntype等支持以下通配符:
# %匹配任意基本类型;?匹配类名的单个字符;*匹配类全名中的多个字符,单不能跨越包分隔符 **匹配类全名中任意多个字符
# ***匹配任意类型;...匹配任意类型的任意数量的参数
#保留main函数为入口点
-keep public class mypackage.MyMain {
public static void main(java.lang.String[]);
}
压缩、优化并混淆一个库的典型配置:
-injars in.jar
-outjars out.jar
-libraryjars <java.home>/lib/rt.jar
-printmapping out.map
#保留本地变量表(LocalVariableTable)、本地变量类型表(LocalVariableTypeTable)
-keepparameternames
-renamesourcefileattribute SourceFile
#保留异常表,以便编译器知道哪些异常可能被抛出
#保留InnerClasses,否则外部无法引用内部类
#保留Signature,否则无法访问泛型
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
#保留所有公共类的公共/保护方法,这是库暴露的接口部分
-keep public class * {
public protected *;
}
#保留native方法的规格,以便能与native库链接
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
#保留枚举类
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保留必要的类成员,以便串行化机制可用
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
通过指定日志方法没有副作用,可以移除代码中的日志记录调用:
-assumenosideeffects interface org.slf4j.Logger { public void trace(...); public void debug(...); public void info(...); public void warn(...); public void error(...); } |
-injars in.jar -outjars out.jar -libraryjars <java.home>/lib/rt.jar -dontshrink -dontoptimize -dontobfuscate -target 1.8 |
保留所有类、类成员上的所有注解设置:
1 | -keepattributes *Annotation* |
保留数据库驱动:
#数据库驱动往往是动态加载的 -keep class * implements java.sql.Driver |
使用依赖注入的应用中,防止私有类成员被移除:
-keepclassmembers class * { @javax.annotation.Resource *; } -keepclassmembers class * { @javax.inject.Inject *; } -keepclassmembers class * { @org.springframework.beans.factory.annotation.Autowired *; } |
当程序被混淆后,适配资源文件名、资源文件内容:
1 2 | -adaptresourcefilenames **.properties,**.gif,**.jpg -adaptresourcefilecontents **.properties,META-INF/MANIFEST.MF |
也可以通过Maven插件proguard-maven-plugin进行集成