入口
為了決定哪些代碼要被保留哪些代碼要出丟棄和混淆,必須指定入口點(diǎn)。這些入口點(diǎn)通常是 main方法,activity,service等。
在壓縮階段,Proguard從這些入口點(diǎn)開始遞歸確定哪些類或類成員要被使用,其余的都會(huì)被丟棄。
在優(yōu)化階段,ProGuard 會(huì)進(jìn)一步優(yōu)化代碼。在其他優(yōu)化中,可以將不是入口點(diǎn)的類和方法設(shè)為 private,static 或 final ,刪除未使用的參數(shù),并且可以內(nèi)聯(lián)一些方法。
在混淆階段,ProGuard 會(huì)重新命名不屬于入口點(diǎn)的類和類成員。在整個(gè)過(guò)程中,保證入口點(diǎn)仍然可以通過(guò)其原始名稱訪問。
查看 Proguard 輸出結(jié)果
為了避免引入 bug 我們有必要對(duì) 結(jié)果進(jìn)行檢查。
在Android中,開啟了混淆構(gòu)建會(huì)在 <module-name>/build/outputs/mapping/ 目錄下會(huì)輸出以下文件:
dump.txt 描述APK文件中所有類的內(nèi)部結(jié)構(gòu)
mapping.txt 提供混淆前后類、方法、類成員等的對(duì)照表
seeds.txt 列出沒有被混淆的類和成員
-
usage.txt 列出被移除的代碼
?
我們可以根據(jù) seeds.txt 文件檢查未被混淆的類和成員中是否已包含所有期望保留的,再根據(jù) usage.txt 文件查看是否有被誤移除的代碼。
過(guò)濾器
ProGuard 為許多配置提供了不同方面的過(guò)濾選項(xiàng):文件名稱,目錄,類別,軟件包,屬性,優(yōu)化等。
過(guò)濾器是可以包含通配符的,以逗號(hào)分隔的,名稱列表。
只有與列表中的項(xiàng)目匹配的名稱才會(huì)通過(guò)過(guò)濾器。
每種配置的通配符可能有所不同,但以下通配符是通用的:
? 匹配名稱中的任何單個(gè)字符。
* 匹配不包含包分隔符或目錄分隔符的名稱的任何部分
** 匹配名稱的任何部分,可能包含任意數(shù)量的包分隔符或目錄分隔符。
此外,名稱前可以加上否定感嘆號(hào) ! 排除名稱與進(jìn)一步嘗試匹配后續(xù)名稱。
因此,如果名稱與過(guò)濾器中的某個(gè)項(xiàng)目相匹配,則會(huì)立即接受或拒絕該項(xiàng)目,具體取決于項(xiàng)目是否具有否定符。
如果名稱與項(xiàng)目不匹配,則會(huì)針對(duì)下一個(gè)項(xiàng)目進(jìn)行測(cè)試,依此類推。
它如果與任何項(xiàng)目不匹配,則根據(jù)最后一項(xiàng)是否具有否定符而被接受或拒絕。
如,"!foobar,*.bar" 匹配除了foobar之外的所有以bar結(jié)尾的名稱。
下面以過(guò)濾文件具體舉例。
文件過(guò)濾器
像通用過(guò)濾器一樣,文件過(guò)濾器是逗號(hào)分隔的文件名列表,可以包含通配符。只有具有匹配文件名的文件被讀?。ㄔ谳斎氲那闆r下),或者被寫入(在輸出的情況下)。支持以下通配符:
? 匹配文件名字中的任何單個(gè)字符
* 匹配不包含目錄分隔符的文件名的任何部分。
** 匹配文件名的任何部分,可以包含任意數(shù)目的目錄分隔符。
例如 "java/**.class ,javax/**.class" 可以匹配 java和javax目錄下所有的 class 文件。
此外,文件名前面可能帶有感嘆號(hào)'!'將文件名排除在與后續(xù)文件名匹配上。
例如 "!**.gif,images/**" 匹配images目錄下所有除了 gif 的文件
關(guān)于更詳細(xì)的用法 可以查看官方文檔 filtering
keep 選項(xiàng)
-keep [,modifier,...] class specification
指定類和類成員(字段,方法)作為入口點(diǎn)被保留。
例如,為了保留一個(gè)程序,你要指定Main方法和類。為了保留一個(gè)庫(kù),你應(yīng)該指定所有被公開訪問的元素。
- 保留 main 類和 main 方法
-keep public class com.example.MyMain {
public static void main(java.lang.String[]);
}
- 保留所有被公開訪問的元素
-keep public class * {
public protected *;
}
Note:如果你只保留了類,沒有保留類成員,那么你的類成員將不會(huì)被保留
例如 有一個(gè)實(shí)體類
public class Product implements Serializable {
public static final int A = 1;
public static final int B = 2;
private String name;
private String url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
規(guī)則配置如下
# 保留 Product類
-keep class cn.sintoon.camera.Product
usage.txt文件中有以下內(nèi)容 ,可以看到 類中的成員全部被移除了
cn.sintoon.camera.Product:
public static final int A
public static final int B
private java.lang.String name
private java.lang.String url
16:16:public java.lang.String getName()
20:21:public void setName(java.lang.String)
24:24:public java.lang.String getUrl()
28:29:public void setUrl(java.lang.String)
-keepclassmembers [,modifier,...] class specification
指定要保留的類成員,前提是它們的類也被保留了。
例如,你想保留實(shí)現(xiàn)了 Serializable 接口的類中的所有 serializable 方法和字段。
-keepclassmembers class * implements java.io.Serializable {
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
Note: 注意字段類型帶上包名; String 類型為 java.lang.String;另外,如果只保留了類成員沒有保留類跟沒有保留一樣
還是拿上面那個(gè)例子,改一下規(guī)則
-keepclassmembers class * implements java.io.Serializable{
private String name;
public String getName();
public static final int A;
}
再看 usage.txt 類都被移除了,保留字段沒毛線用。
cn.sintoon.camera.Product
-keepclasseswithmembers [,modifier,...] class specification
指定要保留的類和類成員,條件是所有指定的類成員都在。
例如,你要保留程序中所有的主程序,不用顯示的列出。
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
還是用上面那個(gè)例子,保留住類和所有的類成員
-keepclasseswithmembers class cn.sintoon.camera.Product{
public static final int A;
public static final int B;
private java.lang.String name;
private java.lang.String url;
public java.lang.String getName();
public void setName(java.lang.String);
public java.lang.String getUrl();
public void setUrl(java.lang.String);
}
看 seeds.text 中就會(huì)出現(xiàn)這個(gè)類和類成員
cn.sintoon.camera.Product
cn.sintoon.camera.Product: int A
cn.sintoon.camera.Product: int B
cn.sintoon.camera.Product: java.lang.String name
cn.sintoon.camera.Product: java.lang.String url
cn.sintoon.camera.Product: java.lang.String getName()
cn.sintoon.camera.Product: void setName(java.lang.String)
cn.sintoon.camera.Product: java.lang.String getUrl()
cn.sintoon.camera.Product: void setUrl(java.lang.String)
Note:一定要注意指定的類成員必須存在,如果不存在的話,這個(gè)規(guī)則相當(dāng)于沒有配,一點(diǎn)作用沒有
-keepnames class specification
-keep,allowshrinking class specification的簡(jiǎn)寫
指定要保留名稱的類成員和類成員(如果它們?cè)趬嚎s階段未被刪除)。
例如,你可能希望保留實(shí)現(xiàn) Serializable 接口的類的所有類名,以便處理后的代碼與任何原始序列化的類保持兼容。
完全不用的類仍然可以刪除。只有在混淆時(shí)才適用。
-keepnames class * implements java.io.Serializable
Note: 前提是在壓縮階段沒有被刪除掉,這里相當(dāng)于使用了修飾符 allowshrinking
-keepclassmembernames class specification
-keepclassmembers,allowshrinking class specification 的簡(jiǎn)寫
指定要保留名稱的類成員(如果它們?cè)趬嚎s階段未被刪除)。
例如,在處理由JDK 1.2或更早版本編譯的庫(kù)時(shí),可能希望保留合成類$方法的名稱。
所以當(dāng)處理使用處理過(guò)的庫(kù)的應(yīng)用程序時(shí),混淆器可以再次檢測(cè)到它(盡管ProGuard本身不需要這個(gè))。
只有在混淆時(shí)才適用。
-keepclassmembernames class * {
java.lang.Class class$(java.lang.String);
java.lang.Class class$(java.lang.String, boolean);
}
Note: 前提是在壓縮階段沒有被刪除掉,這里相當(dāng)于使用了修飾符 allowshrinking
-keepclasseswithmembernames class specification
-keepclasseswithmembers,allowshrinking class specification 的簡(jiǎn)寫
指定要保留名稱的類和類成員,條件是所有指定的類成員都存在于收縮階段之后。
例如,可能希望保留所有本機(jī)方法名稱和類別的名稱,以便處理的代碼仍可以與本機(jī)庫(kù)代碼鏈接。完全沒有使用的本地方法仍然可以被刪除。
如果使用了一個(gè)類文件,但它的本地方法都不是,它的名字仍然會(huì)被混淆。只有在混淆時(shí)才適用。
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
Note: 前提是在壓縮階段沒有被刪除掉,這里相當(dāng)于使用了修飾符 allowshrinking
-printseeds [filename]
指定詳盡列出由各種-keep選項(xiàng)匹配的類和類成員。列表打印到標(biāo)準(zhǔn)輸出或給定文件。該列表可用于驗(yàn)證是否真的找到了預(yù)期的類成員,尤其是在使用通配符的情況下。
例如,您可能想要列出您保存的所有應(yīng)用程序或所有小程序。
參考上面說(shuō)的 seeds.txt
-whyareyoukeeping class specification
指定打印詳細(xì)信息,說(shuō)明為什么給定的類和類成員正在壓縮步驟中。
如果想知道為什么某些給定元素出現(xiàn)在輸出中,這會(huì)很有用。
一般來(lái)說(shuō),可能有很多不同的原因。
此選項(xiàng)為每個(gè)指定的類和類成員打印最短的方法鏈到指定的種子或入口點(diǎn)。
在當(dāng)前的實(shí)施中,打印出的最短鏈有時(shí)可能包含循環(huán)扣除 - 這些并不反映實(shí)際收縮過(guò)程。
如果指定了 -verbose 選項(xiàng),則跟蹤包括完整的字段和方法簽名。只適用于壓縮。
壓縮規(guī)則
-dontshrink
指定不被壓縮的類文件。
默認(rèn)情況下壓縮是開啟的,除了用各種用 keep 選項(xiàng)直接或間接用到的類或類成員,其他的都會(huì)被移除。
壓縮步驟通常在優(yōu)化之后,因?yàn)槟承﹥?yōu)化可能會(huì)打開已經(jīng)刪除的類或類成員。
-printusage [filename]
指定列出移除的死代碼。該列表打印到標(biāo)準(zhǔn)輸出或給定文件。
參考上面說(shuō)的 usage.txt
例如,您可以列出應(yīng)用程序的未使用代碼。只適用于壓縮。
優(yōu)化規(guī)則
-dontoptimize
指定不優(yōu)化輸入類文件。默認(rèn)情況下,優(yōu)化已啟用;所有方法都在字節(jié)碼級(jí)別進(jìn)行了優(yōu)化
-optimizationpasses n
指定要執(zhí)行的優(yōu)化傳遞的數(shù)量。
默認(rèn)情況下,執(zhí)行一次傳遞。多次通行可能會(huì)導(dǎo)致進(jìn)一步的改進(jìn)。如果在優(yōu)化后沒有找到改進(jìn),則優(yōu)化結(jié)束。只適用于優(yōu)化。
混淆規(guī)則
-dontobfuscate
指定不混淆輸入的類文件。
默認(rèn)情況下,混淆是開啟的,類和類成員會(huì)被改成新的短隨機(jī)名稱,除了各種-keep選項(xiàng)列出的名稱外。
內(nèi)部屬性對(duì)于調(diào)試很有用,例如源文件名,變量名和行號(hào)被刪除。
-printmapping [filename]
指定將舊名稱映射到已重命名的類和類成員的新名稱的映射。映射打印到標(biāo)準(zhǔn)輸出或給定文件。
例如,它是后續(xù)增量混淆所必需的,或者如果想再次理解混淆的堆棧跟蹤。只有在混淆時(shí)才適用。
參考 上面說(shuō)的 mapping.txt。
-useuniqueclassmembernames
指定將相同的混淆名稱分配給具有相同名稱的類成員,并將不同混淆名稱分配給名稱不同的類成員(對(duì)于每個(gè)給定的類成員簽名)。
沒有這個(gè)選項(xiàng),更多的類成員可以被映射到相同的短名稱,比如'a','b'等等。
這個(gè)選項(xiàng)因此稍微增加了結(jié)果代碼的大小,但是它確保了保存的混淆名稱映射總是可以在隨后的增量混淆步驟中受到尊重。
例如,考慮兩個(gè)不同的接口,它們包含具有相同名稱和簽名的方法。如果沒有此選項(xiàng),這些方法可能會(huì)在第一個(gè)混淆步驟中獲取不同的混淆名稱。
如果添加了包含實(shí)現(xiàn)兩個(gè)接口的類的補(bǔ)丁程序,則ProGuard必須在增量混淆步驟中為這兩種方法強(qiáng)制執(zhí)行相同的方法名稱。
原始模糊代碼已更改,以保持結(jié)果代碼的一致性。在最初的混淆步驟中使用此選項(xiàng),這種重命名將永遠(yuǎn)不是必需的。
該選項(xiàng)僅適用于混淆。
實(shí)際上,如果計(jì)劃執(zhí)行增量混淆,則可能希望完全避免壓縮和優(yōu)化,因?yàn)檫@些步驟可能會(huì)刪除或修改部分代碼,這些代碼對(duì)于以后的添加至關(guān)重要。
-dontusemixedcaseclassnames
指定在混淆時(shí)不生成混合大小寫的類名。
默認(rèn)情況下,混淆的類名可以包含大寫字符和小寫字符的混合。
創(chuàng)建的這個(gè)完全可接受和可用的jars 只有在不區(qū)分大小寫的文件系統(tǒng)(比如Windows)的平臺(tái)上解壓縮jar時(shí),解壓縮工具可能會(huì)讓類似命名的類文件相互覆蓋。
解壓縮后自毀的代碼!真正想在Windows上解壓他們的jar的開發(fā)人員可以使用這個(gè)選項(xiàng)來(lái)關(guān)閉這種行為。
混淆的jars會(huì)因此變得稍大。
只有在混淆時(shí)才適用。
-keeppackagenames [package_filter]
指定不混淆給定的軟件包名稱。
可選過(guò)濾器是包名稱的逗號(hào)分隔列表。包名可以包含?,和*通配符,并且它們可以在!否定器。只有在混淆時(shí)才適用。
-flattenpackagehierarchy [package_name]
指定將所有重命名的軟件包重新打包,方法是將它們移動(dòng)到單個(gè)給定的父軟件包中。如果沒有參數(shù)或空字符串(''),程序包將移動(dòng)到根程序包中。
該選項(xiàng)是進(jìn)一步混淆軟件包名稱的一個(gè)示例。它可以使處理后的代碼更小,更難理解。
只有在混淆時(shí)才適用。
-repackageclasses [package_name]
指定將所有重命名的類文件重新打包,方法是將它們移動(dòng)到單個(gè)給定的包中。沒有參數(shù)或者使用空字符串(''),該軟件包將被完全刪除。
該選項(xiàng)將覆蓋 -flattenpackagehierarchy 選項(xiàng)。
這是進(jìn)一步模糊軟件包名稱的另一個(gè)例子。
它可以使處理后的代碼更小,更難理解。
其不推薦使用的名稱是-defaultpackage。
只有在混淆時(shí)才適用。
警告:如果在別處移動(dòng)它們,則在其包目錄中查找資源文件的類將不再正常工作。如有疑問,請(qǐng)不要使用此選項(xiàng),以免觸及包裝。
-keepattributes [attribute_filter]
指定要保留的任何可選屬性。這些屬性可以用一個(gè)或多個(gè)-keepattributes指令來(lái)指定。
可選過(guò)濾器是Java虛擬機(jī)和ProGuard支持的屬性名稱的逗號(hào)分隔列表。
屬性名稱可以包含?,*和**通配符,并且可以在之前加上!否定器。
例如,在處理庫(kù)時(shí),您至少應(yīng)保留Exceptions,InnerClasses和Signature屬性。
您還應(yīng)該保留SourceFile和LineNumberTable屬性以生成有用的混淆堆棧跟蹤。
最后,如果你的代碼依賴于它們,你可能需要保留注釋。
只有在混淆時(shí)才適用。
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 拋出異常時(shí)保留代碼行號(hào)
-keepattributes SourceFile,LineNumberTable
-keepparameternames
指定保留所保存方法的參數(shù)名稱和類型。
該選項(xiàng)實(shí)際上保留了調(diào)試屬性LocalVariableTable和LocalVariableTypeTable的修剪版本。
處理庫(kù)時(shí)它可能很有用。
一些IDE可以使用這些信息來(lái)幫助使用該庫(kù)的開發(fā)人員,
例如工具提示或自動(dòng)完成。
只有在混淆時(shí)才適用。
-renamesourcefileattribute [string]
指定要放入類文件的SourceFile屬性(和SourceDir屬性)中的常量字符串。請(qǐng)注意,該屬性必須首先出現(xiàn),所以它也必須使用-keepattributes指令明確保留。
例如,您可能希望讓處理過(guò)的庫(kù)和應(yīng)用程序生成有用的混淆堆棧跟蹤。
只有在混淆時(shí)才適用
預(yù)校驗(yàn) 規(guī)則
-dontpreverify
指定不預(yù)先驗(yàn)證已處理的類文件。
默認(rèn)情況下,如果類文件針對(duì)Java Micro Edition或Java 6或更高版本,則會(huì)對(duì)其進(jìn)行預(yù)驗(yàn)證。
對(duì)于Java Micro Edition,需要進(jìn)行預(yù)驗(yàn)證,因此如果指定此選項(xiàng),則需要在處理的代碼上運(yùn)行外部預(yù)驗(yàn)證程序。
對(duì)于Java 6,預(yù)驗(yàn)證是可選的,但從Java 7開始,它是必需的。
只有在最終對(duì)Android時(shí),它才不是必需的,因此您可以將其關(guān)閉以縮短處理時(shí)間。
-android
指定已處理的類文件針對(duì)Android平臺(tái)。然后ProGuard確保一些功能與Android兼容。
例如,如果您正在處理Android應(yīng)用程序,則應(yīng)該指定此選項(xiàng)。
一般規(guī)則
-verbose
指定在處理期間寫出更多信息。如果程序以異常終止,則此選項(xiàng)將打印出整個(gè)堆棧跟蹤,而不僅僅是異常消息。
-dontnote [class_filter]
指定不打印有關(guān)配置中可能的錯(cuò)誤或遺漏的注釋,
例如類名中的拼寫錯(cuò)誤或缺少可能有用的選項(xiàng)。
可選的過(guò)濾器是一個(gè)正則表達(dá)式;
ProGuard不打印有關(guān)匹配名稱的類的注釋。
-dontwarn [class_filter]
指定不警告有關(guān)未解決的引用和其他重要問題。
可選的過(guò)濾器是一個(gè)正則表達(dá)式; ProGuard不打印關(guān)于具有匹配名稱的類的警告。忽略警告可能是危險(xiǎn)的。
例如,如果處理確實(shí)需要未解決的類或類成員,則處理后的代碼將無(wú)法正常工作。
只有在你知道自己在做什么的情況下才使用此選項(xiàng)!
-ignorewarnings
指定打印任何關(guān)于未解決的引用和其他重要問題的警告,但在任何情況下都繼續(xù)處理,忽略警告。
忽略警告可能是危險(xiǎn)的。
例如,如果處理確實(shí)需要未解決的類或類成員,則處理后的代碼將無(wú)法正常工作。
只有在知道自己在做什么的情況下才使用此選項(xiàng)!
-printconfiguration [filename]
指定使用包含的文件和替換的變量寫出已解析的整個(gè)配置。結(jié)構(gòu)打印到標(biāo)準(zhǔn)輸出或給定文件。
這對(duì)于調(diào)試配置或?qū)ML配置轉(zhuǎn)換為更易讀的格式有時(shí)會(huì)很有用。
-dump [filename]
指定在任何處理后寫出類文件的內(nèi)部結(jié)構(gòu)。結(jié)構(gòu)打印到標(biāo)準(zhǔn)輸出或給定文件。
例如,可能希望寫出給定jar文件的內(nèi)容,而不進(jìn)行處理。
參考上面說(shuō)的 dump.txt。
-addconfigurationdebugging
指定用調(diào)試語(yǔ)句來(lái)處理已處理的代碼,這些語(yǔ)句顯示缺少ProGuard配置的建議。
如果處理后的代碼崩潰,那么在運(yùn)行時(shí)獲得實(shí)用提示可能非常有用,因?yàn)樗匀蝗鄙僖恍┓瓷渑渲谩?/p>
例如,代碼可能是使用GSON庫(kù)序列化類,可能需要一些配置。通常可以將控制臺(tái)的建議復(fù)制/粘貼到配置文件中。
警告:不要在發(fā)行版本中使用此選項(xiàng),因?yàn)樗鼘⒒煜畔⑻砑拥揭烟幚淼拇a中。
keep 選項(xiàng)修飾符
includedescriptorclasses
指定-keep選項(xiàng)所保存的方法和字段的類型描述符中的任何類也應(yīng)保存。
在保留方法名稱時(shí),這通常很有用,以確保方法的參數(shù)類型不會(huì)重命名。他們的簽名保持完全不變,并與本地庫(kù)兼容。
includecode
指定保持-keep選項(xiàng)所保存的字段的方法的代碼屬性也應(yīng)該保留,即可能未被優(yōu)化或模糊處理。這對(duì)于已優(yōu)化或混淆的類通常很有用,以確保在優(yōu)化期間未修改其代碼。
allowshrinking
指定-keep選項(xiàng)中指定的入口點(diǎn)可能會(huì)壓縮,即使必須另外保留它們。
也就是說(shuō),可以在壓縮步驟中刪除入口點(diǎn),但如果它們是必需的,則它們可能未被優(yōu)化或混淆。
allowoptimization
指定-keep選項(xiàng)中指定的入口點(diǎn)可能會(huì)被優(yōu)化,即使它們必須另外保存。
也就是說(shuō),入口點(diǎn)可能會(huì)在優(yōu)化步驟中被更改,但它們可能不會(huì)被刪除或混淆。
此修飾符僅用于實(shí)現(xiàn)不尋常的要求。
allowobfuscation
指定在-keep選項(xiàng)中指定的入口點(diǎn)可能會(huì)被混淆,即使它們必須另外保存。
也就是說(shuō),入口點(diǎn)可能在混淆步驟中被重命名,但它們可能不會(huì)被刪除或優(yōu)化。
此修飾符僅用于實(shí)現(xiàn)不尋常的要求。
keep 選項(xiàng)之間的關(guān)系
壓縮和混淆的各種-keep選項(xiàng)起初看起來(lái)有點(diǎn)混亂,但實(shí)際上它們背后有一個(gè)模式。
下表總結(jié)了它們之間的關(guān)系:
| 內(nèi)容 | 被刪除或重命名 | 被重命名 |
|---|---|---|
| 類和類成員 | -keep | -keepnames |
| 只有類成員 | -keepclassmembers | -keepclassmembernames |
| 類和類成員,引用成員存在 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果指定了一個(gè)沒有類成員的類,ProGuard只保留該類及其無(wú)參數(shù)的構(gòu)造函數(shù)作為入口點(diǎn)。它可能仍會(huì)刪除,優(yōu)化或混淆其他班級(jí)成員。
如果指定了一個(gè)方法,則ProGuard僅將該方法作為入口點(diǎn)進(jìn)行保存。其代碼可能仍會(huì)進(jìn)行優(yōu)化和調(diào)整。
類規(guī)范
類規(guī)范是類和類成員(字段和方法)的模板。它用于各種-keep選項(xiàng)和-assumenosideeffects選項(xiàng)中。相應(yīng)的選項(xiàng)僅適用于與模板匹配的類和類成員。
模板的設(shè)計(jì)看起來(lái)非常類似于Java,并為通配符進(jìn)行了一些擴(kuò)展。為了理解語(yǔ)法,你應(yīng)該看看這些例子,但這是對(duì)一個(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 ... ] *;
...
}]
方括號(hào) “[]” 表示其內(nèi)容是可選的。
省略號(hào)點(diǎn)“...”表示可以指定任意數(shù)量的前述項(xiàng)目。
垂直條“|”劃定了兩種選擇。
非粗體括號(hào)“()”只是將屬于規(guī)范的部分組合在一起。
縮進(jìn)嘗試澄清預(yù)期的含義,但在實(shí)際配置文件中,空白是不相關(guān)的。
class關(guān)鍵字指的是任何接口或類。interface 關(guān)鍵字限制匹配接口類。 enum關(guān)鍵字限制匹配枚舉類。在 interface 或 enum 關(guān)鍵字前加上!將匹配限制為不是接口或枚舉的類。
每一個(gè)類名字都必須是完全限定名,例如 java.lang.String 內(nèi)部類用美元符號(hào)“$”分隔,例如java.lang.Thread$State。類名可以被指定為包含以下通配符的正則表達(dá)式:
? 匹配類名稱中的任何單個(gè)字符,但不匹配包分隔符。例如 "com.example.Test?" 可以匹配 "com.example.Test1" 和 "com.example.Test2" 但不能匹配 "com.example.Test12"
* 匹配不包含包分隔符的類名的任何部分。例如 "com.example.*Test*" 能夠匹配 "com.example.MyTest" 和 "com.example.MyTestProduct" 但不能匹配 "com.example.mxc.MyTest" 或者 "com.example.*" 能夠匹配 "com.example" 但不能匹配 "com.example.mxc"
** 匹配類名稱的任何部分,可能包含任意數(shù)量的包分隔符。例如,"**.Testz" 匹配除根包以外的所有包中的所有Test類?;蛘?,"com.example.**" 匹配 "com.example" 中的所有類及其子包。
<n> 在相同的選項(xiàng)中匹配第n個(gè)匹配的通配符。例如,"com.example.*Foo<1>" 匹配"com.example.BarFooBar"。
為了獲得更多的靈活性,類名實(shí)際上可以是逗號(hào)分隔的類名列表,可以加!。這個(gè)符號(hào)看起來(lái)不是很像java,所以應(yīng)該適度使用。
為了方便和向后兼容,類名*指任何類,而不考慮它的包。
extends 和 **implements ** 通常用來(lái)限制使用通配符的類。目前他們是一樣的。他們的意思是 只有繼承或?qū)崿F(xiàn)了給定類的類才有資格。給定的類本身不包含在這個(gè)集合中。如果需要,應(yīng)該在單獨(dú)的選項(xiàng)中指定。
@ 可用于將類和類成員限制為使用指定的注釋類型進(jìn)行注釋的類。annotationtype 就像類名一樣被指定。
除了方法參數(shù)列表不包含參數(shù)名稱外,字段和方法在Java中的定義非常類似(就像在javadoc和javap等其他工具中一樣)。這些規(guī)范還可以包含以下通配符通配符:
| 通配符 | 意義 |
|---|---|
| <init> | 匹配任何構(gòu)造方法 |
| <fields> | 匹配任何字段 |
| <methods> | 匹配任何方法 |
| * | 匹配任何方法和字段 |
請(qǐng)注意,上述通配符沒有返回類型。只有<init>通配符才有一個(gè)參數(shù)列表。
字段和方法也可以使用正則表達(dá)式來(lái)指定。名稱可以包含以下通配符:
| 通配符 | 意義 |
|---|---|
| ? | 匹配方法名的任何單個(gè)字符 |
| * | 匹配方法名的任何部分 |
| <n> | 在相同的選項(xiàng)中匹配第n個(gè)匹配的通配符 |
類型可以包含以下通配符
| 通配符 | 意義 |
|---|---|
| % | 匹配任何原始類型(boolean,int 等,不包含 void) |
| ? | 匹配類名中的單個(gè)字符 |
| * | 匹配類名中的任何部分但不包含包分隔符 |
| ** | 匹配類名中的任何部分但不包含包分隔符 |
| *** | 匹配任何類型(原始類型或者非原始類型,數(shù)組或者非數(shù)組) |
| --- | 匹配任何類型的任意數(shù)量的參數(shù) |
| <n> | 在相同的選項(xiàng)中匹配第n個(gè)匹配的通配符。 |
請(qǐng)注意,?,*和**通配符永遠(yuǎn)不會(huì)匹配基本類型。
而且,只有***通配符才能匹配任何維度的數(shù)組類型。
例如,“** get *()”匹配“java.lang.Object getObject()”,但不匹配“float getFloat()”和“java.lang.Object [] getObjects()”。
也可以使用短類名(無(wú)包)或使用完整的類名來(lái)指定構(gòu)造函數(shù)。和Java語(yǔ)言一樣,構(gòu)造函數(shù)規(guī)范有一個(gè)參數(shù)列表,但沒有返回類型。
類訪問修飾符和類成員訪問修飾符通常用于限制通配類和類成員。它們指定必須為成員設(shè)置相應(yīng)的訪問標(biāo)志以匹配。前面加 "!" 決定相應(yīng)的訪問標(biāo)志應(yīng)該被取消設(shè)置。
允許組合多個(gè)標(biāo)志(例如,public static)。這意味著必須設(shè)置兩個(gè)訪問標(biāo)志(例如 public static ),除非它們有沖突,在這種情況下,至少必須設(shè)置其中一個(gè)(例如至少public或 protected)。
ProGuard支持可能由編譯器設(shè)置的其他修飾符 synthetic,bridge和varargs。
參考資料
End
