1. 程式人生 > >ProGuard 混淆規則整理

ProGuard 混淆規則整理

filters 構造函數 ati 函數 tar 解析 enum 程序 跟蹤

說明:本文參考(翻譯)自Android SDK根目錄下的proguard目錄下的說明文檔,是其中的一篇。,文中除了翻譯外加了一些作者的實際驗證。文章對Android混淆規則做一個解釋說明。作者才疏學淺,如有錯誤,請諒解!<@_@>
另可參考官網ProGuard manual

Input/Output Options 輸入輸出選項

-include filename
遞歸引入目錄的配置文件
-basedirectory directoryname
-injars class_path
指定應用程序要處理的jars包(或者wars、ears、zip、或者目錄結構),它們裏面的class文件會被處理並被寫入到輸出jars裏面。它們裏面的任何非class文件會被直接復制過去但是不會處理。(需要註意過濾調一些IDE自動生成的文件);
-outjars

class_path
指定輸出jars(wars、ears、zip、目錄結構)的名稱;由-injars 指定的被處理的jars將被寫入到指定的輸出jars裏。如果不指定outjars將不會有class文件被寫入。
-libraryjars class_path 不混淆指定的jar庫(android 項目中一般不混淆引入的第三方類庫)
-skipnonpubliclibraryclasses 不混淆指定jars中的非public calsses
-dontskipnonpubliclibraryclasses 不忽略指定jars中的非public calsses (默認選項)和上面的選手想對
-dontskipnonpubliclibraryclassmembers

不忽略指定類庫的public類成員(變量和方法),默認情況下,ProGuard會忽略他們
-keepdirectories [directory_filter] 指定要保持的目錄結構,默認情況下會刪除所有目錄以減小jar的大小。
-target version
指定java版本號。 版本號可以是1.0,1.1,1.2,1.3,1.4,1.5(或僅5),1.6(或僅6)或1.7(或僅7)中的一個。 默認情況下,類文件的版本號保持不變。 例如,您可能想要將類文件升級到Java 6,通過更改其版本號並對其進行預驗證。
-forceprocessing 強制處理輸入(-injars)jars。即使輸出jars是最新的。通過指定的輸入,輸出和配置文件或者目錄的時間戳判斷是否最新。

Keep Options 保留選項

-keep [,modifier,...] class_specification
指定需要保留的類和類成員(作為公共類庫,應該保留所有可公開訪問的public方法)
-keepclassmembers [,modifier,...] class_specification
指定需要保留的類成員:變量或者方法
-keepclasseswithmembers [,modifier,...] class_specification
指定保留的類和類成員,條件是所指定的類成員都存在(既在壓縮階段沒有被刪除的成員,效果和keep差不多)
-keepnames class_specification
[-keep allowshrinking class_specification 的簡寫]
指定要保留名稱的類和類成員,前提是在壓縮階段未被刪除。僅用於模糊處理
-keepclassmembernames class_specification
[-keepclassmembers allowshrinking class_specification 的簡寫]
指定要保留名稱的類成員,前提是在壓縮階段未被刪除。僅用於模糊處理
-keepclasseswithmembernames class_specification
[-keepclasseswithmembers allowshrinking class_specification 的簡寫]
指定要保留名稱的類成員,前提是在壓縮階段後所指定的類成員都存在。僅用於模糊處理
-printseeds [filename]
指定詳盡列出由各種-keep選項匹配的類和類成員。 列表打印到標準輸出或給定文件。 該列表可用於驗證是否真的找到了預期的類成員,特別是如果您使用通配符。 例如,您可能想要列出所有應用程序或您保存的所有小程序。

Keep選項概述對比(Overview of Keep Options)

作用範圍保持所指定類、成員所指定類、成員在壓縮階段沒有被刪除,才能被保持
類和類成員 -keep -keepnames
僅類成員 -keepclassmembers -keepclassmembernames
類和類成員(前提是成員都存在) -keepclasseswithmembers -keepclasseswithmembernames
  • 建議初學者,如果不確定用那個keep選項就盡量用-keep,這個比較簡單且不易使混淆代碼出錯。
  • 如果只指定類被保留,那麽它的成員同樣可能會被壓縮、優化或者混淆。
  • 如果指定類成員被保留,那麽其他代碼也有可能會被壓縮、優化或者混淆

Keep命令修飾符說明 [,modifier,...] :

可用在keep、keepclassmembers、keepclasseswithmembers命令後面

  • allowshrinking 指定對象可能會被壓縮,即使他們被keep選項保留。如果所指定的對象不是必需的,則他們可能會被刪出(在壓縮步驟)。反之如果他們是必須的,則他們可能不會被優化或者混淆。

如: 類ClassOneThree在代碼中並沒有被引用,是一個沒有使用的類.
-keep class com.dev.demo.one.ClassOneThree* {*;}

-keep命令讓上ClassOneThree保留下來,但是如果加了allowshrinking 修飾符結果就不一樣了,下面這條命令效果其實是一樣的:ClassOneThree在壓縮階段就被刪除了,沒有被保留,因為它並沒有被引用到。

-keepnames class com.dev.demo.one.ClassOneThree* {*;}

- *** allowoptimization *** 指定對象可能會被優化,即使他們被keep選項保留。所指定對象可能會被改變(優化步驟),但可能不會被混淆或者刪除。該修飾符只對實現異常要求有用。
- *** allowobfuscation *** 指定對象可能會被混淆,即使他們被keep保留。所指定對象可能會被重命名,但可能不會被刪除或者優化。該修飾符只對實現異常要求有用。
***
###Shrinking Options *壓縮選項*
壓縮是默認開啟的。壓縮會刪除沒有使用的類以及類成員,除了由各種“-keep”選項列出的類和它們直接或間接依賴的類。 在每個優化步驟之後都會進行壓縮步驟,因為一些優化後可能會出現更多可以刪除的類及類成員
**-dontshrink**
關閉壓縮
**-printusage** [*filename*]
列出被那些未使用的代碼,並可輸出到指定文件。僅用於收縮階段
**-whyareyoukeeping** *class_specification*
打印指定的類在壓縮階段為什麽會保留其類、類成員的詳細信息
***
###Optimization Options *優化選項*
優化是默認情況下啟用的;。所有方法都在字節碼級進行優化。
**-dontoptimize**
關閉優化
**-optimizations** *optimization_filter*
指定更精細級別的優化,僅用於優化階段
**-optimizationpasses** *n*
指定優化次數,默認情況下1次。多次可以更進一步優化代碼,如果在一次優化後沒有改進則優化結束。僅用於優化階段
**-assumenosideeffects** *class_specification*
指示沒有任何副作用的類方法。優化過程中如果確定這些方法沒有被調用或者返回值沒有被使用則刪除它們。ProGuard會分析出庫代碼以外的程序代碼。如指定System.currentTimeMillis()方法,任何對他的空閑調用將被刪除。僅實用與優化。慎用!
**-allowaccessmodification**
指示容許修改類和類成員的訪問修飾符,這可以改進優化結果。
**-mergeinterfacesaggressively**
指示容許合並接口,及時他們的實現類沒有實現所有接口方法。這個可以減少類的總數來減小輸出的大小。僅實用於優化
***
###Obfuscation Options  *混淆*
混淆是默認開啟的。混淆使類和類成員名稱變成短的隨機名,被各種“-keep”選項保護的類、類成員除外。對調試有用的內部屬性(源文件名稱,變量名稱,行號)將被刪除。
**-dontobfuscate**
關閉混淆
**-printmapping** [*filename*]
打印舊名稱到重命名的類、類成員的新名稱的映射關系,可輸出到指定文件。
僅實用於混淆處理
**-applymapping** [*filename*]
指定文件為映射文件,混淆時映射文件中列出的類和類成員接收指定的名稱,文件未提及的類和類成員接收新名稱。
**-obfuscationdictionary** [*filename*]
指定一個文本文件,其中所有有效字詞都用作混淆字段和方法名稱。 默認情況下,諸如“a”,“b”等短名稱用作混淆名稱。 使用模糊字典,您可以指定保留關鍵字的列表,或具有外來字符的標識符,例如: 忽略空格,標點符號,重復字和#符號後的註釋。 註意,模糊字典幾乎不改善混淆。 有些編譯器可以自動替換它們,並且通過使用更簡單的名稱再次混淆,可以很簡單地撤消該效果。 最有用的是指定類文件中通常已經存在的字符串(例如Code),從而減少類文件的大小。 僅適用於混淆處理。
**-classobfuscationdictionary** [*filename*]
指定一個文本文件,其中所有有效詞都用作混淆類名。 與-obfuscationdictionary類似。 僅適用於混淆處理。
**-packageobfuscationdictionary** [*filename*]
指定一個文本文件,其中所有有效詞都用作混淆包名稱。與-obfuscationdictionary類似。 僅適用於混淆處理。
 **-overloadaggressively**
深度重載混淆。這個選項可能會使參數和返回類型不同的方法和屬性混淆後獲得同樣的名字,這個選項會使代碼更小(且不易理解),僅實用於混淆處理 

**-useuniqueclassmembernames**
指定為具有相同名稱的類成員分配相同的混淆名稱,並為具有不同名稱(對於每個給定類成員簽名)的類成員分配不同的混淆名稱。 沒有該選項,更多的類成員可以映射到相同的短名稱,如ab等。因此選項稍微增加了生成的代碼的大小,但它確保保存的混淆名稱映射可以始終 在隨後的增量混淆步驟中受到尊重。

**-dontusemixedcaseclassnames**
混淆時不生成大小寫混合的類名。 默認情況下,混淆的類名稱可以包含大寫字符和小寫字符的混合。僅適用於混淆處理。

**-keeppackagenames** [*package_filter*]
指定不混淆給定的包名稱。*package_filter*過濾器是以逗號分隔的包名稱列表。包名稱可以包含 **?**、** * **、** ** ** 通配符。僅適用於混淆處理。

**-flattenpackagehierarchy** [*package_name*]
將所有重命名後的包移動到給定的包中重新打包,如果沒有參數或者空字符串,包將被移動到根包中,此選項進一步混淆包名稱,可以使代碼跟小更不易理解。僅適用於混淆處理。
**-repackageclasses** [*package_name*]
重新打包所有重命名的類文件,將它們移動到給定包中。 如果包中沒有參數或一個空字符串,包被完全刪除。 此選項將覆蓋-flattenpackagehierarchy選項。  它可以使處理後的代碼更小,更不容易理解。 它的已棄用名稱是-defaultpackage。 僅適用於混淆處理。
**-keepattributes** [*attribute_filter*]
指定要保留的任何可選屬性(註釋...)。 可以使用一個或多個-keepattributes指令指定屬性。 *attribute_filter*過濾器是以逗號分隔的屬性名稱列表。如果代碼依賴於註釋,則可能需要保留註釋。 僅適用於混淆處理。
**-keepparameternames**
指定保留參數名稱和保留的方法類型。 此選項實際上保留調試屬性LocalVariableTable和LocalVariableTypeTable的修剪版本。 它在處理庫時很有用。 一些IDE可以使用該信息來幫助使用庫的開發人員,例如使用工具提示或自動完成。僅適用於混淆處理。
**-renamesourcefileattribute** [string]
指定要放在類文件的SourceFile屬性(和SourceDir屬性)中的常量字符串。 請註意,該屬性必須以開頭存在,因此也必須使用-keepattributes指令明確保留。 例如,您可能希望使已處理的庫和應用程序生成有用的混淆堆棧跟蹤。僅適用於混淆處理。
**-adaptclassstrings** [*class_filter*]
混淆和類名稱對應的字符串常量,如果沒有filter則匹配與類名稱相對應的所有字符串常量,使用filter則僅匹配與filter匹配的類中的字符串常量。 僅適用於混淆處理。
**-adaptresourcefilenames** [*file_filter*]
根據相應類文件(如果有)的混淆名稱指定要重命名的資源文件。 如果沒有過濾器,則重命名與類文件相對應的所有資源文件。 使用過濾器,僅重命名匹配的文件。僅適用於混淆處理。
**-adaptresourcefilecontents** [*file_filter*]
指定要更新其內容的資源文件。 根據相應類的混淆名稱(如果有)重命名資源文件中提到的任何類名。 如果沒有過濾器,所有資源文件的內容都會更新。 使用過濾器,僅更新匹配的文件。 資源文件使用平臺的默認字符集進行解析和編寫。 您可以通過設置環境變量LANG或Java系統屬性file.encoding來更改此默認字符集。 僅適用於混淆處理。
***
###Preverification Options *預驗證*
預驗證默認情況下啟動的。能提高虛擬機的加載效率。
**-dontpreverify**
關閉預驗證
**-microedition**
指定已處理的類文件針對Java Micro Edition。 然後,preverifier將添加適當的StackMap屬性,這些屬性與Java Standard Edition的默認StackMapTable屬性不同。 例如,如果您正在處理midlet,則需要此選項。
***
###General Options *一般選項*
**-verbose**
混淆過程中打印詳細信息,如果異常終止則打印整個堆棧信息
**-dontnote** [*class_filter*]
不打印配置類中可能的錯誤或遺漏的註釋,如類名稱中的拼寫錯誤,或者可能有用的缺失選項。 可選*class_filter*是正則表達式; ProGuard不打印關於具有匹配名稱的類的註釋。
**-dontwarn** [*class_filter*]
不對指定的類、包中的不完整的引用發出警告
**-ignorewarnings**
忽略警告繼續處理
**-printconfiguration** [*filename*]
打印配置信息,到指定文件。包括文件和替換的變量
**-dump** [*filename*]
打印類文件內部結構到指定文件
***
###<p id=classpath>Class Paths</p>
*class_paths* 主要用以指示選項(***-injars、-outjars、-libraryjars***)的輸入輸出文件路徑。ProGuard接受類路徑的泛化。由一系列文件和分隔符組成(在Unix上為***:***,windows上為***;***);文件的順序決定著優先級。
***輸入項可包括:***
  - class 文件或者 resource文件;
  - jar 文件, 可包含上述任意文件;
  - war文件,可包含上述任意文件;
  - ear文件, 可包含上述任意文件;
  - zip文件, 可包含上述任意文件;
  - 包含上面任意文件的目錄結構。

直接指定類文件或者資源文件的路徑會被忽略,類文件可作為jar文件、war、ear、zip或者目錄結構的一個部分。此外,類文件的路徑在歸檔或目錄中不應有任何其他目錄前綴。

***輸出項可包括:***
  - jar 文件, 包含了所處理的所有class文件或者資源文件;
  - war文件,包含上述任意文件;
  - ear文件, 包含上述任意文件;
  - zip文件, 包含上述任意文件;
  - 包含上面任意文件的目錄結構。

ProGuard 在輸出時,會以合理的形式打包輸出結果,根據需要重新構建輸入文件。將所有類容寫入輸出目錄是ProGuard最便捷的方式,輸出目錄包含了輸入類容完整的重構。包可以是任意復雜的,可以是處理整個應用程序,zip文件中的包及其文檔,將再次作為zip文件被處理。


ProGuard容許在文件名中稱使用過濾器,它們能相對於完整的文件名過濾條一些文件或者其內容,過濾器位於括號()之間,他能支持5個類型以“***;***隔開”:
  - 對所有的zip文件名稱的過濾;
  - 對所有的ear文件名稱的過濾;
  - 對所有的war文件名稱的過濾;
  - 對所有的jar文件名稱的過濾;
  - 對於所有的class文件名,資源文件名。

括號中的過濾器如果少於5個,則它們將被假設為最後一個過濾器,空的過濾器將被忽視, 格式如下:
***classpathentry([[[[zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter)***
***“[]”*** 表示其內容是可選的。

rt.jar(java/.class,javax/class)

匹配了rt.jar文件中,所有文件夾java、javax下的所有class文件

input.jar(!.gif,images/)

匹配了input.jar中,images文件夾下除.gif格式的所有文件


不同的過濾器可同時在一個條目中,它會被應用於所有相應的類型;不管它們的嵌套關系如何他們都是相交的

input.war(lib/.jar,support/jar;.class,.gif)

只包含了lib、support目錄下的jar文件,並且匹配了它其中的所有.class文件和.gif文件


一些例子:

所有輸出類和資源將被合並到一個jar文件中。

-injars classes
-injars in1.jar
-injars in2.jar
-injars in3.jar
-outjars out.jar

將classes目錄、in1.jar、in2.jar、in3.jar重構合並到out.jar文件中


如果想保留輸入的結構,則可以輸出到目錄結構(或者 war、ear、zip)中。

-injars in1.jar
-injars in2.jar
-injars in3.jar
-outjars out

輸入文件能夠保留原來文件名在目錄out中構建


通過***分組使用-injars和-outjars***可以靈活的將jars(wars、ears、zips 或者 directories)合並到不同的jars(wars、ears、zips 或者 directories)中:

-injars base_in1.jar
-injars base_in2.jar
-injars base_in3.jar
-outjars base_out.jar

-injars extra_in.jar
-outjars extra_out.jar

base_in*.jar文件會被合並到base_out.jar文件中,extra_in.jar被合並到extra_out.jar文件中。


過濾掉了所有images下的所有文件

-injars in.jar(!images/**)
-outjars out.jar

忽略掉java運行時不相關的類; “<>”表示引用系統變量java home(JAVA_HOME)

-libraryjars <java.home>/lib/rt.jar(java/,javax/)


-injars in.jar
-outjars code_out.jar(**.class)
-outjars resources_out.jar

**.class文件將被輸出到code_out.jar文件中,而所有其他文件將被輸出到resources_out.jar文件中。

***
###文件名稱(File Names)
ProGuard 接受***絕對路徑和相對路徑***作為文件或者路徑名。相對路徑規則為:

  - 首先相對與基礎路徑,如果沒有設置則:
  - 相對於指定的配置路徑,如果沒有則:
  - 相對於工作目錄。

名稱中可包含java 系統配置,用“<>”括號包裹。系統配置將自動替代它們:

"<java.home>/lib/rt.jar" 將被解釋為 "/usr/local/java/jdk/jre/lib/rt.jar."
"<user.home>" 解釋為用戶的Home目錄
"<user.dir>"解釋為當前的工作目錄

**註意:具有空格和括號的特殊字符名稱必須用單引號或者雙引號引用,名稱列表中的每個文件名必須單獨引用。在命令行中引號本身可能需要轉義,以避免shell誤解**
***
###文件過濾符號說明(File Filters)
>參考 *-adaptresourcefilenames、-adaptresourcefilecontents*

***“file_filter”*** 和一般的Filters一樣,是可以用逗號(",")分隔的一個過路調的文件列表,只讀取匹配到的文件,支持一下通配符:
**?** 匹配單個字符;
**\***  匹配任意多個字符,不包括目錄分隔符;
****** 匹配任意多個字符,不包括目錄分隔符;   
**!** 表示否(取反)。

java/.class,javax/.class //匹配java和javax目錄以及其子目錄下的所有.class文件
-keep class org.codehaus.jackson.* //保持org.codehaus.jackson下面的類文件,不包括其子包裏面類文件
-keep class org.codehaus.jackson.//保持org.codehaus.jackson下面所有類文件,包括其子包裏面類文件
!
.gif,images/** 匹配images目錄下面所有文件,但不包括.gif文件
-injars in.jar(!images/**) //指定輸入jar包,但移除images目錄下面的所有文件

***

###Filter
ProGuard 容許配置的很多選項(文件、目錄、類、包、屬性、優化...等的名稱)使用過濾器。可以使用逗號","分隔。

"foo,bar"匹配foo文件,和所有以bar結尾的名稱。
"!foobar,
bar 匹配所有bar結尾名稱,foobar除外。

***
### Class Specifications(類的書寫規範模版)

類規範是一個類和類成員的書寫模版。它主要用在 ***-keep、-assumenosideeffects***選項後面。它使得相應的選項只適應與模版匹配的類和類成員。
規範看起來類似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***關鍵字表示任何類或接口,***interface*** 僅指示接口,***enum***關鍵字僅指示枚舉;***interface,enum***前面加一個***"!"***表示非既是不是接口也不是枚舉。
- 每個***classname***必須是全名,如:***java.lang.String、com.example.ClassName***;同時***classname***還可以是包含下面一些通配符的正則表達式:

  ***?*** 問號匹配單個字符,但不能是包名分隔符;如:***"mypackage.Test?"***,能匹配***"mypackage.Test1""mypackage.Test2"***,但是不能匹配***"mypackage.Test12"***;
  
  ***\**** 單個星號能匹配任意多個字符,但除了包分隔符,如:***"mypackage.\*Test\*"***,能匹配***”mypackage.Test、mypackage.YourTestApplication“***,但是不能匹配***"mypackage.mysubpackage.MyTest"***; ***"mypackage.\*"***匹配包mypackage下面的類,但不包括它的子包裏面的類;
  ***\*\**** 雙星號匹配任意多個字符,包括包名的分隔符;如:***"\*\*.Test"***,匹配了所有Test類,***"mypackage.\*"***匹配了包mypackage下面的所有子類,包括它的子包的類。

- ***extends 、implements*** 指示一個特定的類,既是表示繼承或者是實現了指定的類(接口)的類才符合條件。
- ***@*** 指示被註釋類型註釋了的類或類成員。註釋類型與類名一樣被指定。
- 類成員、方法和java語法差不多,方法參數就想javadoc一樣,不用包含參數名稱。成員、方法也可以包含一些通配符:
***<init>*** 匹配了構造方法;
***<fields>*** 匹配了成員變量;
***<methods>*** 匹配了方法;
***\**** 匹配了任意成員、方法;
上面這些沒有任何返回類型,只有***"<init>"***有參數列表。
成員變量、方法也可以是包含通配符的正則表達式。可以包含以下通配符:
***?*** 匹配方法中任何單個字符;
***\**** 匹配方法中任何多個字符;
類型可以包含一下通配符
***%***  匹配方法中的原始類型(***"boolean""int"...***,不包括***"void"***);
***?*** 匹配類名中的單個字符;
***\**** 匹配類名中的多個字符,不包括包分隔符;
***\*\**** 匹配類名中的多個字符,包括包分隔符;
***\*\*\**** 匹配原始類型,非原始類型,數組、非數組
***\-\-\-*** 匹配任意數量和類別的參數。
註意:***?、\*、\*\**** 不會匹配原始類型,只有**\*\*\***可以;如:***"\*\* get\*()"***匹配***“java.lang.Object getObject()”*** 不會匹配***"float getFloat()"*** 也不匹配***"java.lang.Object[] getObjects()"***;
- 構造函數可以用短名(不含包名),或者全名(含包名);java中構造函數可以有參數,但沒有返回類型。
- 可以設置類,類成員訪問修飾符(***public,private...***),它們通常幫助限制通配符類和成員。只能匹配具有指定修飾符的類或者成員。**"!"**表示非。
  容許設置多個標識(***"public static"***),但如果它們是沖突的就只能設置一個(如***public***和***private***)

ProGuard支持編譯器設置的一些修飾符***synthetic、bridge、varargs***;

下面是一些簡單的列子:

保持了類ClassOneOne裏面所有public 修飾的成員和方法

-keepclassmembernames class com.dev.demo.one.ClassOneOne {
public *;
}

保持了ClassOne裏面public 修飾的構造函數

-keep class com.dev.demo.ClassOne {
public <init>();
}

保持了類ClassTwoTwo裏面public修飾的參數為int的構造函數

-keep class com.dev.demo.two.ClassTwoTwo {
public <init>(int);
}

保持了類ClassTwoThree 裏面public修飾的方法和private修飾的成員變量

-keepclassmember class com.dev.demo.two.ClassTwoThree {
public <methods>;
private <fields>;
}

保持了ClassTwoThree的子類以及裏面的成員和方法

-keep class * extends com.dev.demo.two.ClassTwoThree {*;}

保持了前綴為ClassOne的類以及裏面的成員和方法

-keepnames class com.dev.demo.one.ClassOne{;}

保持了ClassTwoTwo的內部類ClassTwoTwoInner裏面的成員和方法

-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}



作者:一件小毛衣
鏈接:https://www.jianshu.com/p/b471db6a01af
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。

ProGuard 混淆規則整理