前些日子几次遇到ProGuard的问题,想偷个懒,没好好RTFM,后来通读了一下ProGuard的Manual,有点收获,总结一下。
主要是读了Usage部分,
http://proguard.sourceforge.net/#manual/usage.html
命令:java -jar proguard.jar options ... 或 java -jar proguard.jar @myconfig.pro(myconfig.pro是配置文件)。Android提供的ant脚本把这个包含了进去,ADT也相应做了处理,所以基本不会直接用到这个。proguard包含在android sdk的tools目录下。
options或者配置文件设定了proguard的参数,分为Input/Output Options,Input/Output Options,Shrinking Options,Optimization Options,Obfuscation Options,Preverification Options,General Options 这些可选的参数。
从配置选项中其实可以看出,proguard有几部分的功能,Shrinking,Optimization,Obfuscation,Preverification。
1. Shrinking
就是缩减代码,他的工作是把代码中没被引用或者依赖的类、类成员删掉。可以通过Keep参数来设定保存。我之前就遇到这么一个问题,这里简称为“寻找getSomething游戏”(这个问题几乎会贯穿全文),我写了这么一段代码,大意如下:
public class JavascriptInterface {
void getSomething(String something) {}
}
webview.addJavascriptObject(new JavascriptInterface(), "jsi");
webview.loadUrl("javascript:window.jsi.getSomething("hello")");
就是通过js给java传个值。然后打包运行正常,再然后使用proguard处理打包,结果不正常,提示大意为object没有getSomething方法。
出了什么问题呢,因为android的proguard默认配置是开启Shrinking的,所以结果是getSomething(String)方法被删掉了,我通过反编译打出来的apk包也证实了这一点。
解决方法是,在配置文件中加入keep
-keep public class yourpackagename.JavascriptInterface
对于keep有几个类似的选项,下面的表格抄自:http://proguard.sourceforge.net/#manual/usage.html
我就不解释了。
2. Optimization
代码优化,说是bytecode层级的优化,具体怎么优化的我就不知道,而且Android默认配置也没开起这个。
3. Obfuscation
这个就是传说中的混淆了,可以通过-dontobfuscate关闭(关闭为什么还用proguard呢?)什么是混淆呢,就是把类和类成员 (包括变量和函数)的名字替换成相应的随机字符,大大增加别人解包破解你代码的难度。混淆过程生成mapping文件,记录每个类和成员被替换称什么随机字符了,也可以自己提供一些生成随机字符的规则,这里提供了很多选项,很有意思。
前面说道“寻找getSomething游戏”的例子,其实象刚才那样做并没有搞定这个问题,这里一个重要的概念是,
单写一行keep并不能让proguard不做成员变量的混淆处理,而只是不被删掉。所以getSomething作为一个类成员方法依然会被混淆,变成了一个随机字符,比如a,那么这句代码:webview.loadUrl("javascript:window.jsi.getSomething("hello")"); 显然不再能正常使用了,因为它已经找不到getSomething了。
过程中我反复解包,由于认为keep就可以防止混淆,觉得无解,因我解包发现每次getSomething都变成了a,所以进行了一种很狗屎的方法的尝试,将webview.loadUrl("javascript:window.jsi.
getSomething("hello")");换成webview.loadUrl("javascript:window.jsi.
a("hello")");,把getSomething直接写成了a。。。但是由于我的实际JavascriptInterface类里面还有一些别的东西,有些东西也会被混淆成a,所以居然连狗屎运都没有。
后来我发现-useuniqueclassmembernames这个参数,顾名思义,可以让类成员使用唯一的名字,于是我给getSomething改成了后来混淆后的唯一的名字,终于在寻找getSomething的游戏中,找到了它,虽然它已经不叫getSomething了。。。
还是觉得这个方法太屎了,胜之不武。
关于keep有个复杂的语法,下面依然抄自ProGuard文档。
[@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 ... ] *;
...
}]
。。。
我之前看了这段就跳过了。后来耐心看下还是很容易看懂的,而且这个部分后面有个说明。
这些符号其实很常见了,“|” 表示或关系,“!”表示非,“[]”表示可选,“...”代表等等,黑色的部分是关键字。
之前-keep public class yourpackagename.JavascriptInterface 这样写,注意语法有个花括号,里面是用来说明成员变量是否keep的,可以写一个范围,比如写<fields>就是所有字段不被混淆,<methods>就是所有方法不被混淆,*就是所有都不被混淆。还可以单独指出哪个函数不被混淆,比如对于“寻找getSomething游戏”,可以这样写:
-keep public class yourpackagename.JavascriptInterface {
void getSomething(java.lang.String)
}
这样,getSomething函数就不会被混淆了。这里一个值得注意的问题是,所有类都要写全称,就是包名.类名,String要写成java.lang.String,我最开始就只写了String,结果是还别混淆了,郁闷了很久。
好了,“寻找getSomething游戏”完胜了。。。它再也不会找不到getSomething了。
4. Preverification
预验证,在载入类之前的验证,《
Android前向兼容的几个问题》里面说的大概是这个,Android的ProGuard配置也没有开启这个,我也不是很清楚,就不说了。
还有很多选项,不在赘述,还是
RTFM吧!