Proguard 經常使用規則

思惟導圖

入口

爲了決定哪些代碼要被保留哪些代碼要出丟棄和混淆,必須指定入口點。這些入口點一般是 main方法,activity,service等。java

  • 在壓縮階段,Proguard從這些入口點開始遞歸肯定哪些類或類成員要被使用,其他的都會被丟棄。
  • 在優化階段,ProGuard 會進一步優化代碼。在其餘優化中,能夠將不是入口點的類和方法設爲 private,static 或 final ,刪除未使用的參數,而且能夠內聯一些方法。
  • 在混淆階段,ProGuard 會從新命名不屬於入口點的類和類成員。在整個過程當中,保證入口點仍然能夠經過其原始名稱訪問。

查看 Proguard 輸出結果

爲了不引入 bug 咱們有必要對 結果進行檢查。android

在Android中,開啓了混淆構建會在 <module-name>/build/outputs/mapping/ 目錄下會輸出如下文件:正則表達式

  • dump.txt 描述APK文件中全部類的內部結構
  • mapping.txt 提供混淆先後類、方法、類成員等的對照表
  • seeds.txt 列出沒有被混淆的類和成員
  • usage.txt 列出被移除的代碼

咱們能夠根據 seeds.txt 文件檢查未被混淆的類和成員中是否已包含全部指望保留的,再根據 usage.txt 文件查看是否有被誤移除的代碼。小程序

過濾器

ProGuard 爲許多配置提供了不一樣方面的過濾選項:文件名稱,目錄,類別,軟件包,屬性,優化等。

過濾器是能夠包含通配符的,以逗號分隔的,名稱列表。數組

只有與列表中的項目匹配的名稱纔會經過過濾器。微信

每種配置的通配符可能有所不一樣,但如下通配符是通用的:app

  • ? 匹配名稱中的任何單個字符。
  • * 匹配不包含包分隔符或目錄分隔符的名稱的任何部分
  • ** 匹配名稱的任何部分,可能包含任意數量的包分隔符或目錄分隔符。

此外,名稱前能夠加上否認感嘆號 ! 排除名稱與進一步嘗試匹配後續名稱。ide

所以,若是名稱與過濾器中的某個項目相匹配,則會當即接受或拒絕該項目,具體取決於項目是否具備否認符。函數

若是名稱與項目不匹配,則會針對下一個項目進行測試,依此類推。工具

它若是與任何項目不匹配,則根據最後一項是否具備否認符而被接受或拒絕。

如,"!foobar,*.bar" 匹配除了foobar以外的全部以bar結尾的名稱。

下面以過濾文件具體舉例。

文件過濾器

像通用過濾器同樣,文件過濾器是逗號分隔的文件名列表,能夠包含通配符。只有具備匹配文件名的文件被讀取(在輸入的狀況下),或者被寫入(在輸出的狀況下)。支持如下通配符:
  • ? 匹配文件名字中的任何單個字符
  • * 匹配不包含目錄分隔符的文件名的任何部分。
  • ** 匹配文件名的任何部分,能夠包含任意數目的目錄分隔符。

例如 "java/**.class ,javax/**.class" 能夠匹配 java和javax目錄下全部的 class 文件。

此外,文件名前面可能帶有感嘆號'!'將文件名排除在與後續文件名匹配上。

例如 "!**.gif,images/**" 匹配images目錄下全部除了 gif 的文件

關於更詳細的用法 能夠查看官方文檔 filtering

keep 選項

-keep [,modifier,...] class specification

指定類和類成員(字段,方法)做爲入口點被保留。

例如,爲了保留一個程序,你要指定Main方法和類。爲了保留一個庫,你應該指定全部被公開訪問的元素。

  • 保留 main 類和 main 方法
-keep public class com.example.MyMain { 
      public static void main(java.lang.String[]); 
}
  • 保留全部被公開訪問的元素
-keep public class * { 

      public protected *; 

}

Note:若是你只保留了類,沒有保留類成員,那麼你的類成員將不會被保留

例如 有一個實體類

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;

    }

}

規則配置以下

# 保留 Product類

-keep class cn.sintoon.camera.Product

usage.txt文件中有如下內容 ,能夠看到 類中的成員所有被移除了

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

指定要保留的類成員, 前提是它們的類也被保留了

例如,你想保留實現了 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;另外,若是隻保留了類成員沒有保留類跟沒有保留同樣

仍是拿上面那個例子,改一下規則

-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[]); 

}

仍是用上面那個例子,保留住類和全部的類成員

-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 中就會出現這個類和類成員

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:必定要注意指定的類成員必須存在,若是不存在的話,這個規則至關於沒有配,一點做用沒有

-keepnames class specification

-keep,allowshrinking class specification的簡寫

指定要保留名稱的類成員和類成員(若是它們在壓縮階段未被刪除)。

例如,你可能但願保留實現 Serializable 接口的類的全部類名,以便處理後的代碼與任何原始序列化的類保持兼容。

徹底不用的類仍然能夠刪除。只有在混淆時才適用。

-keepnames class * implements java.io.Serializable

Note: 前提是在壓縮階段沒有被刪除掉,這裏至關於使用了修飾符 allowshrinking

-keepclassmembernames class specification

-keepclassmembers,allowshrinking class specification 的簡寫

指定要保留名稱的類成員(若是它們在壓縮階段未被刪除)。

例如,在處理由JDK 1.2或更早版本編譯的庫時,可能但願保留合成類$方法的名稱。

因此當處理使用處理過的庫的應用程序時,混淆器能夠再次檢測到它(儘管ProGuard自己不須要這個)。

只有在混淆時才適用。

-keepclassmembernames class * { 

    java.lang.Class class$(java.lang.String); 

    java.lang.Class class$(java.lang.String, boolean); 
}

Note: 前提是在壓縮階段沒有被刪除掉,這裏至關於使用了修飾符 allowshrinking

-keepclasseswithmembernames class specification

-keepclasseswithmembers,allowshrinking class specification 的簡寫

指定要保留名稱的類和類成員,條件是全部指定的類成員都存在於收縮階段以後。

例如,可能但願保留全部本機方法名稱和類別的名稱,以便處理的代碼仍能夠與本機庫代碼連接。徹底沒有使用的本地方法仍然能夠被刪除。

若是使用了一個類文件,但它的本地方法都不是,它的名字仍然會被混淆。只有在混淆時才適用。

-keepclasseswithmembernames,includedescriptorclasses class * { 

    native <methods>; 

}

Note: 前提是在壓縮階段沒有被刪除掉,這裏至關於使用了修飾符 allowshrinking

-printseeds [filename]

指定詳盡列出由各類-keep選項匹配的類和類成員。列表打印到標準輸出或給定文件。該列表可用於驗證是否真的找到了預期的類成員,尤爲是在使用通配符的狀況下。

例如,您可能想要列出您保存的全部應用程序或全部小程序。

參考上面說的 seeds.txt

-whyareyoukeeping class specification

指定打印詳細信息,說明爲何給定的類和類成員正在壓縮步驟中。

若是想知道爲何某些給定元素出如今輸出中,這會頗有用。

通常來講,可能有不少不一樣的緣由。

此選項爲每一個指定的類和類成員打印最短的方法鏈到指定的種子或入口點。

在當前的實施中,打印出的最短鏈有時可能包含循環扣除 - 這些並不反映實際收縮過程。

若是指定了 -verbose 選項,則跟蹤包括完整的字段和方法簽名。只適用於壓縮。

壓縮規則

-dontshrink

指定不被壓縮的類文件。

默認狀況下壓縮是開啓的,除了用各類用 keep 選項直接或間接用到的類或類成員,其餘的都會被移除。

壓縮步驟一般在優化以後,由於某些優化可能會打開已經刪除的類或類成員。

-printusage [filename]

指定列出移除的死代碼。該列表打印到標準輸出或給定文件。

參考上面說的 usage.txt

例如,您能夠列出應用程序的未使用代碼。只適用於壓縮。

優化規則

-dontoptimize

指定不優化輸入類文件。默認狀況下,優化已啓用;全部方法都在字節碼級別進行了優化

-optimizationpasses n

指定要執行的優化傳遞的數量。

默認狀況下,執行一次傳遞。屢次通行可能會致使進一步的改進。若是在優化後沒有找到改進,則優化結束。只適用於優化。

混淆規則

-dontobfuscate

指定不混淆輸入的類文件。

默認狀況下,混淆是開啓的,類和類成員會被改爲新的短隨機名稱,除了各類-keep選項列出的名稱外。

內部屬性對於調試頗有用,例如源文件名,變量名和行號被刪除。

-printmapping [filename]

指定將舊名稱映射到已重命名的類和類成員的新名稱的映射。映射打印到標準輸出或給定文件。

例如,它是後續增量混淆所必需的,或者若是想再次理解混淆的堆棧跟蹤。只有在混淆時才適用。

參考 上面說的 mapping.txt。

-useuniqueclassmembernames

指定將相同的混淆名稱分配給具備相同名稱的類成員,並將不一樣混淆名稱分配給名稱不一樣的類成員(對於每一個給定的類成員簽名)。

沒有這個選項,更多的類成員能夠被映射到相同的短名稱,好比'a','b'等等。

這個選項所以稍微增長告終果代碼的大小,可是它確保了保存的混淆名稱映射老是能夠在隨後的增量混淆步驟中受到尊重。

例如,考慮兩個不一樣的接口,它們包含具備相同名稱和簽名的方法。若是沒有此選項,這些方法可能會在第一個混淆步驟中獲取不一樣的混淆名稱。

若是添加了包含實現兩個接口的類的補丁程序,則ProGuard必須在增量混淆步驟中爲這兩種方法強制執行相同的方法名稱。

原始模糊代碼已更改,以保持結果代碼的一致性。在最初的混淆步驟中使用此選項,這種重命名將永遠不是必需的。

該選項僅適用於混淆。

實際上,若是計劃執行增量混淆,則可能但願徹底避免壓縮和優化,由於這些步驟可能會刪除或修改部分代碼,這些代碼對於之後的添加相當重要。

-dontusemixedcaseclassnames

指定在混淆時不生成混合大小寫的類名。

默認狀況下,混淆的類名能夠包含大寫字符和小寫字符的混合。

建立的這個徹底可接受和可用的jars 只有在不區分大小寫的文件系統(好比Windows)的平臺上解壓縮jar時,解壓縮工具可能會讓相似命名的類文件相互覆蓋。

解壓縮後自毀的代碼!真正想在Windows上解壓他們的jar的開發人員可使用這個選項來關閉這種行爲。

混淆的jars會所以變得稍大。

只有在混淆時才適用。

-keeppackagenames [package_filter]

指定不混淆給定的軟件包名稱。

可選過濾器是包名稱的逗號分隔列表。包名能夠包含?,*通配符,而且它們能夠在!否認器。只有在混淆時才適用。

-flattenpackagehierarchy [package_name]

指定將全部重命名的軟件包從新打包,方法是將它們移動到單個給定的父軟件包中。若是沒有參數或空字符串(''),程序包將移動到根程序包中。

該選項是進一步混淆軟件包名稱的一個示例。它可使處理後的代碼更小,更難理解。

只有在混淆時才適用。

-repackageclasses [package_name]

指定將全部重命名的類文件從新打包,方法是將它們移動到單個給定的包中。沒有參數或者使用空字符串(''),該軟件包將被徹底刪除。

該選項將覆蓋 -flattenpackagehierarchy 選項。

這是進一步模糊軟件包名稱的另外一個例子。

它可使處理後的代碼更小,更難理解。

其不推薦使用的名稱是-defaultpackage。

只有在混淆時才適用。

警告:若是在別處移動它們,則在其包目錄中查找資源文件的類將再也不正常工做。若有疑問,請不要使用此選項,以避免觸及包裝。

-keepattributes [attribute_filter]

指定要保留的任何可選屬性。這些屬性能夠用一個或多個-keepattributes指令來指定。

可選過濾器是Java虛擬機和ProGuard支持的屬性名稱的逗號分隔列表。

屬性名稱能夠包含?,*和**通配符,而且能夠在以前加上!否認器。

例如,在處理庫時,您至少應保留Exceptions,InnerClasses和Signature屬性。

您還應該保留SourceFile和LineNumberTable屬性以生成有用的混淆堆棧跟蹤。

最後,若是你的代碼依賴於它們,你可能須要保留註釋。

只有在混淆時才適用。

# 保留Annotation不混淆

-keepattributes *Annotation*,InnerClasses



# 避免混淆泛型

-keepattributes Signature



# 拋出異常時保留代碼行號

-keepattributes SourceFile,LineNumberTable

-keepparameternames

指定保留所保存方法的參數名稱和類型。

該選項實際上保留了調試屬性LocalVariableTable和LocalVariableTypeTable的修剪版本。

處理庫時它可能頗有用。

一些IDE可使用這些信息來幫助使用該庫的開發人員,

例如工具提示或自動完成。

只有在混淆時才適用。

-renamesourcefileattribute [string]

指定要放入類文件的SourceFile屬性(和SourceDir屬性)中的常量字符串。請注意,該屬性必須首先出現,因此它也必須使用-keepattributes指令明確保留。

例如,您可能但願讓處理過的庫和應用程序生成有用的混淆堆棧跟蹤。

只有在混淆時才適用

預校驗 規則

-dontpreverify

指定不預先驗證已處理的類文件。

默認狀況下,若是類文件針對Java Micro Edition或Java 6或更高版本,則會對其進行預驗證。

對於Java Micro Edition,須要進行預驗證,所以若是指定此選項,則須要在處理的代碼上運行外部預驗證程序。

對於Java 6,預驗證是可選的,但從Java 7開始,它是必需的。

只有在最終對Android時,它纔不是必需的,所以您能夠將其關閉以縮短處理時間。

-android

指定已處理的類文件針對Android平臺。而後ProGuard確保一些功能與Android兼容。

例如,若是您正在處理Android應用程序,則應該指定此選項。

通常規則

-verbose

指定在處理期間寫出更多信息。若是程序以異常終止,則此選項將打印出整個堆棧跟蹤,而不只僅是異常消息。

-dontnote [class_filter]

指定不打印有關配置中可能的錯誤或遺漏的註釋,

例如類名中的拼寫錯誤或缺乏可能有用的選項。

可選的過濾器是一個正則表達式;

ProGuard不打印有關匹配名稱的類的註釋。

-dontwarn [class_filter]

指定不警告有關未解決的引用和其餘重要問題。

可選的過濾器是一個正則表達式; ProGuard不打印關於具備匹配名稱的類的警告。忽略警告多是危險的。

例如,若是處理確實須要未解決的類或類成員,則處理後的代碼將沒法正常工做。

只有在你知道本身在作什麼的狀況下才使用此選項!

-ignorewarnings

指定打印任何關於未解決的引用和其餘重要問題的警告,但在任何狀況下都繼續處理,忽略警告。

忽略警告多是危險的。

例如,若是處理確實須要未解決的類或類成員,則處理後的代碼將沒法正常工做。

只有在知道本身在作什麼的狀況下才使用此選項!

-printconfiguration [filename]

指定使用包含的文件和替換的變量寫出已解析的整個配置。結構打印到標準輸出或給定文件。

這對於調試配置或將XML配置轉換爲更易讀的格式有時會頗有用。

-dump [filename]

指定在任何處理後寫出類文件的內部結構。結構打印到標準輸出或給定文件。

例如,可能但願寫出給定jar文件的內容,而不進行處理。

參考上面說的 dump.txt。

-addconfigurationdebugging

指定用調試語句來處理已處理的代碼,這些語句顯示缺乏ProGuard配置的建議。

若是處理後的代碼崩潰,那麼在運行時得到實用提示可能很是有用,由於它仍然缺乏一些反射配置。

例如,代碼多是使用GSON庫序列化類,可能須要一些配置。一般能夠將控制檯的建議複製/粘貼到配置文件中。

警告:不要在發行版本中使用此選項,由於它將混淆信息添加到已處理的代碼中。

keep 選項修飾符

includedescriptorclasses

指定-keep選項所保存的方法和字段的類型描述符中的任何類也應保存。

在保留方法名稱時,這一般頗有用,以確保方法的參數類型不會重命名。他們的簽名保持徹底不變,並與本地庫兼容。

includecode

指定保持-keep選項所保存的字段的方法的代碼屬性也應該保留,便可能未被優化或模糊處理。這對於已優化或混淆的類一般頗有用,以確保在優化期間未修改其代碼。

allowshrinking

指定-keep選項中指定的入口點可能會壓縮,即便必須另外保留它們。

也就是說,能夠在壓縮步驟中刪除入口點,但若是它們是必需的,則它們可能未被優化或混淆。

allowoptimization

指定-keep選項中指定的入口點可能會被優化,即便它們必須另外保存。

也就是說,入口點可能會在優化步驟中被更改,但它們可能不會被刪除或混淆。

此修飾符僅用於實現不尋常的要求。

allowobfuscation

指定在-keep選項中指定的入口點可能會被混淆,即便它們必須另外保存。

也就是說,入口點可能在混淆步驟中被重命名,但它們可能不會被刪除或優化。

此修飾符僅用於實現不尋常的要求。

keep 選項之間的關係

壓縮和混淆的各類-keep選項起初看起來有點混亂,但實際上它們背後有一個模式。

下表總結了它們之間的關係:

內容 被刪除或重命名 被重命名
類和類成員 -keep -keepnames
只有類成員 -keepclassmembers -keepclassmembernames
類和類成員,引用成員存在 -keepclasseswithmembers -keepclasseswithmembernames

若是指定了一個沒有類成員的類,ProGuard只保留該類及其無參數的構造函數做爲入口點。它可能仍會刪除,優化或混淆其餘班級成員。

若是指定了一個方法,則ProGuard僅將該方法做爲入口點進行保存。其代碼可能仍會進行優化和調整。

類規範

類規範是類和類成員(字段和方法)的模板。它用於各類-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 關鍵字前加上!將匹配限制爲不是接口或枚舉的類。
每個類名字都必須是徹底限定名,例如 java.lang.String 內部類用美圓符號「$」分隔,例如java.lang.Thread$State。類名能夠被指定爲包含如下通配符的正則表達式:
  • ? 匹配類名稱中的任何單個字符,但不匹配包分隔符。例如 "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"
  • ** 匹配類名稱的任何部分,可能包含任意數量的包分隔符。例如,"**.Testz" 匹配除根包之外的全部包中的全部Test類。或者,"com.example.**" 匹配 "com.example" 中的全部類及其子包。
  • <n> 在相同的選項中匹配第n個匹配的通配符。例如,"com.example.*Foo<1>" 匹配"com.example.BarFooBar"。

爲了得到更多的靈活性,類名實際上能夠是逗號分隔的類名列表,能夠加!。這個符號看起來不是很像java,因此應該適度使用。

爲了方便和向後兼容,類名*指任何類,而不考慮它的包。

  • extendsimplements 一般用來限制使用通配符的類。目前他們是同樣的。他們的意思是 只有繼承或實現了給定類的類纔有資格。給定的類自己不包含在這個集合中。若是須要,應該在單獨的選項中指定。
  • @ 可用於將類和類成員限制爲使用指定的註釋類型進行註釋的類。annotationtype 就像類名同樣被指定。
  • 除了方法參數列表不包含參數名稱外,字段和方法在Java中的定義很是相似(就像在javadoc和javap等其餘工具中同樣)。這些規範還能夠包含如下通配符通配符:
通配符 意義
<init> 匹配任何構造方法
<fields> 匹配任何字段
<methods> 匹配任何方法
* 匹配任何方法和字段

請注意,上述通配符沒有返回類型。只有<init>通配符纔有一個參數列表。

字段和方法也可使用正則表達式來指定。名稱能夠包含如下通配符:

通配符 意義
? 匹配方法名的任何單個字符
* 匹配方法名的任何部分
<n> 在相同的選項中匹配第n個匹配的通配符

類型能夠包含如下通配符

通配符 意義
% 匹配任何原始類型(boolean,int 等,不包含 void)
? 匹配類名中的單個字符
* 匹配類名中的任何部分但不包含包分隔符
** 匹配類名中的任何部分但不包含包分隔符
*** 匹配任何類型(原始類型或者非原始類型,數組或者非數組)
--- 匹配任何類型的任意數量的參數
<n> 在相同的選項中匹配第n個匹配的通配符。

請注意,?,*和**通配符永遠不會匹配基本類型。
並且,只有***通配符才能匹配任何維度的數組類型。

例如,「** get *()」匹配「java.lang.Object getObject()」,但不匹配「float getFloat()」和「java.lang.Object [] getObjects()」。

  • 也可使用短類名(無包)或使用完整的類名來指定構造函數。和Java語言同樣,構造函數規範有一個參數列表,但沒有返回類型。
  • 類訪問修飾符和類成員訪問修飾符一般用於限制通配類和類成員。它們指定必須爲成員設置相應的訪問標誌以匹配。前面加 "!" 決定相應的訪問標誌應該被取消設置。

容許組合多個標誌(例如,public static)。這意味着必須設置兩個訪問標誌(例如 public static ),除非它們有衝突,在這種狀況下,至少必須設置其中一個(例如至少public或 protected)。

ProGuard支持可能由編譯器設置的其餘修飾符 synthetic,bridge和varargs。

參考資料

End 微信掃一掃,關注個人公衆號

image

相關文章
相關標籤/搜索