混淆(Proguard)用法
最近項目中遇到一些混淆相關的問題,由于之前對proguard了解不多,所以每次都是面向Stackoverflow的編程。copy別人的答案內心還可以接受睁蕾,但是copy了之后不懂別人的邏輯是無法忍受的帘皿。首先不清楚別人的答案是不是一定符合自己的需求;其次案疲,再遇到同類問題還是得抓瞎封恰。于是下決心看了一下proguard的官方文檔。很長褐啡,但是很詳細诺舔,在這里整理一下筆記,分享給大家备畦。
介紹
我們通常說的proguard
包括四個功能低飒,shrinker
(壓縮), optimizer
(優(yōu)化),obfuscator
(混淆),preverifier
(預校驗)。
- shrink: 檢測并移除沒有用到的類懂盐,變量褥赊,方法和屬性;
- optimize: 優(yōu)化代碼莉恼,非入口節(jié)點類會加上
private
/static
/final
, 沒有用到的參數會被刪除拌喉,一些方法可能會變成內聯代碼。 - obfuscate: 使用短又沒有語義的名字重命名非入口類的類名俐银,變量名尿背,方法名。入口類的名字保持不變捶惜。
- preverify: 預校驗代碼是否符合Java1.6或者更高的規(guī)范(唯一一個與入口類不相關的步驟)
如果你的代碼中用到了反射田藐,那需要把反射調用的類,變量,方法也設置成入口節(jié)點汽久。只需要加上
-keep
就可以了鹤竭。(下面會講)
除了proguard之外,還有一個DexGuard景醇,是專門用來優(yōu)化混淆Android應用的臀稚。它的功能包括資源混淆,字符串加密啡直,類加密和dex文件分割等烁涌。它是在android編譯的時候直接產生Dalvik字節(jié)碼。(這個有興趣的同學自行了解酒觅,這里不詳述了)
用法
要執(zhí)行proguard,可以直接執(zhí)行命令:
java -jar proguard.jar options ...
如果有Android SDK的同學可以在
{ANDROID_SDK_ROOT}/tools/proguard/lib/
目錄下找到proguard.jar
這個jar包撮执。或者舷丹,也可以在{ANDROID_SDK_ROOT}/tools/proguard/bin
目錄下直接使用腳本執(zhí)行命令抒钱。
我們也可以把proguard的參數寫到一個配置文件中,比如說proguard.cfg
颜凯。那我們的命令可以這樣寫:
java -jar proguard.jar @proguard.cfg
這個文件也就是我們在Android Studio中經常配置的混淆文件了谋币。我們在編譯正式包的時候打包腳本自動幫我們執(zhí)行了這條命令。通過這個腳本可以避免重復輸入參數症概。
當然蕾额,我們也可以配置文件與命令行參數混用,例如:
java -jar proguard.jar @proguard.cfg -verbose
- 配置文件中
#
放在行首彼城,用來做注釋诅蝶; - 單詞之間多余的空格或分隔符會被忽略;
- 如果文件名包含空格或者其它特殊符號募壕,應當用單引號或者雙引號括起來调炬;
- 配置參數的順序與混淆結果是沒有關系的
輸入/輸出選項
這部分內容平常比較少用到,如果僅僅是做app開發(fā)的話舱馅,了解一下這一節(jié)即可缰泡。
@filename -include 的簡寫
-include filename 需要讀取的配置文件
-basedirectory directory 為所有引用的相對路徑指定一個根路徑
-injars classpath 指定輸入的包,可以包括 jar, aar, war, ear, zip, apk或者文件目錄代嗤。這些包或者目錄下的class文件將被處理后寫入到輸出文件中棘钞。默認情況下非class文件會被原封不動的復制到輸出文件中。
需要注意的是干毅,默認情況下宜猜,一些編譯器的臨時的文件也會被寫入到輸出文件中∪芏В可以使用過濾選項過濾掉他們。過濾選選項后面有講到符隙。
-outjars classpath 指定輸出文件趴捅,類型包括 jar, aar, war, ear, zip, apk和 目錄垫毙。
不要讓輸出文件覆蓋任何一個輸入文件!
-libraryjars classpaath 指定輸入文件引用的類庫拱绑。這些類庫不會被寫入到輸出文件中综芥。每個庫至少要有一個類被引用。
在查找類庫的時候猎拨,proguard運行時的類庫是不算在內的膀藐。需要指明的是應用在運行時依賴的類庫。
簡單用法:
-injars classes
-injars in1.jar
-injars in2.jar
-injars in3.jar
-libraryjars <java.home>/lib/rt.jar(java/**,javax/**)
-outjars out.jar
-skipnonpubliclibraryclasses 指定讀取引用庫文件的時候跳過非public類红省。這樣做可以提高處理速度并節(jié)省內存额各。一般情況下非public在應用內是引用不到的,跳過它們也沒什么關系吧恃。但是虾啦,在一些java類庫中中出現了public類繼承非public類的情況,這樣就不能用這個選項了痕寓。這種情況下傲醉,會打印一個警告出來,提示找不到類呻率。
-dontskipnonpubliclibraryclasses 跟上面的參數相對硬毕。版本4.5以上,這個是默認的選項礼仗。
-dontskipnonpubliclibraryclassmembers 指定不忽略庫類庫中的非public成員(成員變量和方法)吐咳。默認情況下,proguard在讀取庫文件的時候會自動忽略這些類的成員藐守,因為這些非public成員不會被源代碼引用到挪丢。但有時候他們是可以被引用到的。比如說卢厂,源代碼中與庫文件用同一個包名乾蓬,那么源代碼就可以訪問包作用域的變量。在這些情況下慎恒,為了引用一致任内,不被混淆,就需要指定不跳過這些類融柬。
-keepdirectories directory_filter 指定輸出jar包中需要保留的目錄名死嗦。為了減少輸出文件的體積,默認情況下所有的目錄都會被刪除(個人這里不是很理解粒氧,猜測意思是默認所有目錄都會被混淆吧)越除。但是如果你的代碼中有需要從目錄中尋找文件的邏輯,那你就需要保持目錄名一致。這項配置后面不加過濾器的時候摘盆,所有目錄都會被保留翼雀。加了過濾器之后,只有過濾器匹配的目錄才會被保留孩擂。
-target version 指定處理的class文件中java的目標版本狼渊。版本號是1.0, 1.1, 1.2, 1.3, 1.4, 1.5(或者5), 1.6(或者6), 1.7(或者7),1.8(或者8)之中的一個类垦。默認情況下狈邑,class文件的版本號是不會變的。
-forceprocessing 盡管輸出文件已經是最新的蚤认,還是強制進行處理一次米苹。
Keep配置
-keep [,modifier, ...] class_specification 指定類和類的成員變量是入口節(jié)點,保護它們不被移除混淆烙懦。例如驱入,對一個可執(zhí)行jar包來說,需要保護main的入口類氯析;對一個類庫來說需要保護它的所有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 保護的指定的成員變量不被移除、優(yōu)化掩缓、混淆雪情。例如,保護所有序列化的類的成員變量你辣。
例子:
-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 擁有指定成員的類將被保護巡通,根據類成員確定一些將要被保護的類。例如保護所有含有main方法的類舍哄。
例子:
# 這種情況下含有main方法的類和mainf方法都不會被混淆宴凉。
-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
的簡寫。指定一些類名受到保護表悬,前提是他們在shrink這一階段沒有被去掉弥锄。也就是說沒有被入口節(jié)點直接或間接引用的類還是會被刪除。僅在obfuscate
階段有效蟆沫。
-keepclassmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification
的簡寫籽暇。與-keepclassmember
相似。保護指定的類成員饭庞,前提是這些成員在shrink
階段沒有被刪除戒悠。僅在obfuscate
階段有效。
-keepclasseswithmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification
的簡寫舟山。與-keepclasseswithmembers
類似绸狐。保護指定的類卤恳,如果它們沒有在shrink
階段被刪除。僅在obfuscate
階段有效寒矿。
-printseeds [filename] 指定通過-keep
配置匹配的類或者類成員的詳細列表纬黎。列表可以打印到標準輸出流或者文件里面。這個列表可以看到我們想要保護的類或者成員有沒有被真正的保護到劫窒,尤其是那些使用通配符匹配的類。
代碼壓縮配置
-dontshrink 聲明不壓縮輸入文件拆座。默認情況下主巍,除了-keep
相關配置指定的類,所有其它沒有被引用到的類都會被刪除挪凑。每次optimizate
操作之后孕索,也會執(zhí)行一次壓縮操作,因為每次optimizate
操作可能刪除一部分不再需要的類躏碳。
-printusage [filename] 聲明 打印出那些被刪除的元素搞旭。這個列表可能打印到標準輸出流或者一個文件中。僅在shrink
階段有效菇绵。
whyareyoukeeping class_specification 聲明 打印為什么一個類或類的成員變量被保護肄渗。這對檢查一個輸出文件中的類的結果有幫助。
代碼優(yōu)化配置
-dontoptimize 聲明不優(yōu)化輸入文件咬最。默認情況下翎嫡,優(yōu)化選項是開啟的,并且所有的優(yōu)化都是在字節(jié)碼層進行的永乌。
-optimizations optimization_filter 更加細粒度地聲明優(yōu)化開啟或者關閉惑申。只在optimize
這一階段有效。這個選項的使用難度較高翅雏。
-optimizationpasses n 指定執(zhí)行幾次優(yōu)化圈驼,默認情況下,只執(zhí)行一次優(yōu)化望几。執(zhí)行多次優(yōu)化可以提高優(yōu)化的效果绩脆,但是,如果執(zhí)行過一次優(yōu)化之后沒有效果橄妆,就會停止優(yōu)化衙伶,剩下的設置次數不再執(zhí)行。這個選項只在optimizate
階段有效
assumenosideeffects class_specification 指定一些方法被刪除也沒有影響(盡管這些方法可能有返回值)害碾,在optimize
階段矢劲,如果確定這些方法的返回值沒有使用,那么就會刪除這些方法的調用慌随。proguard會自動的分析你的代碼芬沉,但不會分析處理類庫中的代碼躺同。例如,可以指定System.currentTimeMillis()
,這樣在optimize
階段就會刪除所有的它的調用丸逸。還可以用它來刪除打印Log的調用蹋艺。這條配置選項只在optimizate
階段有用。
例子:
# 刪除代碼中Log相關的代碼
-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(...);
}
使用這條配置有點危險黄刚,如果刪除了一些預料之外的代碼捎谨,很容易就會導致代碼崩潰。所以憔维,謹慎使用
-allowaccessmodification 這項配置是設置是否允許改變作用域的涛救。使用這項配置之后可以提高優(yōu)化的效果。**但是业扒,如果你的代碼是一個庫的話检吆,最好不要配置這個選項,因為它可能會導致一些private
變量被改成public
程储。
-mergeinterfacesaggressively 指定一些接口可能被合并蹭沛,即使一些子類沒有同時實現兩個接口的方法。這種情況在java源碼中是不允許存在的章鲤,但是在java字節(jié)碼中是允許存在的摊灭。它的作用是通過合并接口減少類的數量,從而達到減少輸出文件體積的效果败徊。僅在optimize
階段有效斟或。
這項配置對于一些虛擬機的65535方法數限制是有一定效果的。
混淆配置
-dontobfuscate 聲明不混淆集嵌。默認情況下萝挤,混淆是開啟的。除了keep
配置中聲明的類根欧,其它的類或者類的成員混淆后會改成簡短隨機的名字怜珍。
-printmapping [filename] 指定輸出新舊元素名的對照表的文件。映射表會被輸出到標準輸出流或者是一個指定的文件凤粗。
這個文件在追蹤異常的時候是有用的酥泛,在
{android_sdk_home}/tools/proguard/lib
目錄下有一個retrace.jar
文件。我們可以把混淆后的Stack Trace用這個工具處理一下嫌拣,就會轉變成容易閱讀的類柔袁。所以,做app應用的同學每次發(fā)版本的時候都要把這個文件留下來异逐,并標記清楚版本捶索。這對線上版本的調試非常重要。
-applymapping filename 指定重用一個已經寫好了的map文件作為新舊元素名的映射灰瞻。元素名已經存在在mapping文件中的元素腥例,按照映射表重命名辅甥;沒有存在到mapping文件的元素,重新賦一個新的名字燎竖。mapping文件可能引用到輸入文件中的類和類庫中的類璃弄。這里只允許設置一個mapping文件。僅在obfuscate
階段有效构回。
-obfuscationdictionary filename 指定一個文本文件用來生成混淆后的名字夏块。默認情況下,混淆后的名字一般為a,b纤掸,c這種拨扶。通過使用-obfuscationdictionary
配置的字典文件,可以使用一些非英文字符做為類名茁肠。成員變量名、方法名缩举。字典文件中的空格垦梆,標點符號,重復的詞仅孩,還有以'#'
開頭的行都會被忽略托猩。需要注意的是添加了字典并不會顯著提高混淆的效果,只不過是更不利與人類的閱讀辽慕。正常的編譯器會自動處理他們京腥,并且輸出出來的jar包也可以輕易的換個字典再重新混淆一次。最有用的做法一般是選擇已經在類文件中存在的字符串做字典溅蛉,這樣可以稍微壓縮包的體積公浪。
查找了字典文件的格式:一行一個單詞,空行忽略船侧,重復忽略
例如:
# 這里巧妙地使用java中的關鍵字作字典欠气,混淆之后的代碼更加不利于閱讀
#
# 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 指定一個混淆類名的字典,字典的格式與-obfuscationdictionary
相同
-packageobfuscationdictionary filename 指定一個混淆包名的字典镜撩,字典格式與-obfuscationdictionary
相同
-overloadaggressively 混淆的時候大量使用重載预柒,多個方法名使用同一個混淆名,但是他們的方法簽名不同袁梗。這可以使包的體積減小一部分宜鸯,也可以加大理解的難度。僅在混淆階段有效遮怜。
注意淋袖,這項配置有一定的限制:
Sun的JDK1.2上會報異常
Sun JRE 1.4上重載一些方法之后會導致序列化失敗
Sun JRE 1.5上pack200 tool重載一些類之后會報錯
java.lang.reflect.Proxy類不能處理重載的方法
Google's Dalvik VM can't handle overloaded static fields(這句我不懂,重載靜態(tài)變量是什么意思锯梁?有看懂的同學可以回復一下)
-useuniqueclassmembernames 指定相同的混淆名對應相同的方法名适贸,不同的混淆名對應不同的方法名灸芳。如果不設置這個選項,同一個類中將會有很多方法映射到相同的方法名拜姿。這項配置會稍微增加輸出文件中的代碼烙样,但是它能夠保證保存下來的mapping文件能夠在隨后的增量混淆中繼續(xù)被遵守,避免重新命名蕊肥。比如說谒获,兩個接口擁有同名方法和相同的簽名。如果沒有這個配置壁却,在第一次打包混淆之后批狱,他們兩個方法可能會被賦予不同的混淆名。如果說下一次添加代碼的時候有一個類同時實現了兩個接口展东,那么混淆的時候必然會將兩個混淆后的方法名統(tǒng)一起來赔硫。這樣就必須要改混淆文件其中一處的配置,也就不能保證前后兩次混淆的mapping文件一致了盐肃。(如果你只想保留一份mapping文件迭代更新的話爪膊,這項配置比較有用)
-dontusemixedcaseclassnames 指定在混淆的時候不使用大小寫混用的類名。默認情況下砸王,混淆后的類名可能同時包含大寫字母和小寫字母推盛。這樣生成jar包并沒有什么問題。只有在大小寫不敏感的系統(tǒng)(例如windows)上解壓時谦铃,才會涉及到這個問題耘成。因為大小寫不區(qū)分,可能會導致部分文件在解壓的時候相互覆蓋驹闰。如果有在windows系統(tǒng)上解壓輸出包的需求的話瘪菌,可以加上這個配置。
-keeppackagenames [package_filter] 聲明不混淆指定的包名嘹朗。 配置的過濾器是逗號隔開的一組包名控嗜。包名可以包含?,,*通配符,并且可以在前面加!否定符骡显。
-flatternpackagehierarchy [packagename] 所有重新命名的包都重新打包疆栏,并把所有的類移動到packagename包下面。如果沒有指定packagename或者packagename為""
惫谤,那么所有的類都會被移動到根目錄下
-repackageclasses [package_name] 所有重新命名過的類都重新打包壁顶,并把他們移動到指定的packagename目錄下。如果沒有指定packagename溜歪,同樣把他們放到根目錄下面若专。這項配置會覆蓋-flatternpackagehierarchy
的配置。它可以代碼體積更小蝴猪,并且更加難以理解调衰。這個與廢棄的配置-defaultpackage
作用相同膊爪。
如果需要從目錄中讀取資源文件,移動包的位置可能會導致異常嚎莉。如果出現問題米酬,就不要用這個配置了。
-keepattributes [attribute_filter] 指定受保護的屬性趋箩,可以有一個或者多個-keepattributes
配置項赃额,每個配置項后面跟隨的是Java虛擬機和proguard支持的attribute(具體支持的屬性先看這里),兩個屬性之間用逗號分隔叫确。屬性名中可以包含*
,**
,?
等通配符跳芳。也可以加!
做前導符,將某個屬性排除在外竹勉。當混淆一個類庫的時候荷逞,至少要保持InnerClasses
, Exceptions
, Signature
屬性跃闹。為了跟蹤異常信息役听,需要保留SourceFile
, LineNumberTable
兩個屬性睦刃。如果代碼中有用到注解,需要把Annotion
的屬性保留下來檬输。
例子:
-keepattributes SourceFile, LineNumberTable
-keepattributes *Annotation*
-keepattributes EnclosingMethod
# 可以直接寫在一行
-keepattributes Exceptions, InnerClasses, Signature, Deprecated,
SourceFile, LineNumberTable, *Annotation*, EnclosingMethod
-keepparameternames 指定被保護的方法的參數類型和參數名不被混淆。這項配置在混淆一些類庫的時候特別有用匈棘,因為根據IDE提示的參數名和參數類型丧慈,開發(fā)者可以根據他們的語義獲得一些信息,這樣的類庫更友好主卫。
-renamesourcefileattribute [string] 指定一個字符串常量設置到源文件的類的屬性當中逃默。這樣就可以在-keepattributes
配置中使用。(這條我理解的也不是很清楚)
-adaptclassstrings [classfilter] 指定字符串常量如果與類名相同簇搅,也需要被混淆完域。如果沒有加classfilter
,所有的符合要求的字符串常量都會被混淆瘩将;如果帶有classfilter
吟税,只有在匹配的類中的字符串常量才會受此影響。例如姿现,在你的代碼中有大量的類名對應的字符串的hard-code
,并且不想保留他們的本名肠仪,那就可以利用這項配置完成。這項配置只在混淆階段有效备典,但是在壓縮/優(yōu)化階段异旧,涉及到的類會自動保留下來。
adaptresourcefilenames [file_filter] 如果資源文件與某類名同提佣,那么混淆后資源文件被命名為與之對應的類的混淆名吮蛹。不加file_filter
的情況下荤崇,所有資源文件都受此影響;加了file_filter
的情況下潮针,只有匹配到的類受此影響术荤。
adaptresourcefilecontents [file_filter] 指定資源文件的中的類名隨混淆后的名字更新。根據被混淆的名字的前后映射關系然低,更新文件中對應的包名和類名喜每。同樣,如果不配置file_filter
雳攘,所有的資源文件都會受此影響带兜;配置了filter之后,只有對應的資源文件才受此影響吨灭。
文件的讀寫都是按照系統(tǒng)默認的字符集刚照。如果有特殊的字符集的需求,可以修改java的執(zhí)行參數喧兄,或者直接修改java虛擬機的配置文件无畔。
注意,這項配置最好只能影響到字符文件吠冤。如果影響到一些二進制文件會產生意外影響浑彰。所以,設置
filter
的時候拯辙,要設置的足夠 '嚴格'
預校驗配置
-dontpreverify 聲明不預校驗即將執(zhí)行的類郭变。默認情況下,在類文件的編譯版本為java micro 版本或者大于1.6版本時涯保,預校驗是開啟的诉濒。目標文件針對java6的情況下,預校驗是可選的夕春;針對java7的情況下未荒,預校驗是必須的,除非目標運行平臺是Android平臺及志,設置它可以節(jié)省一點點時間片排。
目標為Java Micro版本的情況下,預校驗是必須的速侈。如果你聲明了這項配置划纽,你還需要加上下面一條配置。
-microedition 聲明目標平臺是java micro版本锌畸。預校驗會根據這項配置加載合適的StackMap
勇劣,而不是用標準的StackMap
。
普通配置
-verbose 聲明在處理過程中輸出更多信息。添加這項配置之后比默,如果處理過程中出現異常幻捏,會輸出整個StackTrace而不是一條簡單的異常說明。
-dontnote [class_filter] 聲明不輸出那些潛在的錯誤和缺失命咐,比如說錯別字或者重要的配置缺失篡九。配置中的class_filter
是一串正則表達式,混淆過程中不會輸出被匹配到的類相關的內容醋奠。
-dontwarn [class_filter] 聲明不輸出那些未找到的引用和一些錯誤榛臼,但續(xù)混淆。配置中的class_filter是一串正則表達式窜司,被匹配到的類名相關的警告都不會被輸出出來沛善。
慎用!
-ignorewarnings 輸出所有找不到引用和一些其它錯誤的警告塞祈,但是繼續(xù)執(zhí)行處理過程金刁。不處理警告有些危險,所以在清楚配置的具體作用的時候再使用议薪。
-printconfiguration [filename] 輸出整個處理過程中的所有配置參數尤蛮,包括文件中的參數和命令行中的參數∷挂椋可以不加文件名在標準輸出流中輸出产捞,也可以指定文件名輸出到文件中。它在調試的時候比較有用哼御。
-dump [filename] 聲明輸出整個處理之后的jar文件的類結構坯临,可以輸出到標準輸出流或者一個文件中。
下面這些說明對應了之前每個參數后面的過濾器
Class Paths
它對應上文中的所有class_path艇搀,他是用來指定輸入輸出文件的路徑的尿扯。它可以有多個路徑用分隔符隔開求晶。
我們也可以使用過濾器來過濾需要輸出的文件焰雕。過濾器的格式如下:
classpathentry([[[[[[aarfilter;]apkfilter;]zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter)
[]
中包含的內容是可選的意思。這樣看有些麻煩芳杏,直接上個例子:
-injars in1.jar
# 輸入文件中排除了META-IF/MANIFEST.MF文件
-injars in2.jar(!META-INF/MANIFEST.MF)
-injars in3.jar(!META-INF/MANIFEST.MF)
-outjars out.jar
# 這個的意思是只引入`java`,`javax`包中的類
-libraryjars rt.jar(java/**.class,javax/**.class)
File Names
proguard支持絕對路徑和相對路徑矩屁。相對路徑按照下面順序被解釋:
- 如果設置了base directory,首先按照它來定位爵赵;
- 其次吝秕,按照配置文件所在的路徑定位;
- 最后空幻,按照工作路徑(working directory烁峭,也就是執(zhí)行這條命令的路徑)來解釋。(這個不可能沒有!)
名稱中可以包含java系統(tǒng)屬性约郁,用<>
包圍缩挑。例如:
-libraryjars <java.home>/lib/rt.jar # 可能代表/usr/local/java/jdk/jre/lib/rt.jar
如果路徑名中帶有特殊字符,可以使用單引號或者雙引號括起來鬓梅。
File Filters
就像普通的匹配器一樣供置,可以使用通配符來過濾文件名。
- ? 代表文件名中的一個字符
- * 代表文件名中的一部分,不包括文件分隔符
- ** 代表文件名中的一部分绽快,包括文件分隔符
- ! 放在文件名前面表示將某文件排除在外
Filters
匹配的規(guī)則與File Filters
相似芥丧。只是過濾的范圍更加廣泛。
Keep配置整理
-keep
與-keepnames
的關系一開始理解的時候有些混亂坊罢。但是它們背后是有一定規(guī)則的续担,下面的表格展示了它們的聯系與不同
保留 | 防止被移除或者被重命名 | 防止被重命名 |
---|---|---|
類和類成員 | -keep | -keepnames |
僅類成員 | -keepmembers | -keepmembernames |
如果擁有某成員,保留類和類成員 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果不確定自己該用哪個的話艘绍,就用-keep
吧赤拒,它能保證匹配的類在壓縮這一階段不被移除,并且在混淆階段不會被重新命名诱鞠。
- 如果只聲明保護一個類挎挖,并沒有指定受保護的成員。proguard只會保護它的類名和它的無參構造函數航夺。其它成員依舊會被壓縮蕉朵、優(yōu)化、混淆阳掐。
- 如果聲明保護一個方法始衅,proguard會把它當作程序的入口點,方法名不會變缭保,但它里面的代碼依舊會被優(yōu)化汛闸、混淆。
Keep配置的修飾符
includedescriptorclasses
它是用來聲明描述目標成員的元素也應當被保護艺骂。它在保護native方法時特別有效诸老。因為它可以同時保證參數類型,返回類型不被混淆钳恕。保證最終的方法簽名保持一致别伏。
例子:
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keepclasseswithmembernames
是保護符合條件的含有native方法的類。附加的includedescriptorclasses
是保證參數和返回類型的類同樣不被混淆忧额。這樣就可以做到這些類的方法簽名與調試時完全一致厘肮。
allowshrinking
修飾-keep
, 聲明一個元素可以被移除,即使它已經聲明了被保護睦番。意味著它有可能在壓縮階段被刪除类茂,但是它又是必須的入口,所以它有可能不參與優(yōu)化和混淆階段。
(這里我也看不太懂巩检,壓縮階段不是依賴keep聲明的入口節(jié)點嗎恬涧?)
allowoptimization
修飾-keep
, 聲明一個元素可以被優(yōu)化,即使它已經聲明被保護碴巾。這意味著該元素參與優(yōu)化階段溯捆,但是不參與壓縮和混淆階段。特殊用途的時候使用厦瓢。
allowobfuscation
與前幾個類似提揍,修飾-keep
,只參與混淆階段,但是不參與壓縮和優(yōu)化階段煮仇。
類的匹配
Class Specification
是指一個類和類成員的模板劳跃。它一般跟在各種-keep
配置或者assumenosideeffects
配置之后,只有匹配到的類和類成員會受到影響浙垫。
Class Specification
的形式與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 關鍵字可以匹配class類或interface類,但是
interface
關鍵字只能匹配interface類祈搜,enum
關鍵字只能匹配enum類。在interface
或enum
關鍵字前加一個!
士八,可以表示非這種類型的類容燕。-
classname 必須寫全名,比如
java.lang.String
婚度。內部類用$
間隔蘸秘,例如,java.lang.Thread$State
陕见。類名可以用含有下面這些通配符的正則表達式匹配:- ? 匹配類名中的一個字符秘血,不包括文件分隔符味抖。例如评甜,
mypackage.Test?
可以匹配mypackage.Test1
,mypackage.Test2
,但不能匹配mypackage.Test12
仔涩。 - * 匹配類名中的0到多個字符但不包括文件分隔符忍坷。例如,
mypackage.*Test*
可以匹配到mypackage.Test
和mypackage.YourTestApplication
但是不能匹配mypackage.mysubpackage.MyTest
。一種常用的寫法mypackage.*
就是匹配mypackage
下的所有子文件佩研。 - ** 可以匹配類名中的所有字符柑肴,可能包含多個分隔符。例如旬薯,
**.Test
就是匹配所有目錄下的Test
類晰骑,mypackage.**
就是匹配mypackage及其子目錄下的所有類。
- ? 匹配類名中的一個字符秘血,不包括文件分隔符味抖。例如评甜,
extend與implements 關鍵字是用來限制類的范圍的绊序。他們目前是等價的硕舆,用來匹配某些類的子類。需要注意的是骤公,這個指定的類并不包括在匹配結果中抚官,如果想要該類也被匹配到,就需要額外聲明一項配置阶捆。
@ 符號匹配那些注解標志的類或類成員凌节,它的通配符形式與classname的形式一樣。
成員變量和成員方法的匹配形式與java非常像洒试,只是方法的參數不帶參數名倍奢。此外,他們還可以使用通配符:
變量名和方法名可以使用的通配符:
- <init> 匹配一個類的所有構造函數
- <fields> 匹配一個類中的所有成員變量
- <methods> 匹配一個類中的所有方法
- * 匹配類中的所有成員
列表垒棋。- ? 匹配一個字符
- * 匹配0到多個字符
注意上述通配符并不能設置返回類型娱挨,并且只有<init>方法帶有參數
修飾符中可以使用以下通配符匹配:- % 匹配java中的初始類型(int, boolean, long, float,double等)
- ?
- *
- ** (這三個不解釋了,同上)
- *** 匹配所有類型捕犬,包括初始類型和非初始類型跷坝,數組和非數組。
- ... 匹配任意參數列表
需要注意的是?, *, **不能夠匹配初始類型和數組碉碉。***可以匹配到數組柴钻。例子:
** get*()
上面的例子可以匹配到java.lang.Object getObject()
,但是不能匹配float getFloat()
或者java.lang.Object[] getObjects()
。
構造函數也可以使用簡單類名或全類名來指定垢粮。就像java中的構造函數一樣有參數列表但是沒有返回類型贴届。
類或者類成員的修飾符也是匹配類的限制條件。通過修飾符限制蜡吧,可以縮小匹配的范圍毫蚓。修飾符組合也是可以的,就像java中的
public static
一樣昔善,但是不能沖突, 比如public private
元潘。
混淆相關的點就這些了,下面的例子中是Android應用混淆的默認文件君仆。它放在{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,有什么理解不對的地方還大家多多指教钥庇。