java代码存在互相引用的关系,构成一个网状关系.(个人理解)引用又分为两种:普通引用
和字符串引用(例如反射,native方法等)
.而java代码的执行入口点必然是采用的字符串引用(例如main等方法),因为外部想要执行此代码必须知道一个明确的入口点名字.
为了表述准确,类的成员变量下文称为域(Field)
,类的方法和成员变量统称成员(Member)
.
压缩Shrunk:根据所有入口点建立引用关系网,去除网外的所有代码.
优化Optimize:对入口点以外所有的方法进行分析,将其中一部分方法变为final的,static的,private的或者内联的,从而提高执行效率.
入口点以外的类,方法,成员重构为简短的名字,可以进一步减小生成文件的大小并且混淆代码.
Preverify:此处不讨论.
Proguard能够准确识别普通引用关系,但是只能识别部分典型的字符串引用(Class.forName方法等).而通过字符串引用的内容绝对不能改名字或者移除,所以就要使用keep配置proguard:
keep一共就有三个
-keep [,modifier,…] class_specification
-keepclassmembers [,modifier,…] class_specification
-keepclasseswithmembers [,modifier,…] class_specification
其中modifier为可选配置,具体意义见下文,class_specification是类和成员的模板,用来指定应用keep规则的若干类及其成员.
不配置modifier时,上面的语句可以使保留的内容不被移除优化和混淆.
modifier共有三个可选值:
class_specification是类和成员的一种模板,只有符合此模板的类和成员才会被应用keep规则.
[]内的内容是可选的, …表示可以有若干个前面紧邻项.
classname必须用全名,例如java.lang.String.可以使用正则表达式:
?
表示类名中的任意单个字符,但是不包括分割符(.)*
表示类名中的任意多个字符,但是不包括分隔符(.)**
表示类名中的任意多个字符,包括分隔符(.)可以在类名前面添加!为取非之意.为了兼容旧版本以及使用方便,类名*表示所有类(无论其在哪个包下).
extends和implements关键字是等价的
由于指定成员的规则较为复杂,下面单列一节
成员是java样式的,但是方法参数只有参数类型而没有参数名称,成员可以有以下样式(方法没有返回值,并且只有有参数列表)
代表任意构造方法.
代表任意域.
代表任意方法.*
代表任意成员(包括成员变量和方法).成员可以使用正则表达式
?
代表方法中的任意单个字符.*
代表方法中的任意多个字符.成员部分描述类型时可以使用以下通配符
%
表示任意基本类型(int,char等,但是不包括void).?
表示类名中的任意单个字符.*
表示类名中的任意多个字符,不包括分隔符(.).**
表示类名中的任意多个字符,包括分隔符(.).***
表示任意类型....
表示任意多个任意类型的参数.注意?*和**
无法匹配基本类型,只有***
可以匹配任意维度的数组类型,例如** get*()
*可以匹配java.lang.Object getObject()
但是不能匹配float getFloat()
也不能匹配java.lang.Object[] getObjects()
构造函数既可以通过短名字(不含包名)指定,也可以通过全名指定.
一般如果不了解应该采用哪一个keep的话就使用-keep,它可以keep住类名和成员名不被移除和修改名字
注意:指定了keep的类而没有指定成员则只会keep类名,成员名有可能被移除或混淆
示例1
-keep class com.test.MainActivity$Item{
*;
}
可以keep内部类(仍然为内部类),类名和成员名都没有变
示例2
keep程序入口点
-keep public class mypackage.MyMain {
public static void main(java.lang.String[]);
}