引言
很早就想寫篇關(guān)于proguard的文章,但是CSDN翠胰、簡(jiǎn)書、博客園等等網(wǎng)站上面已經(jīng)有大量關(guān)于proguard的文章自脯,并且很多都寫得很好之景。我再寫似乎也是重復(fù)而已,而且很有可能還沒(méi)有前輩們寫得好膏潮。思來(lái)想去锻狗,我覺(jué)得對(duì)我來(lái)說(shuō)超不超越前人并不重要,記錄自己的知識(shí)最重要焕参,由此便有了本文轻纪。寫著寫著發(fā)現(xiàn)自己大部分是在翻譯英文文檔,實(shí)在沒(méi)有什么自己的東西龟糕,但實(shí)戰(zhàn)是要有理論基礎(chǔ)的桐磁,為了下一篇《ProGuard實(shí)戰(zhàn)》能更容易理解悔耘,我還是決定即使是純翻譯也要把它寫完讲岁。如果你英語(yǔ)還不錯(cuò),建議你跳過(guò)本文衬以,直接閱讀原汁原味的英文文檔:$SDK/tools/proguard/docs/manual/usage.html($SDK表示android sdk路徑)缓艳。
看完你還有興趣的話,歡迎再回來(lái)看《ProGuard實(shí)戰(zhàn)》看峻。如果你英語(yǔ)一般般阶淘,那建議至少將本文簡(jiǎn)單過(guò)一遍,再看《ProGuard實(shí)戰(zhàn)》互妓。
1. proguard簡(jiǎn)介
proguard是一個(gè)能夠?qū)ava 代碼進(jìn)行壓縮(Shrink)溪窒,優(yōu)化(Optimize),混淆(Obfuscate)冯勉,預(yù)檢(Preveirfy)的工具澈蚌。 proguard已集成到Android SDK中,路徑為$SDK\tools\proguard灼狰,其中包含可執(zhí)行文件宛瞄、jar、文檔交胚、使用例子及默認(rèn)的混淆配置文件等等:
簡(jiǎn)單介紹一下proguard的主要文件:
(1) bin目錄下是可執(zhí)行文件份汗,包括:
proguard.bat : 用于對(duì)代碼進(jìn)行壓縮盈电、優(yōu)化、混淆杯活、預(yù)檢的可執(zhí)行文件匆帚。
retrace.bat : 用于對(duì)混淆后的代碼出現(xiàn)的異常或者錯(cuò)誤日志進(jìn)行堆棧還原的可執(zhí)行文件旁钧。
proguardgui.bat: 一個(gè)GUI工具卷扮,它集成了proguard.bat和retrace.bat,在可視化界面中提供了處理過(guò)程的各個(gè)步驟的配置項(xiàng)均践,比在命令行使用更加方便晤锹。
(2) lib目錄下是jar文件,與可執(zhí)行文件相對(duì)應(yīng)也有三個(gè):
proguard.jar
retrace.jar
proguardgui.jar
(3) docs目錄下是使用文檔彤委,里面有使用手冊(cè)鞭铆,不熟悉時(shí)可查看其中的文檔。
(4) exmaples是使用例子焦影,可結(jié)合例子學(xué)習(xí)和理解proguard的使用车遂。
(5) proguard-android.txt、proguard-android-optimize.txt斯辰、proguard-project.txt 是默認(rèn)的proguard配置文件舶担。
2. proguard處理流程
proguard處理流程圖如下圖所示:
- 壓縮(Shrink): 檢測(cè)并刪除未使用的類,字段彬呻,方法和屬性衣陶。
- 優(yōu)化(Optimize): 分析并優(yōu)化方法的字節(jié)碼。
- 混淆(Obfuscate): 使用簡(jiǎn)短的無(wú)意義名稱例如a,b,c等闸氮,重命名類剪况,字段和方法。
- 預(yù)檢(Preveirfy): 主要是在Java平臺(tái)上對(duì)處理后的代碼進(jìn)行預(yù)檢蒲跨。
前面三步使代碼庫(kù)占用空間更小译断,執(zhí)行更高效,并且更難以逆向工程或悲, 最后的預(yù)驗(yàn)證步驟將預(yù)驗(yàn)證信息添加到類中孙咪,這對(duì)于JavaME是必需的,也可以縮短啟動(dòng)時(shí)間巡语。這些步驟都是可選的翎蹈。 例如,proguard可以僅用于列出應(yīng)用程序中的無(wú)效代碼捌臊,或預(yù)驗(yàn)證類文件杨蛋,以便高效的在Java 使用。
3. proguard入口點(diǎn)
3.1 入口點(diǎn)
為了確定哪些代碼必須保留,哪些代碼可以丟棄或混淆逞力,我們必須為代碼指定一個(gè)或多個(gè)入口點(diǎn)曙寡,入口點(diǎn)通常是含有main方法的類。
- 在壓縮(Shrink)步驟中寇荧,從入口點(diǎn)開始遞歸確定已使用的類和類成員举庶,未使用的類和類成員將被丟棄。
- 在優(yōu)化(Optimize)步驟中揩抡,進(jìn)一步優(yōu)化代碼户侥,非入口點(diǎn)的類和方法可能被設(shè)為private,static或final的峦嗤,未使用的參數(shù)可能被刪除蕊唐,某些方法可能被設(shè)為inlined。
- 在混淆(Obfuscate)步驟中烁设,重命名不是入口點(diǎn)的類和類成員替梨。在整個(gè)過(guò)程中將保留入口點(diǎn),確弊昂冢混淆后仍可以使用其原名稱來(lái)訪問(wèn)它們副瀑。
- 預(yù)檢(Preveirfy)步驟是唯一不必知道入口點(diǎn)的步驟。
3.2 反射
值得注意的是恋谭,如代碼中使用了反射需要特殊處理糠睡。例如,Class.forName()構(gòu)造可以在運(yùn)行時(shí)引用任何類疚颊,通常情況下狈孔,我們無(wú)法預(yù)見(jiàn)哪些類的類名必須保留。因此串稀,必須將反射動(dòng)態(tài)創(chuàng)建或調(diào)用的類或類成員也指定為入口點(diǎn)除抛。我們必須在proguard配置文件中使用-keep選項(xiàng)來(lái)保留它們的類名。但是母截,proguard會(huì)自動(dòng)檢測(cè)并處理以下情況:
Class.forName("SomeClass")
SomeClass.class
SomeClass.class.getField("someField")
SomeClass.class.getDeclaredField("someField")
SomeClass.class.getMethod("someMethod", new Class[] {})
SomeClass.class.getMethod("someMethod", new Class[] { A.class })
SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
當(dāng)然,類和類成員的名稱可能會(huì)有所不同橄教,但實(shí)際上類的所有構(gòu)造方法在字面上是相同的清寇,proguard可以識(shí)別它們。被引用的類和類成員在壓縮階段被保留护蝶,并且字符串參數(shù)在混淆階段被適當(dāng)?shù)馗隆?br>
此外华烟,如果出現(xiàn)某些類或類成員需要保留,proguard會(huì)提供一些建議持灰。例如盔夜,proguard會(huì)注意類似“(SomeClass)Class.forName(variable).newInstance()”的構(gòu)造。這些可能表明該類或接口SomeClass或其實(shí)現(xiàn)類可能需要保留。然后喂链,我們可以根據(jù)建議相應(yīng)地調(diào)整配置返十。
為了能夠正確的混淆,我們需要對(duì)正在處理的代碼有所了解椭微《纯樱混淆大量使用反射的代碼可能會(huì)出現(xiàn)錯(cuò)誤,需要反復(fù)試驗(yàn)蝇率,尤其是在沒(méi)有關(guān)于代碼內(nèi)部的必要信息的情況下迟杂。
4. proguard的使用
要使用proguard,只需在命令行鍵入:
java -jar proguard.jar options ...
第1節(jié)中我們提到過(guò)proguard.jar本慕,它的路徑為$SDK/tools/proguard/lib/proguard.jar排拷。當(dāng)然我們也可以選擇使用$SDK/tools/proguard/bin/proguard.bat,用文本編輯器打開proguard.bat可以看到它只是一個(gè)腳本锅尘,實(shí)際上還是用java指令來(lái)執(zhí)行的攻泼。通常,我們會(huì)將proguard的大多數(shù)選項(xiàng)都放在配置文件中鉴象,然后運(yùn)行proguard忙菠,例如配置文件為myconfig.pro:
java -jar proguard.jar @myconfig.pro
我們還可以組合命令行選項(xiàng)和配置文件中的選項(xiàng)。例如:
java -jar proguard.jar @myconfig.pro -verbose
proguard 選項(xiàng)參數(shù)和配置文件的說(shuō)明:
- proguard配置文件中可以添加注釋纺弊,注釋以#字符開始牛欢,一直持續(xù)到該行的末尾。
- 單詞和限定符之間的多余空格將被忽略淆游。帶有空格或特殊字符的文件名應(yīng)使用單引號(hào)或雙引號(hào)引起來(lái)傍睹。
- 選項(xiàng)可以在命令行中的參數(shù)和配置文件中的行中任意分組,這意味著可以引用命令行選項(xiàng)的任意部分犹菱,例如拾稳,以避免特殊字符的shell擴(kuò)展。
- 選項(xiàng)通常是與順序無(wú)關(guān)的腊脱。為了使用方便访得,可以將其縮寫為第一個(gè)唯一字符。
5. proguard選項(xiàng)
參考:$SDK/tools/proguard/docs/manual/usage.html
5.1 輸入/輸出選項(xiàng)(Input/Output Options)
-include filename陕凹,使用時(shí)也可以縮寫成@filename悍抑,從給定的文件中遞歸讀取配置選項(xiàng)。
-basedirectory directoryname杜耙,為配置參數(shù)或此配置文件中的相對(duì)文件名指定基本目錄搜骡。
-injars class_path,指定輸入jar的路徑(或war佑女,ears记靡,zip或目錄)谈竿。這些jar中的類文件將被處理并寫入輸出jar。默認(rèn)情況下摸吠,所有非類文件都將被復(fù)制而不會(huì)更改空凸。直接從目錄中讀取輸入文件時(shí),可以過(guò)濾掉臨時(shí)文件的路徑(例如由IDE創(chuàng)建的臨時(shí)文件)蜕便,在過(guò)濾器小節(jié)會(huì)進(jìn)一步解釋劫恒。為了提高可讀性,可以使用多個(gè)-injars選項(xiàng)指定類路徑條目轿腺。
-outjars class_path两嘴, 指定輸出jar的路徑(或wars,ears族壳,zips或目錄)憔辫。-injars選項(xiàng)指定的jar處理后將被寫入-outjars指定的jar。我們可以將一組輸入jar的內(nèi)容處理后輸出到對(duì)應(yīng)的一組輸出jar中仿荆。與-injars選項(xiàng)一樣贰您,可以過(guò)濾輸出條目,在過(guò)濾器小節(jié)會(huì)進(jìn)一步解釋拢操。然后锦亦,將每個(gè)已處理的類文件或資源文件通過(guò)匹配的過(guò)濾器寫入輸出jar組中的第一個(gè)輸出條目。我們必須避免讓輸出文件覆蓋任何輸入文件令境。為了提高可讀性杠园,可以使用多個(gè)-outjars選項(xiàng)指定類路徑條目。如果沒(méi)有任何-outjars選項(xiàng)舔庶,則不會(huì)寫入任何jar抛蚁。
-libraryjars class_path, 指定依庫(kù)jar(或war惕橙,ears瞧甩,zip或目錄)。依賴庫(kù)jar中的文件將不會(huì)包含進(jìn)outjars中弥鹦。盡管依賴庫(kù)jar的類的存在可以改善優(yōu)化步驟的結(jié)果肚逸,但是它們只需要被調(diào)用,不需要存在于outjars中惶凝『鸹ⅲ可以過(guò)濾整個(gè)依賴庫(kù)jar的路徑,在過(guò)濾器小節(jié)會(huì)進(jìn)一步解釋苍鲜。為了提高可讀性,可以使用多個(gè)-libraryjars選項(xiàng)指定類路徑條目玷犹。請(qǐng)注意混滔,在查找?guī)祛悤r(shí)洒疚,proguard不考慮boot path和class path,所以必須明確指定代碼將使用的運(yùn)行時(shí)jar坯屿。
-skipnonpubliclibraryclasses油湖,指定在讀取依賴庫(kù)jar時(shí)跳過(guò)非公共類,以加快處理速度并減少proguard的內(nèi)存使用量领跛。默認(rèn)情況下乏德,proguard會(huì)讀取非公共和公共庫(kù)類。非公用類通常不相關(guān)吠昭,不影響輸入jar中的實(shí)際程序代碼喊括,忽略它們可以加快proguard的速度,而不會(huì)影響輸出矢棚。某些庫(kù)中可能包含了由公共庫(kù)類擴(kuò)展的非公共庫(kù)類郑什,這種情況我們就不能使用此選項(xiàng)。如果由于設(shè)置了此選項(xiàng)而出現(xiàn)can't find classes錯(cuò)誤蒲肋,則proguard將打印警告蘑拯。
-dontskipnonpubliclibraryclasses, 指定不跳過(guò)非公共庫(kù)類兜粘。從4.5版開始申窘,這是默認(rèn)設(shè)置。Android SDK中的是4.7孔轴,因此這個(gè)是默認(rèn)設(shè)置剃法。
-dontskipnonpubliclibraryclassmembers, 指定不跳過(guò)包可見(jiàn)的庫(kù)類成員(字段和方法)距糖。默認(rèn)情況下玄窝,proguard在解析庫(kù)類時(shí)會(huì)跳過(guò)包可見(jiàn)的庫(kù)類成員。當(dāng)我們確實(shí)引用了包可見(jiàn)的類成員時(shí)悍引,需要設(shè)置此項(xiàng)恩脂。
-keepdirectories [directory_filter], 指定要保留在輸出jar中的目錄(或wars趣斤,ears或directory)俩块。默認(rèn)情況下,目錄條目被刪除浓领。這樣可以減小jar的大小玉凯,但是如果程序代碼嘗試使用“ MyClass.class.getResource(“”)“”之類的結(jié)構(gòu)來(lái)查找它們,則可能會(huì)找不到的联贩。如果指定的選項(xiàng)沒(méi)有過(guò)濾器漫仆,則保留所有目錄。使用過(guò)濾器時(shí)泪幌,僅保留匹配的目錄盲厌。
-target version署照,指定要在已處理的類文件中設(shè)置的版本號(hào)。默認(rèn)情況下吗浩,類文件的版本號(hào)保持不變建芙。例如,版本號(hào)可以是1.0懂扼、1.1禁荸、1.2、1.3阀湿、1.4赶熟、1.5(或僅5),1.6(或僅6)或1.7(或僅7)之一炕倘。某些情況下钧大,可能通過(guò)更改類文件的版本號(hào)并對(duì)其進(jìn)行預(yù)先驗(yàn)證。
-forceprocessing罩旋, 強(qiáng)制重新處理啊央,即使輸出看起來(lái)是最新的也是如此。最新的檢測(cè)機(jī)制是比較指定輸入涨醋,輸出和配置文件或目錄的日期戳瓜饥。
5.2 保留選項(xiàng)(Keep Options)
keep [,modifier浴骂,...] class_specification乓土,指定要保留的類和類成員(字段和方法)。例如溯警,為了保留應(yīng)用程序趣苏,我們需要保留包含main方法的主類。為了處理庫(kù)梯轻,我們應(yīng)該保留所有可公開訪問(wèn)的元素食磕。
-keepclassmembers [,modifier喳挑,...] class_specification彬伦,指定要保留的類成員(如果它們的類也被保留)。例如伊诵,我們可能想要保留所有實(shí)現(xiàn)Serializable接口的類的序列化字段和方法单绑。
-keepclasseswithmembers [,modifier曹宴,...] class_specification搂橙,指定保留滿足條件的類和類成員。例如笛坦,我們可能希望保留所有具有main方法的類份氧,但是又不想一一列出它們唯袄。
-keepnames class_specification弯屈,-keep,allowshrinking class_specification的縮寫蜗帜,指定要保留名稱的類和類成員。例如资厉,我們可能希望保留實(shí)現(xiàn)Serializable接口的類的所有類名厅缺,以便處理后的代碼與任何原始序列化的類保持兼容。注意宴偿,這個(gè)選項(xiàng)僅在混淆時(shí)適用湘捎,因此僅對(duì)壓縮階段未刪除的類和類成員有效,完全未使用的類在壓縮階段仍可以刪除窄刘。
-keepclassmembernames class_specification窥妇,-keepclassmembers,allowshrinking class_specification的縮寫。指定要保留名稱的類成員娩践。同樣的活翩,這個(gè)選項(xiàng)僅在混淆時(shí)適用。
-keepclasseswithmembernames class_specification翻伺, -keepclasseswithmembers,allowshrinking class_specification的縮寫材泄。指定保留滿足條件的類和類成員的名稱。例如吨岭,我們可能希望保留所有native方法名稱及其類的名稱拉宗,以便處理后的代碼仍可以與so代碼鏈接。如果一個(gè)類文件被使用了辣辫,但是它不包含native方法旦事,則其名稱仍將被混淆。同樣的急灭,這個(gè)選項(xiàng)僅在混淆時(shí)適用姐浮。
-printseeds [filename],詳細(xì)列出與各種-keep選項(xiàng)匹配的類和類成員化戳,將列表打印到標(biāo)準(zhǔn)輸出或給定文件单料。該列表對(duì)于驗(yàn)證是否確實(shí)找到了預(yù)期的類成員很有用,尤其是在使用通配符的情況下点楼。例如扫尖,您可能要列出所有應(yīng)用程序或保留的所有小程序。
-keep可以用于壓縮和混淆掠廓,各種-keep選項(xiàng)可能看起來(lái)有些混亂换怖,但是實(shí)際上它們背后有一個(gè)模式。 下表總結(jié)了它們之間的關(guān)系:
如果不確定所需要的選項(xiàng)蟀瞧,則應(yīng)該只使用-keep沉颂。 這樣可以保證在壓縮步驟中不刪除指定的類和類成員条摸,并且在混淆步驟中不將其重命名。
注意:
(1)指定類而不指定該類的成員只會(huì)將類保留為入口點(diǎn)铸屉,它的類成員仍然可以被刪除钉蒲,優(yōu)化或混淆。
(2)指定類成員僅將類成員保留為入口點(diǎn)彻坛,相關(guān)的代碼仍可以被優(yōu)化和調(diào)整顷啼。
5.3 壓縮選項(xiàng)(Shrinking Options)
-dontshrink,指定不壓縮的類文件昌屉,默認(rèn)壓縮钙蒙。除各種-keep選項(xiàng)列出的類以及它們直接或間接依賴的類之外,所有類和類成員都將被刪除间驮。在每個(gè)優(yōu)化步驟之后也會(huì)壓縮躬厌,因?yàn)槟承﹥?yōu)化后可能會(huì)有更多類和類成員可以被刪除。
-printusage [filename]竞帽,列出未使用的代碼扛施。該列表將打印到標(biāo)準(zhǔn)輸出或給定文件。例如抢呆,您可以列出應(yīng)用程序的未使用代碼煮嫌。僅在壓縮時(shí)適用。
-whyareyoukeeping class_specification抱虐, 打印為什么在壓縮步驟中保留給定類和類成員的詳細(xì)信息昌阿。對(duì)于每個(gè)指定的類和類成員,此選項(xiàng)將最短的方法鏈打印到指定的種子或入口點(diǎn)恳邀。打印出的最短鏈有時(shí)可能包含循環(huán)扣除懦冰,這些未反映實(shí)際的壓縮過(guò)程。如果指定了-verbose選項(xiàng)谣沸,則跟蹤將包括完整的字段和方法簽名刷钢。僅在壓縮時(shí)適用。如果我們想知道為什么輸出中存在某些給定的元素乳附,這可能會(huì)很有用内地。
5.4 優(yōu)化選項(xiàng)(Optimization Options)
-dontoptimize,指定不優(yōu)化的類文件赋除。默認(rèn)啟用優(yōu)化阱缓。所有方法都在字節(jié)碼級(jí)別進(jìn)行了優(yōu)化。
-optimizations Optimization_filter举农, 在更細(xì)粒度的級(jí)別上指定要啟用和禁用的優(yōu)化荆针。僅在優(yōu)化時(shí)適用。這是一個(gè)專家選項(xiàng)。
-optimizationpasses n航背,指定要執(zhí)行的優(yōu)化遍數(shù)喉悴,n的取值范圍為0-7,默認(rèn)為1玖媚,Android項(xiàng)目一般設(shè)置為5箕肃。多次優(yōu)化可能會(huì)導(dǎo)致進(jìn)一步的改進(jìn)。如果在優(yōu)化之后未發(fā)現(xiàn)任何改進(jìn)最盅,則優(yōu)化結(jié)束突雪。僅在優(yōu)化時(shí)適用。
-assumenosideeffects class_specification涡贱,指定刪除沒(méi)有任何影響的方法,比如沒(méi)有返回值的且不影響執(zhí)行結(jié)果的方法惹想、有返回值但未使用其返回值且不影響執(zhí)行結(jié)果的方法问词。例如,我們可以指定方法Log.v()嘀粱,用來(lái)優(yōu)化無(wú)用的Log激挪。請(qǐng)注意,該選項(xiàng)將作用于整個(gè)jar锋叨,很容易破壞原有代碼垄分,應(yīng)當(dāng)盡量少用。最好的做法是保持一個(gè)良好的編碼習(xí)慣娃磺,冗余代碼是在寫的時(shí)候就把它刪掉薄湿。僅在優(yōu)化時(shí)適用。
-allowaccessmodification偷卧,指定在處理過(guò)程中可以擴(kuò)大類和類成員的訪問(wèn)修飾符豺瘤。這可以改善優(yōu)化步驟的結(jié)果。例如听诸,當(dāng)有一個(gè)public getter時(shí)坐求,可能也必須將訪問(wèn)的字段改成public。大多數(shù)情況下晌梨,不應(yīng)該使用此選項(xiàng)桥嗤,使用后可能導(dǎo)致在API中設(shè)計(jì)為不公開的類和類成員被公開。
-mergeinterfacesaggressively仔蝌,指定侵略性的合并接口泛领,即使接口的實(shí)現(xiàn)類未實(shí)現(xiàn)所有接口方法,也可以合并接口掌逛。這樣可以通過(guò)減少類的總數(shù)來(lái)減小輸出的大小师逸。同樣會(huì)破壞原有代碼,如不清楚其影響,應(yīng)當(dāng)不使用該選項(xiàng)篓像。僅在優(yōu)化時(shí)適用动知。
5.5 混淆選項(xiàng)(Obfuscation Options)
-dontobfuscate,指定不混淆的類文件员辩,默認(rèn)全部混淆盒粮。除各種-keep選項(xiàng)列出的類和類成員外,其它類和類成員將會(huì)被隨機(jī)重命名成簡(jiǎn)短奠滑、無(wú)意義的名稱丹皱,并且會(huì)刪除對(duì)調(diào)試有用的內(nèi)部屬性,例如源文件名宋税、變量名摊崭、行號(hào)等。
-printmapping [filename]杰赛,指定映射文件呢簸。重命名后,重命名后的類和類成員與重命名前的類和類成員有一個(gè)映射關(guān)系乏屯,此選項(xiàng)會(huì)打印這個(gè)映射關(guān)系根时,輸出到標(biāo)準(zhǔn)輸出或指定文件。例如辰晕,混淆后應(yīng)用程序運(yùn)行遇到了異常蛤迎,我們就需要這個(gè)映射關(guān)系文件來(lái)還原異常的堆棧信息,以便定位源代碼中出錯(cuò)的位置含友。僅在混淆時(shí)適用替裆。
-applymapping filename,指定重復(fù)使用先前混淆生成的映射文件唱较。映射文件中列出的類和類成員將使用原有名稱扎唾。映射文件中沒(méi)有的類和類成員,即新增的類和類成員將被重命名南缓。映射可以引用輸入類以及庫(kù)類胸遇。此選項(xiàng)在增量混淆時(shí)很有用,在這種情況下汉形,還可以考慮使用-useuniqueclassmembernames選項(xiàng)只允許一個(gè)映射文件纸镊。僅在混淆時(shí)適用。
-obfuscationdictionary filename概疆,指定一個(gè)混淆字典逗威。默認(rèn)情況下,短名稱(如“ a”岔冀,“ b”等)用作混淆后的字段和方法的名稱凯旭。指定混淆字典后,混淆字典是一個(gè)文本文件,其中所有有效單詞都將被用作混淆的字段和方法的名稱罐呼,但#符號(hào)后的空格鞠柄,標(biāo)點(diǎn)符號(hào),重復(fù)的單詞和注釋將被忽略嫉柴。我們可以在混淆字典中的輸入一系列保留關(guān)鍵字厌杜、外文字符的標(biāo)識(shí)符。注意计螺,混淆字典幾乎不能改善混淆夯尽。因?yàn)槲谋揪幾g器可以很容易的自動(dòng)替換它們,并且使用混淆字典混淆后登馒,還可以使用更簡(jiǎn)單的名稱再次進(jìn)行混淆匙握。通常,最有用的使用場(chǎng)景是在混淆字典中指定類中已經(jīng)存在的字符串(例如“Code”)作為保留關(guān)鍵字谊娇,這樣可以將類文件的大小減小一點(diǎn)肺孤。僅在混淆時(shí)適用。
-classobfuscationdictionary filename济欢,指定一個(gè)文本文件,所有有效單詞都用作混淆的類名小渊。作用類似obfuscationdictionary選項(xiàng)法褥。僅在混淆時(shí)適用。
-packageobfuscationdictionary filename酬屉,指定一個(gè)文本文件半等,所有有效單詞都用作混淆的包名。作用類似obfuscationdictionary選項(xiàng)呐萨。僅在混淆時(shí)適用杀饵。
-overloadaggressively,指定在混淆時(shí)應(yīng)用侵略性的重載谬擦。多個(gè)字段和方法只要它們的參數(shù)和返回類型不同(不僅是參數(shù))切距,就可以使用相同的名稱。此選項(xiàng)可以使處理后的代碼更小惨远、更難理解谜悟。此選項(xiàng)在某些版本可能會(huì)出現(xiàn)異常,例如北秽,Google的Dalvik VM無(wú)法處理重載的靜態(tài)字段葡幸。如不清楚其影響,應(yīng)當(dāng)不使用該選項(xiàng)贺氓,除非明白處理這種異常蔚叨。僅在混淆時(shí)適用。
-useuniqueclassmembernames,指定將相同的混淆名稱分配給具有相同名稱的類成員蔑水,將不同的混淆名稱分配給具有不同名稱的類成員邢锯。如果沒(méi)有該選項(xiàng),則可以將更多的類成員映射到相同的短名稱肤粱,如“ a”弹囚,“ b”等。因此领曼,該選項(xiàng)會(huì)稍微增加結(jié)果代碼的大小鸥鹉,但可以確保保存的混淆名稱映射始終是在后續(xù)的漸進(jìn)混淆步驟中能夠識(shí)別。此選項(xiàng)僅在混淆時(shí)適用庶骄。
-dontusemixedcaseclassnames毁渗,指定在混淆時(shí)不生成大小寫混合的類名。默認(rèn)情況下单刁,混淆的類名可以包含大寫字符和小寫字符的混合灸异。這將輸出完全可接受且可用的jars。但僅當(dāng)在不區(qū)分大小寫的文件系統(tǒng)(例如Windows)的平臺(tái)上解壓縮jar時(shí)羔飞,解壓工具才可能會(huì)使名稱相似的類文件相互覆蓋肺樟,這樣解壓縮后相當(dāng)于自己損壞了代碼。想在Windows上解壓縮jar的開發(fā)人員可以使用此選項(xiàng)關(guān)閉此行為逻淌。請(qǐng)注意么伯,混淆的jar將因此變大。僅在混淆時(shí)適用卡儒。
-keeppackagenames [package_filter]田柔,指定不混淆指定的包名。package_filter是一系列由逗號(hào)分隔的包名骨望。包名可以包含硬爆?,*和**通配符擎鸠,并且可以在其前面加上诊赊!否定限定符民逼。僅在混淆時(shí)適用。
-flattenpackagehierarchy [package_name],指定父包名趾疚,將重命名后的所有包移動(dòng)到指定父包下重新封包缭嫡。不帶參數(shù)或帶空字符串('')的包將被移入根目錄痛侍。此選項(xiàng)可以進(jìn)一步混淆包名钥弯。它可以使處理后的代碼更小,更難理解垂寥。僅在混淆時(shí)適用颠黎。
-repackageclasses [package_name]另锋,指定通過(guò)將所有重命名的類文件移動(dòng)到單個(gè)給定的包中來(lái)重新打包它們。不帶參數(shù)或帶空字符串('')的狭归,整個(gè)包將被完全刪除夭坪。該選項(xiàng)將覆蓋-flattenpackagehierarchy選項(xiàng)。這是進(jìn)一步混淆軟件包名稱的另一個(gè)示例过椎。它可以使處理后的代碼更小室梅,更難以理解。它不建議使用的名稱是-defaultpackage疚宇。僅在混淆時(shí)適用亡鼠。如果將類移動(dòng)到其他位置,則在其包目錄中查找資源文件的類將無(wú)法正常工作敷待。有疑問(wèn)的情況下间涵,請(qǐng)不要使用此選項(xiàng),以保持包裝原封不動(dòng)榜揖。
-keepattributes [attribute_filter]勾哩,指定要保留的所有可選屬性【儆矗可以使用一個(gè)或多個(gè)-keepattributes指令指定屬性思劳。attribute_filter是一系列由逗號(hào)分隔的屬性名稱。同樣的妨猩,屬性名稱可以包含敢艰?,*和**通配符册赛,并且可以在其前面加上!否定限定符震嫉。典型的可選屬性有:Exceptions森瘪,Signature,Deprecated票堵,SourceFile扼睬,SourceDir,LineNumberTable悴势,LocalVariableTable窗宇,LocalVariableTypeTable,Synthetic特纤,EnclosingMethod军俊,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations捧存,RuntimeVisibleParameterAnnotations粪躬,RuntimeInvisibleParameterAnnotations担败,AnnotationDefault。還可以將內(nèi)部類的屬性名稱作為源文件的一部分镰官,指定定內(nèi)部類的屬性名稱提前。例如,在處理庫(kù)時(shí)泳唠,至少應(yīng)保留Exceptions狈网,InnerClasses和Signature屬性。還應(yīng)該保留SourceFile和LineNumberTable屬性笨腥,以產(chǎn)生有用的混淆堆棧跟蹤拓哺。最后,如果您的代碼依賴注解扇雕,則可能需要保留注解拓售。僅在混淆時(shí)適用。
-keepparameternames镶奉,指定保留參數(shù)名稱和類型的方法础淤。此選項(xiàng)實(shí)際上保留了精簡(jiǎn)版本的調(diào)試屬性LocalVariableTable和LocalVariableTypeTable。在處理庫(kù)時(shí)哨苛,它很有用鸽凶。某些IDE可以使用這些信息來(lái)幫助使用該庫(kù)的開發(fā)人員,例如提供工具提示或自動(dòng)完成功能建峭。僅在混淆時(shí)適用玻侥。
-renamesourcefileattribute [string],指定要放入類文件的SourceFile屬性(和SourceDir屬性)中的常量字符串亿蒸。請(qǐng)注意凑兰,必須首先保留該屬性,因此還必須使用-keepattributes指令顯式地保留該屬性边锁。例如姑食,您可能希望處理后的庫(kù)和應(yīng)用程序生成有用的混淆堆棧跟蹤。僅在混淆時(shí)適用茅坛。
-adaptclassstrings [class_filter]音半,指定與類名相對(duì)應(yīng)的字符串常量也應(yīng)該被混淆。如果沒(méi)有過(guò)濾器贡蓖,則將修改所有與類名稱相對(duì)應(yīng)的字符串常量曹鸠。使用過(guò)濾器時(shí),僅匹配與過(guò)濾器匹配的類中的字符串常量斥铺。例如彻桃,如果您的代碼包含大量引用類的硬編碼字符串,并且您不想保留其名稱仅父,則可能要使用此選項(xiàng)叛薯。主要適用于混淆時(shí)浑吟,但相應(yīng)的類也會(huì)在壓縮步驟中自動(dòng)保留。
-adaptresourcefilenames [file_filter]耗溜,根據(jù)相應(yīng)類文件的混淆名稱指定要重命名的資源文件组力。如果沒(méi)有過(guò)濾器,則將與類文件對(duì)應(yīng)的所有資源文件重命名抖拴。使用過(guò)濾器燎字,僅重命名匹配的文件。僅在混淆時(shí)適用阿宅。
-adaptresourcefilecontents [file_filter]候衍,指定要更新其內(nèi)容的資源文件。資源文件中提到的任何類名都將根據(jù)相應(yīng)類的混淆名進(jìn)行重命名洒放。沒(méi)有過(guò)濾器蛉鹿,所有資源文件的內(nèi)容都會(huì)更新。使用過(guò)濾器往湿,僅更新匹配的文件妖异。使用平臺(tái)的默認(rèn)字符集來(lái)解析和寫入資源文件。您可以通過(guò)設(shè)置環(huán)境變量LANG或Java系統(tǒng)屬性file.encoding來(lái)更改此默認(rèn)字符集领追。
5.6 預(yù)檢選項(xiàng)(Preverification Options)
-dontpreverify他膳,指定不預(yù)先驗(yàn)證已處理的類文件。 默認(rèn)情況下绒窑,如果目標(biāo)版本是Java Micro Edition或Java 6或更高版本棕孙,類文件將進(jìn)行預(yù)驗(yàn)證。 對(duì)于Java Micro Edition些膨,需要進(jìn)行預(yù)驗(yàn)證蟀俊,因此,如果指定此選項(xiàng)订雾,則需要在已處理的代碼上運(yùn)行外部預(yù)驗(yàn)證器欧漱。 對(duì)于Java 6,尚不需要進(jìn)行預(yù)驗(yàn)證葬燎,但可以提高Java虛擬機(jī)中類加載的效率。android項(xiàng)目中一般會(huì)配置此選項(xiàng)缚甩。
-microedition谱净,指定已處理的類文件針對(duì)Java Micro Edition。 然后擅威,預(yù)驗(yàn)證器將添加適當(dāng)?shù)腟tackMap屬性壕探,該屬性與Java Standard Edition的默認(rèn)StackMapTable屬性不同。 例如郊丛,如果您正在處理Midlet李请,則將需要此選項(xiàng)瞧筛。
5.7 通用選項(xiàng)(General Options)
-verbose,指定在處理期間寫出更多信息导盅。如果程序因異常終止较幌,則此選項(xiàng)將打印出整個(gè)堆棧跟蹤,而不僅僅是異常消息白翻。
-dontnote [class_filter]乍炉,指定不打印有關(guān)配置中潛在的錯(cuò)誤或遺漏,例如類名中的錯(cuò)字或缺少可能有用的選項(xiàng)滤馍〉呵恚可選過(guò)濾器是一個(gè)正則表達(dá)式。設(shè)置后巢株,proguard不會(huì)打印匹配有關(guān)名稱的類的注釋槐瑞。
-dontwarn [class_filter],指定不警告尚未解決的引用和其他重要問(wèn)題阁苞±ч荩可選過(guò)濾器是一個(gè)正則表達(dá)式; proguard不會(huì)打印匹配有關(guān)名稱的類的警告猬错。忽視警告可能很危險(xiǎn)窗看,如果未處理的類或類成員確實(shí)需要進(jìn)行處理,則處理后的代碼將無(wú)法正常運(yùn)行倦炒。僅當(dāng)我們知道沒(méi)有風(fēng)險(xiǎn)時(shí)才使用此選項(xiàng)显沈,例如,android的默認(rèn)混淆配置中忽略了support包或androidx包中所有類的警告逢唤,因?yàn)閟upport包或androidx包一般不會(huì)出什么問(wèn)題拉讯。
-ignorewarnings,指定打印有關(guān)未解決的引用和其他重要問(wèn)題的任何警告鳖藕,但在任何情況下都將繼續(xù)處理魔慷。
-printconfiguration [filename],指定打印已解析的整個(gè)配置著恩,包括所包含的文件和替換的變量院尔。結(jié)構(gòu)被打印到標(biāo)準(zhǔn)輸出或給定的文件。有時(shí)這對(duì)于調(diào)試配置或?qū)ML配置轉(zhuǎn)換為更具可讀性的格式很有用喉誊。
-dump [filename]邀摆,指定在進(jìn)行任何處理后輸出該類文件的內(nèi)部結(jié)構(gòu)。結(jié)構(gòu)被打印到標(biāo)準(zhǔn)輸出或給定的文件伍茄。例如栋盹,您可能想要輸出給定jar文件的內(nèi)容,但不想進(jìn)行任何處理敷矫。
5.8 類路徑(Class Paths)
proguard接受通用化的類路徑來(lái)指定輸入文件和輸出文件例获。類路徑由許多條目組成汉额,這些條目由傳統(tǒng)的路徑分隔符分隔(例如,在Unix上為“:”榨汤,在Windows平臺(tái)上為“;”)蠕搜。在重復(fù)的情況下,按條目的順序決定優(yōu)先級(jí)件余。
每個(gè)輸入條目可以是:
- 一個(gè)類文件或資源文件讥脐,
- 包含以上任何內(nèi)容的jar文件,
- 包含以上任何內(nèi)容的war文件啼器,
- 包含以上任何內(nèi)容的ear文件旬渠,
- 包含以上任何內(nèi)容的zip文件,
- 包含以上任何內(nèi)容的目錄端壳。
直接指定的多個(gè)類文件和資源文件的路徑將被忽略告丢,因此多個(gè)類文件通常應(yīng)為jar文件,war文件损谦,ear文件岖免,zip文件或目錄的一部分。此外照捡,在歸檔或目錄內(nèi)的類文件的路徑不應(yīng)有任何其他目錄前綴颅湘。
每個(gè)輸出條目可以是:
- 一個(gè)jar文件,其中將包含了所有已處理的類文件和資源文件栗精。
- 一個(gè)war文件闯参,包含了以上所有內(nèi)容,
- 一個(gè)ear文件悲立,包含了以上所有內(nèi)容鹿寨,
- 一個(gè)zip文件,包含了以上所有內(nèi)容薪夕,
- 一個(gè)目錄脚草,包含了以上所有內(nèi)容。
指定輸出條目后原献,proguard通常會(huì)以合理的方式打包結(jié)果馏慨,并根據(jù)需要重新構(gòu)造輸入條目。一般是直接將所有輸入內(nèi)容寫入輸出目錄姑隅,輸出目錄將包含輸入條目的完整重構(gòu)熏纯。但是打包過(guò)程是可以自定義的,我們也可以將文檔也打包到輸出目錄粤策,重新生成zip文件,如果有此需要误窖,請(qǐng)參考使用手冊(cè)的restructure output archives.一節(jié)叮盘。
此外秩贰,proguard還可以根據(jù)其完整的相對(duì)文件名來(lái)過(guò)濾類路徑及其內(nèi)容。每個(gè)類路徑后面都可以跟隨多達(dá)5種類型的文件過(guò)濾器柔吼,過(guò)濾器以括號(hào)包涵毒费,不同類型的過(guò)濾器以分號(hào)分隔,相同類型的過(guò)濾器以逗號(hào)分隔:
- zip名稱過(guò)濾器愈魏,
- ear名稱過(guò)濾器觅玻,
- war名稱過(guò)濾器,
- jar名稱過(guò)濾器培漏,
- 類文件名和資源文件名的過(guò)濾器溪厘。
如果指定的過(guò)濾器少于5個(gè),則假定它們是后者牌柄。任何空的過(guò)濾器都將被忽略畸悬。類路徑的過(guò)濾格式一般如下所示(方括號(hào)“ []”表示其內(nèi)容是可選的):
classpathentry([[[[zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter)
例如,“ rt.jar(java/**.class珊佣,javax/**.class)”匹配rt jar內(nèi)java和javax目錄中的所有類文件蹋宦。
例如,“ input.jar(!**.gif咒锻,images/**)”匹配input.jar內(nèi)images目錄中的所有文件冷冗,但gif文件除外。
注意惑艇,不同的過(guò)濾器將應(yīng)用于所有相應(yīng)的文件類型蒿辙,而不管其在輸入中的嵌套級(jí)別如何; 它們是正交的敦捧。
例如须板,“ input.war(lib/**.jar,support/**.jar; **.class兢卵,**.gif)”僅考慮input.war中l(wèi)ib和support目錄中的jar文件习瑰,而不考慮 其他jar文件。 然后秽荤,它將匹配所有遇到的類文件和gif文件甜奄。
5.9 文件名(File Names)
proguard接受基于絕對(duì)路徑和相對(duì)路徑的文件名和目錄名,相對(duì)路徑解釋如下:
- 如果設(shè)置了基準(zhǔn)目錄窃款,則相對(duì)于基本目錄(前面提到的basedirectory選項(xiàng))课兄,否則
- 如果指定了配置文件的位置,則相對(duì)于指定配置文件的位置晨继,否則
- 相對(duì)于工作目錄烟阐。
名稱可以包含以'<'和'>'分隔的Java系統(tǒng)屬性。系統(tǒng)屬性將自動(dòng)替換為其各自的值。例如蜒茄,<java.home>/lib/rt.jar將自動(dòng)擴(kuò)展為/usr/local/java/jdk/jre/lib/rt.jar唉擂。同樣,<user.home>將擴(kuò)展到用戶的主目錄檀葛,而<user.dir>將擴(kuò)展到當(dāng)前的工作目錄玩祟。
帶有特殊字符(如空格和括號(hào))的名稱必須用單引號(hào)或雙引號(hào)引起來(lái)。請(qǐng)注意屿聋,名稱列表中的每個(gè)文件名都必須單獨(dú)引用空扎。另請(qǐng)注意,在命令行上使用引號(hào)本身時(shí)可能需要轉(zhuǎn)義润讥。例如转锈,在命令行上,可以使用 '-injars "my program.jar":"/your directory/your program.jar"'.之類的選項(xiàng)象对。
5.10 過(guò)濾器(Filters)
為了配置的處理過(guò)程中的諸多方面黑忱,proguard提供了許多帶有過(guò)濾器的選項(xiàng):文件名,目錄名勒魔,類名甫煞,包名,屬性名冠绢,優(yōu)化名等抚吠。而過(guò)濾器是可以包含通配符的逗號(hào)分隔名稱的列表。 只有與列表中的項(xiàng)目匹配的名稱才能通過(guò)過(guò)濾器弟胀,輸入文件中只有具有匹配文件名的文件才被讀瓤Α;輸出文件中只有具有匹配文件名的文件才被被寫入孵户。 支持的通配符取決于使用過(guò)濾器的名稱的類型萧朝,但是以下通配符是通用的:
5.11 保留選項(xiàng)修飾符(Keep Option Modifiers)
- allowshrinking,允許壓縮夏哭,指定-keep選項(xiàng)中指定的入口點(diǎn)可以壓縮检柬,即使必須保留這些入口點(diǎn)也可以。 也就是說(shuō)竖配,可以在壓縮步驟中移除入口點(diǎn)何址,但是如果確有必要,可以不對(duì)其進(jìn)行優(yōu)化或混淆进胯。
- allowoptimization用爪,允許優(yōu)化,指定可以優(yōu)化-keep選項(xiàng)中指定的入口點(diǎn)胁镐,即使必須保留這些入口點(diǎn)也是如此偎血。 也就是說(shuō)诸衔,可以在優(yōu)化步驟中更改入口點(diǎn),但不能將其刪除或混淆颇玷。 該修飾符僅對(duì)實(shí)現(xiàn)不常用要求有用署隘。
- allowobfuscation,允許混淆亚隙,指定-keep選項(xiàng)中指定的入口點(diǎn)可能會(huì)被混淆,即使必須保留它們也是如此违崇。 也就是說(shuō)阿弃,入口點(diǎn)可以在混淆步驟中重命名,但是它們可能不會(huì)被刪除或優(yōu)化羞延。 該修飾符僅對(duì)達(dá)到不常用要求有用渣淳。
5.12 類規(guī)范(Class Specifications)
類規(guī)范是類和類成員(字段和方法)的模板。 它在各種-keep選項(xiàng)和-assumenosideeffects選項(xiàng)中使用伴箩。 相應(yīng)的選項(xiàng)僅適用于與模板匹配的類和類成員入愧。這種模板看起來(lái)非常像Java代碼,還有一些帶有通配符的擴(kuò)展名嗤谚。 例如:
[@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)“ ...”表示可以指定任何數(shù)量的前述項(xiàng)目。豎線“ |”分隔兩個(gè)選項(xiàng)巩步。
括號(hào)“()”表示同一分組旁赊。
縮進(jìn)是為了看起來(lái)直觀一些,空格在實(shí)際配置文件中無(wú)關(guān)緊要椅野。
class關(guān)鍵字是指任何接口或類终畅。 interface關(guān)鍵字將匹配限制為接口類。 enum關(guān)鍵字將匹配限制為枚舉類竟闪。在接口或枚舉關(guān)鍵字之前加离福!將匹配分別限制為不是接口或枚舉的類。
每個(gè)類別名稱必須完整的類名炼蛤,例如java.lang.String妖爷。但類名可以使用通配符“?”鲸湃,“*”赠涮, “**”、否定限定符 “暗挑!”笋除。
extends和implements 通常用于使用通配符限制類。一個(gè)用于類炸裆,一個(gè)用于接口垃它,可以看做是等效的,只能匹配擴(kuò)展或?qū)崿F(xiàn)了指定類的類,即某個(gè)類或接口的子類国拇。請(qǐng)注意洛史,匹配的類不包括該類本身。如果需要酱吝,應(yīng)在單獨(dú)的選項(xiàng)中指定該類也殖。
@ specifications可用于將類和類成員限制為使用指定注釋類型進(jìn)行注釋的成員。指定注釋類型就像類名一樣务热。
字段和方法的指定與Java中的指定非常相似忆嗜,除了方法參數(shù)列表不包含參數(shù)名稱(就像在Javadoc和javap等其他工具中一樣)。規(guī)范還可以包含以下所有通配符:
(1)<init>匹配任何構(gòu)造函數(shù)崎岂。
(2)<fields>匹配任何字段捆毫。
(3)<methods>匹配任何方法。
(4)* 匹配任何字段或方法冲甘。
請(qǐng)注意绩卤,上述通配符沒(méi)有返回類型。僅<init>通配符具有參數(shù)列表江醇。
字段和方法也可以使用正則表達(dá)式指定濒憋。名稱可以包含以下通配符:
(1)?匹配方法名稱中的任何單個(gè)字符嫁审。
(2)* 匹配方法名稱的任何部分跋炕。
描述符中的類型可以包含以下通配符:
(1)% 匹配任何原始類型(“ boolean”,“ int”等律适,但不匹配“ void”)辐烂。
(2)? 匹配類名稱中的任何單個(gè)字符捂贿。
(3)* 與不包含包分隔符的類名的任何部分匹配纠修。
(4)** 匹配類名的任何部分,可能包含任意數(shù)量的包分隔符厂僧。
(5)*** 匹配任何類型(基本類型或非基本類型扣草,數(shù)組或非數(shù)組)。
(6)... 匹配任何類型的任意數(shù)量的參數(shù)颜屠。
請(qǐng)注意辰妙,?甫窟,*和**通配符將永遠(yuǎn)與基本類型不匹配密浑。此外,僅***通配符將匹配任何維度的數(shù)組類型粗井。例如尔破,“ ** get *()”匹配“ java.lang.Object getObject()”街图,但不匹配“ float getFloat()”,也不匹配“ java.lang.Object [] getObjects()”懒构。也可以使用其簡(jiǎn)短的類名(不帶包名)或使用其完整的類名來(lái)指定構(gòu)造函數(shù)餐济。與Java語(yǔ)言一樣,構(gòu)造函數(shù)規(guī)范具有參數(shù)列表胆剧,但沒(méi)有返回類型絮姆。
類和類成員的訪問(wèn)修飾符通常用于限制通配類和類成員。只有設(shè)置了相同的訪問(wèn)修飾符才能匹配秩霍。前面的滚朵!代表不能設(shè)置該訪問(wèn)修飾符。允許組合多個(gè)修飾符(例如public static)前域。這意味著必須設(shè)置兩個(gè)訪問(wèn)修飾符(例如public和static),除非它們發(fā)生沖突韵吨,否則必須至少設(shè)置其中一個(gè)(例如public或protected)匿垄。
最后,本文翻譯內(nèi)容居多归粉,部分內(nèi)容翻譯之后重新組織了語(yǔ)言進(jìn)行描述椿疗,可能有不正確或者不準(zhǔn)確的地方,歡迎交流糠悼、指正届榄。