为了决定哪些代码要被保留哪些代码要出丢弃和混淆,必须指定入口点。这些入口点一般是 main方法,activity,service等。java
为了不引入 bug 咱们有必要对 结果进行检查。android
在Android中,开启了混淆构建会在 <module-name>/build/outputs/mapping/ 目录下会输出如下文件:正则表达式
咱们能够根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含全部指望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。小程序
ProGuard 为许多配置提供了不一样方面的过滤选项:文件名称,目录,类别,软件包,属性,优化等。
过滤器是能够包含通配符的,以逗号分隔的,名称列表。数组
只有与列表中的项目匹配的名称才会经过过滤器。微信
每种配置的通配符可能有所不一样,但如下通配符是通用的:app
此外,名称前能够加上否认感叹号 !
排除名称与进一步尝试匹配后续名称。ide
所以,若是名称与过滤器中的某个项目相匹配,则会当即接受或拒绝该项目,具体取决于项目是否具备否认符。函数
若是名称与项目不匹配,则会针对下一个项目进行测试,依此类推。工具
它若是与任何项目不匹配,则根据最后一项是否具备否认符而被接受或拒绝。
如,"!foobar,*.bar" 匹配除了foobar以外的全部以bar结尾的名称。
下面以过滤文件具体举例。
像通用过滤器同样,文件过滤器是逗号分隔的文件名列表,能够包含通配符。只有具备匹配文件名的文件被读取(在输入的状况下),或者被写入(在输出的状况下)。支持如下通配符:
例如 "java/**.class ,javax/**.class" 能够匹配 java和javax目录下全部的 class 文件。
此外,文件名前面可能带有感叹号'!'将文件名排除在与后续文件名匹配上。
例如 "!**.gif,images/**" 匹配images目录下全部除了 gif 的文件
关于更详细的用法 能够查看官方文档 filtering
指定类和类成员(字段,方法)做为入口点被保留。
例如,为了保留一个程序,你要指定Main方法和类。为了保留一个库,你应该指定全部被公开访问的元素。
-keep public class com.example.MyMain { public static void main(java.lang.String[]); }
-keep public class * { public protected *; }
Note:若是你只保留了类,没有保留类成员,那么你的类成员将不会被保留
例如 有一个实体类
public class Product implements Serializable { public static final int A = 1; public static final int B = 2; private String name; private String url; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
规则配置以下
# 保留 Product类 -keep class cn.sintoon.camera.Product
usage.txt文件中有如下内容 ,能够看到 类中的成员所有被移除了
cn.sintoon.camera.Product: public static final int A public static final int B private java.lang.String name private java.lang.String url 16:16:public java.lang.String getName() 20:21:public void setName(java.lang.String) 24:24:public java.lang.String getUrl() 28:29:public void setUrl(java.lang.String)
指定要保留的类成员, 前提是它们的类也被保留了。
例如,你想保留实现了 Serializable 接口的类中的全部 serializable 方法和字段。
-keepclassmembers class * implements java.io.Serializable { 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(); }
Note: 注意字段类型带上包名; String 类型为 java.lang.String;另外,若是只保留了类成员没有保留类跟没有保留同样
仍是拿上面那个例子,改一下规则
-keepclassmembers class * implements java.io.Serializable{ private String name; public String getName(); public static final int A; }
再看 usage.txt 类都被移除了,保留字段没毛线用。
cn.sintoon.camera.Product
指定要保留的类和类成员,条件是全部指定的类成员都在。
例如,你要保留程序中全部的主程序,不用显示的列出。
-keepclasseswithmembers public class * { public static void main(java.lang.String[]); }
仍是用上面那个例子,保留住类和全部的类成员
-keepclasseswithmembers class cn.sintoon.camera.Product{ public static final int A; public static final int B; private java.lang.String name; private java.lang.String url; public java.lang.String getName(); public void setName(java.lang.String); public java.lang.String getUrl(); public void setUrl(java.lang.String); }
看 seeds.text 中就会出现这个类和类成员
cn.sintoon.camera.Product cn.sintoon.camera.Product: int A cn.sintoon.camera.Product: int B cn.sintoon.camera.Product: java.lang.String name cn.sintoon.camera.Product: java.lang.String url cn.sintoon.camera.Product: java.lang.String getName() cn.sintoon.camera.Product: void setName(java.lang.String) cn.sintoon.camera.Product: java.lang.String getUrl() cn.sintoon.camera.Product: void setUrl(java.lang.String)
Note:必定要注意指定的类成员必须存在,若是不存在的话,这个规则至关于没有配,一点做用没有
-keep,allowshrinking class specification的简写
指定要保留名称的类成员和类成员(若是它们在压缩阶段未被删除)。
例如,你可能但愿保留实现 Serializable 接口的类的全部类名,以便处理后的代码与任何原始序列化的类保持兼容。
彻底不用的类仍然能够删除。只有在混淆时才适用。
-keepnames class * implements java.io.Serializable
Note: 前提是在压缩阶段没有被删除掉,这里至关于使用了修饰符 allowshrinking
-keepclassmembers,allowshrinking class specification 的简写
指定要保留名称的类成员(若是它们在压缩阶段未被删除)。
例如,在处理由JDK 1.2或更早版本编译的库时,可能但愿保留合成类$方法的名称。
因此当处理使用处理过的库的应用程序时,混淆器能够再次检测到它(尽管ProGuard自己不须要这个)。
只有在混淆时才适用。
-keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean); }
Note: 前提是在压缩阶段没有被删除掉,这里至关于使用了修饰符 allowshrinking
-keepclasseswithmembers,allowshrinking class specification 的简写
指定要保留名称的类和类成员,条件是全部指定的类成员都存在于收缩阶段以后。
例如,可能但愿保留全部本机方法名称和类别的名称,以便处理的代码仍能够与本机库代码连接。彻底没有使用的本地方法仍然能够被删除。
若是使用了一个类文件,但它的本地方法都不是,它的名字仍然会被混淆。只有在混淆时才适用。
-keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }
Note: 前提是在压缩阶段没有被删除掉,这里至关于使用了修饰符 allowshrinking
指定详尽列出由各类-keep选项匹配的类和类成员。列表打印到标准输出或给定文件。该列表可用于验证是否真的找到了预期的类成员,尤为是在使用通配符的状况下。
例如,您可能想要列出您保存的全部应用程序或全部小程序。
参考上面说的 seeds.txt
指定打印详细信息,说明为何给定的类和类成员正在压缩步骤中。
若是想知道为何某些给定元素出如今输出中,这会颇有用。
通常来讲,可能有不少不一样的缘由。
此选项为每一个指定的类和类成员打印最短的方法链到指定的种子或入口点。
在当前的实施中,打印出的最短链有时可能包含循环扣除 - 这些并不反映实际收缩过程。
若是指定了 -verbose 选项,则跟踪包括完整的字段和方法签名。只适用于压缩。
指定不被压缩的类文件。
默认状况下压缩是开启的,除了用各类用 keep
选项直接或间接用到的类或类成员,其余的都会被移除。
压缩步骤一般在优化以后,由于某些优化可能会打开已经删除的类或类成员。
指定列出移除的死代码。该列表打印到标准输出或给定文件。
参考上面说的 usage.txt
例如,您能够列出应用程序的未使用代码。只适用于压缩。
指定不优化输入类文件。默认状况下,优化已启用;全部方法都在字节码级别进行了优化
指定要执行的优化传递的数量。
默认状况下,执行一次传递。屡次通行可能会致使进一步的改进。若是在优化后没有找到改进,则优化结束。只适用于优化。
指定不混淆输入的类文件。
默认状况下,混淆是开启的,类和类成员会被改为新的短随机名称,除了各类-keep选项列出的名称外。
内部属性对于调试颇有用,例如源文件名,变量名和行号被删除。
指定将旧名称映射到已重命名的类和类成员的新名称的映射。映射打印到标准输出或给定文件。
例如,它是后续增量混淆所必需的,或者若是想再次理解混淆的堆栈跟踪。只有在混淆时才适用。
参考 上面说的 mapping.txt。
指定将相同的混淆名称分配给具备相同名称的类成员,并将不一样混淆名称分配给名称不一样的类成员(对于每一个给定的类成员签名)。
没有这个选项,更多的类成员能够被映射到相同的短名称,好比'a','b'等等。
这个选项所以稍微增长告终果代码的大小,可是它确保了保存的混淆名称映射老是能够在随后的增量混淆步骤中受到尊重。
例如,考虑两个不一样的接口,它们包含具备相同名称和签名的方法。若是没有此选项,这些方法可能会在第一个混淆步骤中获取不一样的混淆名称。
若是添加了包含实现两个接口的类的补丁程序,则ProGuard必须在增量混淆步骤中为这两种方法强制执行相同的方法名称。
原始模糊代码已更改,以保持结果代码的一致性。在最初的混淆步骤中使用此选项,这种重命名将永远不是必需的。
该选项仅适用于混淆。
实际上,若是计划执行增量混淆,则可能但愿彻底避免压缩和优化,由于这些步骤可能会删除或修改部分代码,这些代码对于之后的添加相当重要。
指定在混淆时不生成混合大小写的类名。
默认状况下,混淆的类名能够包含大写字符和小写字符的混合。
建立的这个彻底可接受和可用的jars 只有在不区分大小写的文件系统(好比Windows)的平台上解压缩jar时,解压缩工具可能会让相似命名的类文件相互覆盖。
解压缩后自毁的代码!真正想在Windows上解压他们的jar的开发人员可使用这个选项来关闭这种行为。
混淆的jars会所以变得稍大。
只有在混淆时才适用。
指定不混淆给定的软件包名称。
可选过滤器是包名称的逗号分隔列表。包名能够包含?,和*通配符,而且它们能够在!否认器。只有在混淆时才适用。
指定将全部重命名的软件包从新打包,方法是将它们移动到单个给定的父软件包中。若是没有参数或空字符串(''),程序包将移动到根程序包中。
该选项是进一步混淆软件包名称的一个示例。它可使处理后的代码更小,更难理解。
只有在混淆时才适用。
指定将全部重命名的类文件从新打包,方法是将它们移动到单个给定的包中。没有参数或者使用空字符串(''),该软件包将被彻底删除。
该选项将覆盖 -flattenpackagehierarchy 选项。
这是进一步模糊软件包名称的另外一个例子。
它可使处理后的代码更小,更难理解。
其不推荐使用的名称是-defaultpackage。
只有在混淆时才适用。
警告:若是在别处移动它们,则在其包目录中查找资源文件的类将再也不正常工做。若有疑问,请不要使用此选项,以避免触及包装。
指定要保留的任何可选属性。这些属性能够用一个或多个-keepattributes指令来指定。
可选过滤器是Java虚拟机和ProGuard支持的属性名称的逗号分隔列表。
属性名称能够包含?,*和**通配符,而且能够在以前加上!否认器。
例如,在处理库时,您至少应保留Exceptions,InnerClasses和Signature属性。
您还应该保留SourceFile和LineNumberTable属性以生成有用的混淆堆栈跟踪。
最后,若是你的代码依赖于它们,你可能须要保留注释。
只有在混淆时才适用。
# 保留Annotation不混淆 -keepattributes *Annotation*,InnerClasses # 避免混淆泛型 -keepattributes Signature # 抛出异常时保留代码行号 -keepattributes SourceFile,LineNumberTable
指定保留所保存方法的参数名称和类型。
该选项实际上保留了调试属性LocalVariableTable和LocalVariableTypeTable的修剪版本。
处理库时它可能颇有用。
一些IDE可使用这些信息来帮助使用该库的开发人员,
例如工具提示或自动完成。
只有在混淆时才适用。
指定要放入类文件的SourceFile属性(和SourceDir属性)中的常量字符串。请注意,该属性必须首先出现,因此它也必须使用-keepattributes指令明确保留。
例如,您可能但愿让处理过的库和应用程序生成有用的混淆堆栈跟踪。
只有在混淆时才适用
指定不预先验证已处理的类文件。
默认状况下,若是类文件针对Java Micro Edition或Java 6或更高版本,则会对其进行预验证。
对于Java Micro Edition,须要进行预验证,所以若是指定此选项,则须要在处理的代码上运行外部预验证程序。
对于Java 6,预验证是可选的,但从Java 7开始,它是必需的。
只有在最终对Android时,它才不是必需的,所以您能够将其关闭以缩短处理时间。
指定已处理的类文件针对Android平台。而后ProGuard确保一些功能与Android兼容。
例如,若是您正在处理Android应用程序,则应该指定此选项。
指定在处理期间写出更多信息。若是程序以异常终止,则此选项将打印出整个堆栈跟踪,而不只仅是异常消息。
指定不打印有关配置中可能的错误或遗漏的注释,
例如类名中的拼写错误或缺乏可能有用的选项。
可选的过滤器是一个正则表达式;
ProGuard不打印有关匹配名称的类的注释。
指定不警告有关未解决的引用和其余重要问题。
可选的过滤器是一个正则表达式; ProGuard不打印关于具备匹配名称的类的警告。忽略警告多是危险的。
例如,若是处理确实须要未解决的类或类成员,则处理后的代码将没法正常工做。
只有在你知道本身在作什么的状况下才使用此选项!
指定打印任何关于未解决的引用和其余重要问题的警告,但在任何状况下都继续处理,忽略警告。
忽略警告多是危险的。
例如,若是处理确实须要未解决的类或类成员,则处理后的代码将没法正常工做。
只有在知道本身在作什么的状况下才使用此选项!
指定使用包含的文件和替换的变量写出已解析的整个配置。结构打印到标准输出或给定文件。
这对于调试配置或将XML配置转换为更易读的格式有时会颇有用。
指定在任何处理后写出类文件的内部结构。结构打印到标准输出或给定文件。
例如,可能但愿写出给定jar文件的内容,而不进行处理。
参考上面说的 dump.txt。
指定用调试语句来处理已处理的代码,这些语句显示缺乏ProGuard配置的建议。
若是处理后的代码崩溃,那么在运行时得到实用提示可能很是有用,由于它仍然缺乏一些反射配置。
例如,代码多是使用GSON库序列化类,可能须要一些配置。一般能够将控制台的建议复制/粘贴到配置文件中。
警告:不要在发行版本中使用此选项,由于它将混淆信息添加到已处理的代码中。
指定-keep选项所保存的方法和字段的类型描述符中的任何类也应保存。
在保留方法名称时,这一般颇有用,以确保方法的参数类型不会重命名。他们的签名保持彻底不变,并与本地库兼容。
指定保持-keep选项所保存的字段的方法的代码属性也应该保留,便可能未被优化或模糊处理。这对于已优化或混淆的类一般颇有用,以确保在优化期间未修改其代码。
指定-keep选项中指定的入口点可能会压缩,即便必须另外保留它们。
也就是说,能够在压缩步骤中删除入口点,但若是它们是必需的,则它们可能未被优化或混淆。
指定-keep选项中指定的入口点可能会被优化,即便它们必须另外保存。
也就是说,入口点可能会在优化步骤中被更改,但它们可能不会被删除或混淆。
此修饰符仅用于实现不寻常的要求。
指定在-keep选项中指定的入口点可能会被混淆,即便它们必须另外保存。
也就是说,入口点可能在混淆步骤中被重命名,但它们可能不会被删除或优化。
此修饰符仅用于实现不寻常的要求。
压缩和混淆的各类-keep选项起初看起来有点混乱,但实际上它们背后有一个模式。
下表总结了它们之间的关系:
内容 | 被删除或重命名 | 被重命名 |
---|---|---|
类和类成员 | -keep | -keepnames |
只有类成员 | -keepclassmembers | -keepclassmembernames |
类和类成员,引用成员存在 | -keepclasseswithmembers | -keepclasseswithmembernames |
若是指定了一个没有类成员的类,ProGuard只保留该类及其无参数的构造函数做为入口点。它可能仍会删除,优化或混淆其余班级成员。
若是指定了一个方法,则ProGuard仅将该方法做为入口点进行保存。其代码可能仍会进行优化和调整。
类规范是类和类成员(字段和方法)的模板。它用于各类-keep选项和-assumenosideeffects选项中。相应的选项仅适用于与模板匹配的类和类成员。
模板的设计看起来很是相似于Java,并为通配符进行了一些扩展。为了理解语法,你应该看看这些例子,但这是对一个完整的正式定义的尝试:
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname [extends|implements [@annotationtype] classname] [{ [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> | (fieldtype fieldname); [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> | <init>(argumenttype,...) | classname(argumenttype,...) |(returntype methodname(argumenttype,...)); [@annotationtype] [[!]public|private|protected|static ... ] *; ... }]
方括号 “[]” 表示其内容是可选的。
省略号点“...”表示能够指定任意数量的前述项目。
垂直条“|”划定了两种选择。
非粗体括号“()”只是将属于规范的部分组合在一块儿。
缩进尝试澄清预期的含义,但在实际配置文件中,空白是不相关的。
class关键字指的是任何接口或类。interface 关键字限制匹配接口类。 enum关键字限制匹配枚举类。在 interface 或 enum 关键字前加上!将匹配限制为不是接口或枚举的类。
每个类名字都必须是彻底限定名,例如 java.lang.String 内部类用美圆符号“$”分隔,例如java.lang.Thread$State。类名能够被指定为包含如下通配符的正则表达式:
为了得到更多的灵活性,类名实际上能够是逗号分隔的类名列表,能够加!。这个符号看起来不是很像java,因此应该适度使用。
为了方便和向后兼容,类名*指任何类,而不考虑它的包。
通配符 | 意义 |
---|---|
<init> | 匹配任何构造方法 |
<fields> | 匹配任何字段 |
<methods> | 匹配任何方法 |
* | 匹配任何方法和字段 |
请注意,上述通配符没有返回类型。只有<init>通配符才有一个参数列表。
字段和方法也可使用正则表达式来指定。名称能够包含如下通配符:
通配符 | 意义 |
---|---|
? | 匹配方法名的任何单个字符 |
* | 匹配方法名的任何部分 |
<n> | 在相同的选项中匹配第n个匹配的通配符 |
类型能够包含如下通配符
通配符 | 意义 |
---|---|
% | 匹配任何原始类型(boolean,int 等,不包含 void) |
? | 匹配类名中的单个字符 |
* | 匹配类名中的任何部分但不包含包分隔符 |
** | 匹配类名中的任何部分但不包含包分隔符 |
*** | 匹配任何类型(原始类型或者非原始类型,数组或者非数组) |
--- | 匹配任何类型的任意数量的参数 |
<n> | 在相同的选项中匹配第n个匹配的通配符。 |
请注意,?,*和**通配符永远不会匹配基本类型。
并且,只有***通配符才能匹配任何维度的数组类型。
例如,“** get *()”匹配“java.lang.Object getObject()”,但不匹配“float getFloat()”和“java.lang.Object [] getObjects()”。
容许组合多个标志(例如,public static)。这意味着必须设置两个访问标志(例如 public static ),除非它们有冲突,在这种状况下,至少必须设置其中一个(例如至少public或 protected)。
ProGuard支持可能由编译器设置的其余修饰符 synthetic,bridge和varargs。
End 微信扫一扫,关注个人公众号