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