混淆(Proguard)用法
最近項(xiàng)目中遇到一些混淆相關(guān)的問(wèn)題,由于之前對(duì)proguard了解不多,所以每次都是面向Stackoverflow的編程。copy別人的答案內(nèi)心還可以接受,但是copy了之后不懂別人的邏輯是無(wú)法忍受的。首先不清楚別人的答案是不是一定符合自己的需求;其次,再遇到同類問(wèn)題還是得抓瞎。于是下決心看了一下proguard的官方文檔。很長(zhǎng),但是很詳細(xì),在這里整理一下筆記,分享給大家。
介紹
我們通常說(shuō)的proguard
包括四個(gè)功能,shrinker
(壓縮), optimizer
(優(yōu)化),obfuscator
(混淆),preverifier
(預(yù)校驗(yàn))。
- shrink: 檢測(cè)并移除沒有用到的類,變量,方法和屬性;
- optimize: 優(yōu)化代碼,非入口節(jié)點(diǎn)類會(huì)加上
private
/static
/final
, 沒有用到的參數(shù)會(huì)被刪除,一些方法可能會(huì)變成內(nèi)聯(lián)代碼。 - obfuscate: 使用短又沒有語(yǔ)義的名字重命名非入口類的類名,變量名,方法名。入口類的名字保持不變。
- preverify: 預(yù)校驗(yàn)代碼是否符合Java1.6或者更高的規(guī)范(唯一一個(gè)與入口類不相關(guān)的步驟)
如果你的代碼中用到了反射,那需要把反射調(diào)用的類,變量,方法也設(shè)置成入口節(jié)點(diǎn)。只需要加上
-keep
就可以了。(下面會(huì)講)
除了proguard之外,還有一個(gè)DexGuard,是專門用來(lái)優(yōu)化混淆Android應(yīng)用的。它的功能包括資源混淆,字符串加密,類加密和dex文件分割等。它是在android編譯的時(shí)候直接產(chǎn)生Dalvik字節(jié)碼。(這個(gè)有興趣的同學(xué)自行了解,這里不詳述了)
用法
要執(zhí)行proguard,可以直接執(zhí)行命令:
java -jar proguard.jar options ...
如果有Android SDK的同學(xué)可以在
{ANDROID_SDK_ROOT}/tools/proguard/lib/
目錄下找到proguard.jar
這個(gè)jar包。或者,也可以在{ANDROID_SDK_ROOT}/tools/proguard/bin
目錄下直接使用腳本執(zhí)行命令。
我們也可以把proguard的參數(shù)寫到一個(gè)配置文件中,比如說(shuō)proguard.cfg
。那我們的命令可以這樣寫:
java -jar proguard.jar @proguard.cfg
這個(gè)文件也就是我們?cè)贏ndroid Studio中經(jīng)常配置的混淆文件了。我們?cè)诰幾g正式包的時(shí)候打包腳本自動(dòng)幫我們執(zhí)行了這條命令。通過(guò)這個(gè)腳本可以避免重復(fù)輸入?yún)?shù)。
當(dāng)然,我們也可以配置文件與命令行參數(shù)混用,例如:
java -jar proguard.jar @proguard.cfg -verbose
- 配置文件中
#
放在行首,用來(lái)做注釋; - 單詞之間多余的空格或分隔符會(huì)被忽略;
- 如果文件名包含空格或者其它特殊符號(hào),應(yīng)當(dāng)用單引號(hào)或者雙引號(hào)括起來(lái);
- 配置參數(shù)的順序與混淆結(jié)果是沒有關(guān)系的
輸入/輸出選項(xiàng)
這部分內(nèi)容平常比較少用到,如果僅僅是做app開發(fā)的話,了解一下這一節(jié)即可。
@filename -include 的簡(jiǎn)寫
-include filename 需要讀取的配置文件
-basedirectory directory 為所有引用的相對(duì)路徑指定一個(gè)根路徑
-injars classpath 指定輸入的包,可以包括 jar, aar, war, ear, zip, apk或者文件目錄。這些包或者目錄下的class文件將被處理后寫入到輸出文件中。默認(rèn)情況下非class文件會(huì)被原封不動(dòng)的復(fù)制到輸出文件中。
需要注意的是,默認(rèn)情況下,一些編譯器的臨時(shí)的文件也會(huì)被寫入到輸出文件中。可以使用過(guò)濾選項(xiàng)過(guò)濾掉他們。過(guò)濾選選項(xiàng)后面有講到。
-outjars classpath 指定輸出文件,類型包括 jar, aar, war, ear, zip, apk和 目錄。
不要讓輸出文件覆蓋任何一個(gè)輸入文件!
-libraryjars classpaath 指定輸入文件引用的類庫(kù)。這些類庫(kù)不會(huì)被寫入到輸出文件中。每個(gè)庫(kù)至少要有一個(gè)類被引用。
在查找類庫(kù)的時(shí)候,proguard運(yùn)行時(shí)的類庫(kù)是不算在內(nèi)的。需要指明的是應(yīng)用在運(yùn)行時(shí)依賴的類庫(kù)。
簡(jiǎn)單用法:
-injars classes
-injars in1.jar
-injars in2.jar
-injars in3.jar
-libraryjars <java.home>/lib/rt.jar(java/**,javax/**)
-outjars out.jar
-skipnonpubliclibraryclasses 指定讀取引用庫(kù)文件的時(shí)候跳過(guò)非public類。這樣做可以提高處理速度并節(jié)省內(nèi)存。一般情況下非public在應(yīng)用內(nèi)是引用不到的,跳過(guò)它們也沒什么關(guān)系。但是,在一些java類庫(kù)中中出現(xiàn)了public類繼承非public類的情況,這樣就不能用這個(gè)選項(xiàng)了。這種情況下,會(huì)打印一個(gè)警告出來(lái),提示找不到類。
-dontskipnonpubliclibraryclasses 跟上面的參數(shù)相對(duì)。版本4.5以上,這個(gè)是默認(rèn)的選項(xiàng)。
-dontskipnonpubliclibraryclassmembers 指定不忽略庫(kù)類庫(kù)中的非public成員(成員變量和方法)。默認(rèn)情況下,proguard在讀取庫(kù)文件的時(shí)候會(huì)自動(dòng)忽略這些類的成員,因?yàn)檫@些非public成員不會(huì)被源代碼引用到。但有時(shí)候他們是可以被引用到的。比如說(shuō),源代碼中與庫(kù)文件用同一個(gè)包名,那么源代碼就可以訪問(wèn)包作用域的變量。在這些情況下,為了引用一致,不被混淆,就需要指定不跳過(guò)這些類。
-keepdirectories directory_filter 指定輸出jar包中需要保留的目錄名。為了減少輸出文件的體積,默認(rèn)情況下所有的目錄都會(huì)被刪除(個(gè)人這里不是很理解,猜測(cè)意思是默認(rèn)所有目錄都會(huì)被混淆吧)。但是如果你的代碼中有需要從目錄中尋找文件的邏輯,那你就需要保持目錄名一致。這項(xiàng)配置后面不加過(guò)濾器的時(shí)候,所有目錄都會(huì)被保留。加了過(guò)濾器之后,只有過(guò)濾器匹配的目錄才會(huì)被保留。
-target version 指定處理的class文件中java的目標(biāo)版本。版本號(hào)是1.0, 1.1, 1.2, 1.3, 1.4, 1.5(或者5), 1.6(或者6), 1.7(或者7),1.8(或者8)之中的一個(gè)。默認(rèn)情況下,class文件的版本號(hào)是不會(huì)變的。
-forceprocessing 盡管輸出文件已經(jīng)是最新的,還是強(qiáng)制進(jìn)行處理一次。
Keep配置
-keep [,modifier, ...] class_specification 指定類和類的成員變量是入口節(jié)點(diǎn),保護(hù)它們不被移除混淆。例如,對(duì)一個(gè)可執(zhí)行jar包來(lái)說(shuō),需要保護(hù)main的入口類;對(duì)一個(gè)類庫(kù)來(lái)說(shuō)需要保護(hù)它的所有public元素。例子:
-injars myapplication.jar
-outjars myapplication_out.jar
-libraryjars <java.home>/lib/rt.jar
-printmapping myapplication.map
-keep public class mypackage.MyMain {
public static void main(java.lang.String[]);
}
-keepclassmembers [,modifier] class_specification 保護(hù)的指定的成員變量不被移除、優(yōu)化、混淆。例如,保護(hù)所有序列化的類的成員變量。
例子:
-keepnames class * implements java.io.Serializable
# 這指定了繼承Serizalizable的類的如下成員不被移除混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keepclasseswithmembers [,modifier,...] class_specification 擁有指定成員的類將被保護(hù),根據(jù)類成員確定一些將要被保護(hù)的類。例如保護(hù)所有含有main方法的類。
例子:
# 這種情況下含有main方法的類和mainf方法都不會(huì)被混淆。
-injars in.jar
-outjars out.jar
-libraryjars <java.home>/lib/rt.jar
-printseeds
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
-keepnames class_specification 是 -keep,allowshrinking class_pecification
的簡(jiǎn)寫。指定一些類名受到保護(hù),前提是他們?cè)趕hrink這一階段沒有被去掉。也就是說(shuō)沒有被入口節(jié)點(diǎn)直接或間接引用的類還是會(huì)被刪除。僅在obfuscate
階段有效。
-keepclassmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification
的簡(jiǎn)寫。與-keepclassmember
相似。保護(hù)指定的類成員,前提是這些成員在shrink
階段沒有被刪除。僅在obfuscate
階段有效。
-keepclasseswithmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification
的簡(jiǎn)寫。與-keepclasseswithmembers
類似。保護(hù)指定的類,如果它們沒有在shrink
階段被刪除。僅在obfuscate
階段有效。
-printseeds [filename] 指定通過(guò)-keep
配置匹配的類或者類成員的詳細(xì)列表。列表可以打印到標(biāo)準(zhǔn)輸出流或者文件里面。這個(gè)列表可以看到我們想要保護(hù)的類或者成員有沒有被真正的保護(hù)到,尤其是那些使用通配符匹配的類。
代碼壓縮配置
-dontshrink 聲明不壓縮輸入文件。默認(rèn)情況下,除了-keep
相關(guān)配置指定的類,所有其它沒有被引用到的類都會(huì)被刪除。每次optimizate
操作之后,也會(huì)執(zhí)行一次壓縮操作,因?yàn)槊看?code>optimizate操作可能刪除一部分不再需要的類。
-printusage [filename] 聲明 打印出那些被刪除的元素。這個(gè)列表可能打印到標(biāo)準(zhǔn)輸出流或者一個(gè)文件中。僅在shrink
階段有效。
whyareyoukeeping class_specification 聲明 打印為什么一個(gè)類或類的成員變量被保護(hù)。這對(duì)檢查一個(gè)輸出文件中的類的結(jié)果有幫助。
代碼優(yōu)化配置
-dontoptimize 聲明不優(yōu)化輸入文件。默認(rèn)情況下,優(yōu)化選項(xiàng)是開啟的,并且所有的優(yōu)化都是在字節(jié)碼層進(jìn)行的。
-optimizations optimization_filter 更加細(xì)粒度地聲明優(yōu)化開啟或者關(guān)閉。只在optimize
這一階段有效。這個(gè)選項(xiàng)的使用難度較高。
-optimizationpasses n 指定執(zhí)行幾次優(yōu)化,默認(rèn)情況下,只執(zhí)行一次優(yōu)化。執(zhí)行多次優(yōu)化可以提高優(yōu)化的效果,但是,如果執(zhí)行過(guò)一次優(yōu)化之后沒有效果,就會(huì)停止優(yōu)化,剩下的設(shè)置次數(shù)不再執(zhí)行。這個(gè)選項(xiàng)只在optimizate
階段有效
assumenosideeffects class_specification 指定一些方法被刪除也沒有影響(盡管這些方法可能有返回值),在optimize
階段,如果確定這些方法的返回值沒有使用,那么就會(huì)刪除這些方法的調(diào)用。proguard會(huì)自動(dòng)的分析你的代碼,但不會(huì)分析處理類庫(kù)中的代碼。例如,可以指定System.currentTimeMillis()
,這樣在optimize
階段就會(huì)刪除所有的它的調(diào)用。還可以用它來(lái)刪除打印Log的調(diào)用。這條配置選項(xiàng)只在optimizate
階段有用。
例子:
# 刪除代碼中Log相關(guān)的代碼
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
使用這條配置有點(diǎn)危險(xiǎn),如果刪除了一些預(yù)料之外的代碼,很容易就會(huì)導(dǎo)致代碼崩潰。所以,謹(jǐn)慎使用
-allowaccessmodification 這項(xiàng)配置是設(shè)置是否允許改變作用域的。使用這項(xiàng)配置之后可以提高優(yōu)化的效果。**但是,如果你的代碼是一個(gè)庫(kù)的話,最好不要配置這個(gè)選項(xiàng),因?yàn)樗赡軙?huì)導(dǎo)致一些private
變量被改成public
。
-mergeinterfacesaggressively 指定一些接口可能被合并,即使一些子類沒有同時(shí)實(shí)現(xiàn)兩個(gè)接口的方法。這種情況在java源碼中是不允許存在的,但是在java字節(jié)碼中是允許存在的。它的作用是通過(guò)合并接口減少類的數(shù)量,從而達(dá)到減少輸出文件體積的效果。僅在optimize
階段有效。
這項(xiàng)配置對(duì)于一些虛擬機(jī)的65535方法數(shù)限制是有一定效果的。
混淆配置
-dontobfuscate 聲明不混淆。默認(rèn)情況下,混淆是開啟的。除了keep
配置中聲明的類,其它的類或者類的成員混淆后會(huì)改成簡(jiǎn)短隨機(jī)的名字。
-printmapping [filename] 指定輸出新舊元素名的對(duì)照表的文件。映射表會(huì)被輸出到標(biāo)準(zhǔn)輸出流或者是一個(gè)指定的文件。
這個(gè)文件在追蹤異常的時(shí)候是有用的,在
{android_sdk_home}/tools/proguard/lib
目錄下有一個(gè)retrace.jar
文件。我們可以把混淆后的Stack Trace用這個(gè)工具處理一下,就會(huì)轉(zhuǎn)變成容易閱讀的類。所以,做app應(yīng)用的同學(xué)每次發(fā)版本的時(shí)候都要把這個(gè)文件留下來(lái),并標(biāo)記清楚版本。這對(duì)線上版本的調(diào)試非常重要。
-applymapping filename 指定重用一個(gè)已經(jīng)寫好了的map文件作為新舊元素名的映射。元素名已經(jīng)存在在mapping文件中的元素,按照映射表重命名;沒有存在到mapping文件的元素,重新賦一個(gè)新的名字。mapping文件可能引用到輸入文件中的類和類庫(kù)中的類。這里只允許設(shè)置一個(gè)mapping文件。僅在obfuscate
階段有效。
-obfuscationdictionary filename 指定一個(gè)文本文件用來(lái)生成混淆后的名字。默認(rèn)情況下,混淆后的名字一般為a,b,c這種。通過(guò)使用-obfuscationdictionary
配置的字典文件,可以使用一些非英文字符做為類名。成員變量名、方法名。字典文件中的空格,標(biāo)點(diǎn)符號(hào),重復(fù)的詞,還有以'#'
開頭的行都會(huì)被忽略。需要注意的是添加了字典并不會(huì)顯著提高混淆的效果,只不過(guò)是更不利與人類的閱讀。正常的編譯器會(huì)自動(dòng)處理他們,并且輸出出來(lái)的jar包也可以輕易的換個(gè)字典再重新混淆一次。最有用的做法一般是選擇已經(jīng)在類文件中存在的字符串做字典,這樣可以稍微壓縮包的體積。
查找了字典文件的格式:一行一個(gè)單詞,空行忽略,重復(fù)忽略
例如:
# 這里巧妙地使用java中的關(guān)鍵字作字典,混淆之后的代碼更加不利于閱讀
#
# This obfuscation dictionary contains reserved Java keywords. They can't
# be used in Java source files, but they can be used in compiled class files.
# Note that this hardly improves the obfuscation. Decent decompilers can
# automatically replace reserved keywords, and the effect can fairly simply be
# undone by obfuscating again with simpler names.
# Usage:
# java -jar proguard.jar ..... -obfuscationdictionary keywords.txt
#
do
if
for
int
new
try
byte
case
char
else
goto
long
this
void
break
catch
class
const
final
float
short
super
throw
while
double
import
native
public
return
static
switch
throws
boolean
default
extends
finally
package
private
abstract
continue
strictfp
volatile
interface
protected
transient
implements
instanceof
synchronized
-classobfuscationdictionary filename 指定一個(gè)混淆類名的字典,字典的格式與-obfuscationdictionary
相同
-packageobfuscationdictionary filename 指定一個(gè)混淆包名的字典,字典格式與-obfuscationdictionary
相同
-overloadaggressively 混淆的時(shí)候大量使用重載,多個(gè)方法名使用同一個(gè)混淆名,但是他們的方法簽名不同。這可以使包的體積減小一部分,也可以加大理解的難度。僅在混淆階段有效。
注意,這項(xiàng)配置有一定的限制:
Sun的JDK1.2上會(huì)報(bào)異常
Sun JRE 1.4上重載一些方法之后會(huì)導(dǎo)致序列化失敗
Sun JRE 1.5上pack200 tool重載一些類之后會(huì)報(bào)錯(cuò)
java.lang.reflect.Proxy類不能處理重載的方法
Google's Dalvik VM can't handle overloaded static fields(這句我不懂,重載靜態(tài)變量是什么意思?有看懂的同學(xué)可以回復(fù)一下)
-useuniqueclassmembernames 指定相同的混淆名對(duì)應(yīng)相同的方法名,不同的混淆名對(duì)應(yīng)不同的方法名。如果不設(shè)置這個(gè)選項(xiàng),同一個(gè)類中將會(huì)有很多方法映射到相同的方法名。這項(xiàng)配置會(huì)稍微增加輸出文件中的代碼,但是它能夠保證保存下來(lái)的mapping文件能夠在隨后的增量混淆中繼續(xù)被遵守,避免重新命名。比如說(shuō),兩個(gè)接口擁有同名方法和相同的簽名。如果沒有這個(gè)配置,在第一次打包混淆之后,他們兩個(gè)方法可能會(huì)被賦予不同的混淆名。如果說(shuō)下一次添加代碼的時(shí)候有一個(gè)類同時(shí)實(shí)現(xiàn)了兩個(gè)接口,那么混淆的時(shí)候必然會(huì)將兩個(gè)混淆后的方法名統(tǒng)一起來(lái)。這樣就必須要改混淆文件其中一處的配置,也就不能保證前后兩次混淆的mapping文件一致了。(如果你只想保留一份mapping文件迭代更新的話,這項(xiàng)配置比較有用)
-dontusemixedcaseclassnames 指定在混淆的時(shí)候不使用大小寫混用的類名。默認(rèn)情況下,混淆后的類名可能同時(shí)包含大寫字母和小寫字母。這樣生成jar包并沒有什么問(wèn)題。只有在大小寫不敏感的系統(tǒng)(例如windows)上解壓時(shí),才會(huì)涉及到這個(gè)問(wèn)題。因?yàn)榇笮懖粎^(qū)分,可能會(huì)導(dǎo)致部分文件在解壓的時(shí)候相互覆蓋。如果有在windows系統(tǒng)上解壓輸出包的需求的話,可以加上這個(gè)配置。
-keeppackagenames [package_filter] 聲明不混淆指定的包名。 配置的過(guò)濾器是逗號(hào)隔開的一組包名。包名可以包含?,,*通配符,并且可以在前面加!否定符。
-flatternpackagehierarchy [packagename] 所有重新命名的包都重新打包,并把所有的類移動(dòng)到packagename包下面。如果沒有指定packagename或者packagename為""
,那么所有的類都會(huì)被移動(dòng)到根目錄下
-repackageclasses [package_name] 所有重新命名過(guò)的類都重新打包,并把他們移動(dòng)到指定的packagename目錄下。如果沒有指定packagename,同樣把他們放到根目錄下面。這項(xiàng)配置會(huì)覆蓋-flatternpackagehierarchy
的配置。它可以代碼體積更小,并且更加難以理解。這個(gè)與廢棄的配置-defaultpackage
作用相同。
如果需要從目錄中讀取資源文件,移動(dòng)包的位置可能會(huì)導(dǎo)致異常。如果出現(xiàn)問(wèn)題,就不要用這個(gè)配置了。
-keepattributes [attribute_filter] 指定受保護(hù)的屬性,可以有一個(gè)或者多個(gè)-keepattributes
配置項(xiàng),每個(gè)配置項(xiàng)后面跟隨的是Java虛擬機(jī)和proguard支持的attribute(具體支持的屬性先看這里),兩個(gè)屬性之間用逗號(hào)分隔。屬性名中可以包含*
,**
,?
等通配符。也可以加!
做前導(dǎo)符,將某個(gè)屬性排除在外。當(dāng)混淆一個(gè)類庫(kù)的時(shí)候,至少要保持InnerClasses
, Exceptions
, Signature
屬性。為了跟蹤異常信息,需要保留SourceFile
, LineNumberTable
兩個(gè)屬性。如果代碼中有用到注解,需要把Annotion
的屬性保留下來(lái)。
例子:
-keepattributes SourceFile, LineNumberTable
-keepattributes *Annotation*
-keepattributes EnclosingMethod
# 可以直接寫在一行
-keepattributes Exceptions, InnerClasses, Signature, Deprecated,
SourceFile, LineNumberTable, *Annotation*, EnclosingMethod
-keepparameternames 指定被保護(hù)的方法的參數(shù)類型和參數(shù)名不被混淆。這項(xiàng)配置在混淆一些類庫(kù)的時(shí)候特別有用,因?yàn)楦鶕?jù)IDE提示的參數(shù)名和參數(shù)類型,開發(fā)者可以根據(jù)他們的語(yǔ)義獲得一些信息,這樣的類庫(kù)更友好。
-renamesourcefileattribute [string] 指定一個(gè)字符串常量設(shè)置到源文件的類的屬性當(dāng)中。這樣就可以在-keepattributes
配置中使用。(這條我理解的也不是很清楚)
-adaptclassstrings [classfilter] 指定字符串常量如果與類名相同,也需要被混淆。如果沒有加classfilter
,所有的符合要求的字符串常量都會(huì)被混淆;如果帶有classfilter
,只有在匹配的類中的字符串常量才會(huì)受此影響。例如,在你的代碼中有大量的類名對(duì)應(yīng)的字符串的hard-code
,并且不想保留他們的本名,那就可以利用這項(xiàng)配置完成。這項(xiàng)配置只在混淆階段有效,但是在壓縮/優(yōu)化階段,涉及到的類會(huì)自動(dòng)保留下來(lái)。
adaptresourcefilenames [file_filter] 如果資源文件與某類名同,那么混淆后資源文件被命名為與之對(duì)應(yīng)的類的混淆名。不加file_filter
的情況下,所有資源文件都受此影響;加了file_filter
的情況下,只有匹配到的類受此影響。
adaptresourcefilecontents [file_filter] 指定資源文件的中的類名隨混淆后的名字更新。根據(jù)被混淆的名字的前后映射關(guān)系,更新文件中對(duì)應(yīng)的包名和類名。同樣,如果不配置file_filter
,所有的資源文件都會(huì)受此影響;配置了filter之后,只有對(duì)應(yīng)的資源文件才受此影響。
文件的讀寫都是按照系統(tǒng)默認(rèn)的字符集。如果有特殊的字符集的需求,可以修改java的執(zhí)行參數(shù),或者直接修改java虛擬機(jī)的配置文件。
注意,這項(xiàng)配置最好只能影響到字符文件。如果影響到一些二進(jìn)制文件會(huì)產(chǎn)生意外影響。所以,設(shè)置
filter
的時(shí)候,要設(shè)置的足夠 '嚴(yán)格'
預(yù)校驗(yàn)配置
-dontpreverify 聲明不預(yù)校驗(yàn)即將執(zhí)行的類。默認(rèn)情況下,在類文件的編譯版本為java micro 版本或者大于1.6版本時(shí),預(yù)校驗(yàn)是開啟的。目標(biāo)文件針對(duì)java6的情況下,預(yù)校驗(yàn)是可選的;針對(duì)java7的情況下,預(yù)校驗(yàn)是必須的,除非目標(biāo)運(yùn)行平臺(tái)是Android平臺(tái),設(shè)置它可以節(jié)省一點(diǎn)點(diǎn)時(shí)間。
目標(biāo)為Java Micro版本的情況下,預(yù)校驗(yàn)是必須的。如果你聲明了這項(xiàng)配置,你還需要加上下面一條配置。
-microedition 聲明目標(biāo)平臺(tái)是java micro版本。預(yù)校驗(yàn)會(huì)根據(jù)這項(xiàng)配置加載合適的StackMap
,而不是用標(biāo)準(zhǔn)的StackMap
。
普通配置
-verbose 聲明在處理過(guò)程中輸出更多信息。添加這項(xiàng)配置之后,如果處理過(guò)程中出現(xiàn)異常,會(huì)輸出整個(gè)StackTrace而不是一條簡(jiǎn)單的異常說(shuō)明。
-dontnote [class_filter] 聲明不輸出那些潛在的錯(cuò)誤和缺失,比如說(shuō)錯(cuò)別字或者重要的配置缺失。配置中的class_filter
是一串正則表達(dá)式,混淆過(guò)程中不會(huì)輸出被匹配到的類相關(guān)的內(nèi)容。
-dontwarn [class_filter] 聲明不輸出那些未找到的引用和一些錯(cuò)誤,但續(xù)混淆。配置中的class_filter是一串正則表達(dá)式,被匹配到的類名相關(guān)的警告都不會(huì)被輸出出來(lái)。
慎用!
-ignorewarnings 輸出所有找不到引用和一些其它錯(cuò)誤的警告,但是繼續(xù)執(zhí)行處理過(guò)程。不處理警告有些危險(xiǎn),所以在清楚配置的具體作用的時(shí)候再使用。
-printconfiguration [filename] 輸出整個(gè)處理過(guò)程中的所有配置參數(shù),包括文件中的參數(shù)和命令行中的參數(shù)。可以不加文件名在標(biāo)準(zhǔn)輸出流中輸出,也可以指定文件名輸出到文件中。它在調(diào)試的時(shí)候比較有用。
-dump [filename] 聲明輸出整個(gè)處理之后的jar文件的類結(jié)構(gòu),可以輸出到標(biāo)準(zhǔn)輸出流或者一個(gè)文件中。
下面這些說(shuō)明對(duì)應(yīng)了之前每個(gè)參數(shù)后面的過(guò)濾器
Class Paths
它對(duì)應(yīng)上文中的所有class_path,他是用來(lái)指定輸入輸出文件的路徑的。它可以有多個(gè)路徑用分隔符隔開。
我們也可以使用過(guò)濾器來(lái)過(guò)濾需要輸出的文件。過(guò)濾器的格式如下:
classpathentry([[[[[[aarfilter;]apkfilter;]zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter)
[]
中包含的內(nèi)容是可選的意思。這樣看有些麻煩,直接上個(gè)例子:
-injars in1.jar
# 輸入文件中排除了META-IF/MANIFEST.MF文件
-injars in2.jar(!META-INF/MANIFEST.MF)
-injars in3.jar(!META-INF/MANIFEST.MF)
-outjars out.jar
# 這個(gè)的意思是只引入`java`,`javax`包中的類
-libraryjars rt.jar(java/**.class,javax/**.class)
File Names
proguard支持絕對(duì)路徑和相對(duì)路徑。相對(duì)路徑按照下面順序被解釋:
- 如果設(shè)置了base directory,首先按照它來(lái)定位;
- 其次,按照配置文件所在的路徑定位;
- 最后,按照工作路徑(working directory,也就是執(zhí)行這條命令的路徑)來(lái)解釋。(這個(gè)不可能沒有!)
名稱中可以包含java系統(tǒng)屬性,用<>
包圍。例如:
-libraryjars <java.home>/lib/rt.jar # 可能代表/usr/local/java/jdk/jre/lib/rt.jar
如果路徑名中帶有特殊字符,可以使用單引號(hào)或者雙引號(hào)括起來(lái)。
File Filters
就像普通的匹配器一樣,可以使用通配符來(lái)過(guò)濾文件名。
- ? 代表文件名中的一個(gè)字符
- * 代表文件名中的一部分,不包括文件分隔符
- ** 代表文件名中的一部分,包括文件分隔符
- ! 放在文件名前面表示將某文件排除在外
Filters
匹配的規(guī)則與File Filters
相似。只是過(guò)濾的范圍更加廣泛。
Keep配置整理
-keep
與-keepnames
的關(guān)系一開始理解的時(shí)候有些混亂。但是它們背后是有一定規(guī)則的,下面的表格展示了它們的聯(lián)系與不同
保留 | 防止被移除或者被重命名 | 防止被重命名 |
---|---|---|
類和類成員 | -keep | -keepnames |
僅類成員 | -keepmembers | -keepmembernames |
如果擁有某成員,保留類和類成員 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果不確定自己該用哪個(gè)的話,就用-keep
吧,它能保證匹配的類在壓縮這一階段不被移除,并且在混淆階段不會(huì)被重新命名。
- 如果只聲明保護(hù)一個(gè)類,并沒有指定受保護(hù)的成員。proguard只會(huì)保護(hù)它的類名和它的無(wú)參構(gòu)造函數(shù)。其它成員依舊會(huì)被壓縮、優(yōu)化、混淆。
- 如果聲明保護(hù)一個(gè)方法,proguard會(huì)把它當(dāng)作程序的入口點(diǎn),方法名不會(huì)變,但它里面的代碼依舊會(huì)被優(yōu)化、混淆。
Keep配置的修飾符
includedescriptorclasses
它是用來(lái)聲明描述目標(biāo)成員的元素也應(yīng)當(dāng)被保護(hù)。它在保護(hù)native方法時(shí)特別有效。因?yàn)樗梢酝瑫r(shí)保證參數(shù)類型,返回類型不被混淆。保證最終的方法簽名保持一致。
例子:
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keepclasseswithmembernames
是保護(hù)符合條件的含有native方法的類。附加的includedescriptorclasses
是保證參數(shù)和返回類型的類同樣不被混淆。這樣就可以做到這些類的方法簽名與調(diào)試時(shí)完全一致。
allowshrinking
修飾-keep
, 聲明一個(gè)元素可以被移除,即使它已經(jīng)聲明了被保護(hù)。意味著它有可能在壓縮階段被刪除,但是它又是必須的入口,所以它有可能不參與優(yōu)化和混淆階段。
(這里我也看不太懂,壓縮階段不是依賴keep聲明的入口節(jié)點(diǎn)嗎?)
allowoptimization
修飾-keep
, 聲明一個(gè)元素可以被優(yōu)化,即使它已經(jīng)聲明被保護(hù)。這意味著該元素參與優(yōu)化階段,但是不參與壓縮和混淆階段。特殊用途的時(shí)候使用。
allowobfuscation
與前幾個(gè)類似,修飾-keep
,只參與混淆階段,但是不參與壓縮和優(yōu)化階段。
類的匹配
Class Specification
是指一個(gè)類和類成員的模板。它一般跟在各種-keep
配置或者assumenosideeffects
配置之后,只有匹配到的類和類成員會(huì)受到影響。
Class Specification
的形式與java的類的形式很像,只是而外加了幾個(gè)通配符。如果想要清晰的看它的形式,最好看例子。但這里還是給出了通用的模型:
[@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 ... ] *;
...
}]
[]
代表可選可不選;···
代表還有更多選項(xiàng)可以配置;|
分隔的部分代表多選一;()
括起來(lái)的部分代表是一個(gè)整體,不能分割。
class 關(guān)鍵字可以匹配class類或interface類,但是
interface
關(guān)鍵字只能匹配interface類,enum
關(guān)鍵字只能匹配enum類。在interface
或enum
關(guān)鍵字前加一個(gè)!
,可以表示非這種類型的類。-
classname 必須寫全名,比如
java.lang.String
。內(nèi)部類用$
間隔,例如,java.lang.Thread$State
。類名可以用含有下面這些通配符的正則表達(dá)式匹配:- ? 匹配類名中的一個(gè)字符,不包括文件分隔符。例如,
mypackage.Test?
可以匹配mypackage.Test1
,mypackage.Test2
,但不能匹配mypackage.Test12
。 - * 匹配類名中的0到多個(gè)字符但不包括文件分隔符。例如,
mypackage.*Test*
可以匹配到mypackage.Test
和mypackage.YourTestApplication
但是不能匹配mypackage.mysubpackage.MyTest
。一種常用的寫法mypackage.*
就是匹配mypackage
下的所有子文件。 - ** 可以匹配類名中的所有字符,可能包含多個(gè)分隔符。例如,
**.Test
就是匹配所有目錄下的Test
類,mypackage.**
就是匹配mypackage及其子目錄下的所有類。
- ? 匹配類名中的一個(gè)字符,不包括文件分隔符。例如,
extend與implements 關(guān)鍵字是用來(lái)限制類的范圍的。他們目前是等價(jià)的,用來(lái)匹配某些類的子類。需要注意的是,這個(gè)指定的類并不包括在匹配結(jié)果中,如果想要該類也被匹配到,就需要額外聲明一項(xiàng)配置。
@ 符號(hào)匹配那些注解標(biāo)志的類或類成員,它的通配符形式與classname的形式一樣。
成員變量和成員方法的匹配形式與java非常像,只是方法的參數(shù)不帶參數(shù)名。此外,他們還可以使用通配符:
變量名和方法名可以使用的通配符:
- <init> 匹配一個(gè)類的所有構(gòu)造函數(shù)
- <fields> 匹配一個(gè)類中的所有成員變量
- <methods> 匹配一個(gè)類中的所有方法
- * 匹配類中的所有成員
列表。- ? 匹配一個(gè)字符
- * 匹配0到多個(gè)字符
注意上述通配符并不能設(shè)置返回類型,并且只有<init>方法帶有參數(shù)
修飾符中可以使用以下通配符匹配:- % 匹配java中的初始類型(int, boolean, long, float,double等)
- ?
- *
- ** (這三個(gè)不解釋了,同上)
- *** 匹配所有類型,包括初始類型和非初始類型,數(shù)組和非數(shù)組。
- ... 匹配任意參數(shù)列表
需要注意的是?, *, **不能夠匹配初始類型和數(shù)組。***可以匹配到數(shù)組。例子:
** get*()
上面的例子可以匹配到java.lang.Object getObject()
,但是不能匹配float getFloat()
或者java.lang.Object[] getObjects()
。
構(gòu)造函數(shù)也可以使用簡(jiǎn)單類名或全類名來(lái)指定。就像java中的構(gòu)造函數(shù)一樣有參數(shù)列表但是沒有返回類型。
類或者類成員的修飾符也是匹配類的限制條件。通過(guò)修飾符限制,可以縮小匹配的范圍。修飾符組合也是可以的,就像java中的
public static
一樣,但是不能沖突, 比如public private
。
混淆相關(guān)的點(diǎn)就這些了,下面的例子中是Android應(yīng)用混淆的默認(rèn)文件。它放在{android_sdk_home}/tools/proguard/proguard-android.txt
文件中,其它的可以參考的例子在{android_sdk_home}/tools/proguard/examples/
目錄下。
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
native <methods>;
}
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
本人也是初步了解proguard,有什么理解不對(duì)的地方還大家多多指教。