Android混淆之ProGuard

初識ProGuard

Android開發的小夥伴們都或多或少的接觸過混淆,很多人都對混淆很困惑。需要發版的時候,從網上load一份混淆文件,或從其他項目中拷貝一份過來,修改一下,管用就不去管了,有問題就卡住了,各種baidu也不一定能解決問題。本文力求讓大家對混淆規則輕車熟路,能快速的上手。知其然也能知其所以然。

從Android Studio2.3開始,已經集成了ProGuard。ProGuard是一款Java類文件的混淆器,集成了壓縮器,優化器,混淆器和預驗證器。 與其他Java混淆器相比,ProGuard的主要優勢可能是其緊湊的基於模板的配置。通常只需幾個直觀的命令行選項或一個簡單的配置文件即可。ProGuard減少了處理後的代碼的大小,並帶來了一些潛在的效率提高。處理幾兆字節的程序和庫只需要幾秒鐘。

ProGuard的典型用途是:
創建更緊湊的代碼,以實現更小的代碼歸檔,更快的網絡傳輸,更快的加載和更小的內存佔用。
使程序和庫更難以逆向工程。
列出無效代碼,可以將其從源代碼中刪除。
重新定位和預先驗證Java 6的現有類文件,以充分利用Java 6更快的類加載速度。

在gradle中配置ProGuard開啓混淆很簡單:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

minifyEnabled 屬性設爲 true即開啓混淆。
proguardFiles 屬性指定了混淆文件的所在目錄。proguard-android.txt爲sdk路徑下默認的混淆文件,後面的’proguard-rules.pro’就是我們自定義的混淆文件。

ProGuard處理代碼的流程如下:
Shrunk壓縮器可檢測並刪除未使用的類,字段,方法和屬性。 Optimize優化器分析並優化了方法的字節碼。 Obfuscate混淆器使用簡短的無意義名稱重命名其餘的類,字段和方法。 這些步驟使代碼庫更小,更有效,並且更難以逆向工程。 最後的Preverify預驗證器將預驗證信息添加到類中,這對於Java Micro Edition是必需的,可以縮短Java 6的啓動時間。
在這裏插入圖片描述

ProGuard詳解

什麼在壓縮?

Java源代碼(.java文件)通常被編譯爲字節碼(.class文件)。字節碼比Java源代碼更緊湊,但是字節碼仍可能包含許多未使用的代碼,尤其是在包含程序庫的情況下。壓縮程序(例如ProGuard)可以分析字節碼並刪除未使用的類,字段和方法。該程序在功能上保持等效,包括異常堆棧跟蹤中給出的信息。

什麼是混淆?

默認情況下,已編譯的字節碼仍包含許多調試信息:源文件名,行號,字段名,方法名,參數名,變量名等。此信息使直接編譯字節碼和對整個程序進行反向工程變得很簡單。有時,這是不可取的。諸如ProGuard之類的混淆器可以刪除調試信息,並以無意義的字符序列替換所有名稱,這使得對代碼進行反向工程變得更加困難。同時它進一步壓縮了代碼。該程序在功能上保持等效,除了在異常堆棧跟蹤中給出的類名,方法名和行號。

反射

反射給代碼的自動處理帶來了特殊的問題。 在ProGuard中,必須將代碼中動態創建或調用的類或類成員指定爲入口點, 用keep選項保護起來。 例如,Class.forName()構造可以在運行時引用任何類。 通常無法預見必須保留哪些類(及其原始名稱),比如可以從配置文件中讀取類名稱。 因此,必須在ProGuard配置中使用相同的簡單-keep選項來指定它們。

此外,如果需要保留某些類或類成員,ProGuard將提供一些建議。 例如,ProGuard將注意類似“(SomeClass)Class.forName(variable).newInstance()”的結構。 這些可能表明該類或接口SomeClass或它的實現可能需要保留。 然後,我們可以相應地調整混淆配置。

混淆選項

一份混淆文件主要有一系列的keep選項及非keep選項構成。keep選項用來告訴ProGuard哪些類、類成員不被混淆;非keep選項包括輸入、壓縮、優化、 混淆、常規等選項,用來告訴ProGuard額外的配置。

非keep選項

  • 輸入選項
    -skipnonpubliclibraryclasses
    指定在讀取庫jar時跳過非公共類,以加快處理速度並減少ProGuard的內存使用量。默認情況下,ProGuard會讀取非公共和公共庫類。但是,非公用類通常不相關,只要它們不影響輸入jar中的實際程序代碼即可。然後忽略它們可以加快ProGuard的速度,而不會影響輸出。不幸的是,某些庫,包括最近的JSE運行時庫,都包含由公共庫類擴展的非公共庫類。如果由於設置了此選項而無法找到類,則ProGuard將打印警告。

    -dontskipnonpubliclibraryclasses
    指定不忽略非公共庫類。從4.5版開始爲默認設置。

    -dontskipnonpubliclibraryclassmembers
    指定不忽略包可見的庫類成員(字段和方法)。默認情況下,ProGuard在解析庫類時會跳過這些類成員,因爲程序類通常不會引用它們。但是,有時程序類與庫類位於同一包中,並且它們確實引用其包可見的類成員。在這種情況下,實際讀取類成員可能很有用,以確保處理後的代碼保持一致。

  • 壓縮選項
    默認開啓壓縮; 除各種-keep選項列出的類以及它們直接或間接依賴的類之外,所有類和類成員 都將被刪除。 在每個優化(optimization )步驟之後,還會執行壓縮步驟,因爲優化後可能會再次暴露一些未被使用的類和成員。
    關閉壓縮:-dontshrink

  • 優化選項
    默認開啓優化。所有方法都在字節碼級別進行了優化。但某些時候,優化可能導致程序執行異常,它可能會改變程序原有的邏輯。比如刪除了某些特殊的註釋,刪除了它認爲無意義的空loop。
    關閉優化:-dontoptimize

    -optimizationpasss n
    指定要執行的優化遍數。默認情況下,執行一次通過。多次通過可能會有進一步的改進。如果 在優化通過後未發現任何改進,則優化結束。僅在優化時適用。

    -allowaccessmodification
    指定在處理過程中可以擴大類和類成員的訪問修飾符。這樣可以改善優化步驟的結果。

  • 混淆選項
    默認開啓混淆。除了各種-keep選項列出的名稱外,類和類成員會收到新的簡短隨機名稱。刪除了對調試有用的內部屬性,例如源文件名,變量名和行號。
    關閉混淆:-dontobfuscate

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

    -useuniqueclassmembernames
    該選項將爲需要混淆的類生成唯一的混淆名稱。如果沒有該選項,則將更多的類成員映射到相同的短名稱,如“ a”,“ b”等。

    -dontusemixedcaseclassnames
    指定在混淆時不生成大小寫混合的類名,即全部小寫。 默認情況下,混淆的類名可以包含大寫字符和小寫字符的混合。

    -keeppackagenames [package_filter]
    指定不混淆指定的包名稱。 可選的過濾器是包名稱的逗號分隔列表。包名稱可以包含?,*和**通配符,或在其前面加上!。
    主工程不同的庫工程時,不同的庫工程混淆後的類名可能衝突,比如都是a.a.a.a。當主工程引用混淆後的庫aar時就會編譯出錯:

#Duplicate class a.a.a.a found in modules classes.jar (:libA-release:) and classes.jar (:libB-release:)

這時可以keeppackagenames指定一個庫的包名稱不混淆來避免此問題。

-keepattributes [attribute_filter ]
指定要保留的可選屬性。可以使用一個或多個-keepattributes指令指定屬性。可選過濾器是用逗號分隔的屬性名稱列表。屬性名稱可以包含?,*和**通配符,或在其前面加上!。
典型的可選屬性包括:
Exceptions,Signature,InnerClasses,Deprecated,SourceFile,SourceDir,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Synthetic,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations和AnnotationDefault。

例如,在處理庫時,至少應保留Exceptions,InnerClasses和Signature屬性。還應該保留SourceFile和LineNumberTable屬性,以產生有用的混淆堆棧跟蹤。最後,如果您的代碼依賴註釋,則可能需要保留註釋。

示例:

-keepattributes Exceptions,InnerClasses,Signature #保留內部接口或內部類、內部類、泛型簽名類型
-renamesourcefileattribute SourceFile #將崩潰日誌文件來源重命名爲“SourceFile”
-keepattributes SourceFile,LineNumberTable #產生有用的混淆堆棧跟蹤
-keepattributes *Annotation* #保留註釋
  • 常規選項

    -verbose
    指定在處理期間輸出更多信息。如果程序因異常終止,則此選項將打印出整個堆棧跟蹤,而不僅僅是異常消息。

    -dontnote [class_filter]
    指定不打印有關配置中潛在錯誤或遺漏的註釋,例如類名中的錯字或缺少可能有用的選項。可選過濾器class_filter是一個正則表達式; ProGuard不會打印與可選名稱匹配的類的註釋。

    -dontwarn [class_filter]
    指定不警告尚未解決的引用和其他重要問題。可選過濾器class_filter是一個正則表達式; ProGuard不會打印與可選名稱匹配的類的警告。忽視警告可能很危險。例如,如果確實需要對未解析的類或類成員進行處理,則處理後的代碼將無法正常運行。僅當知道自己在做什麼時才使用此選項!

    -ignorewarnings
    指定打印有關未解決引用和其他重要問題的任何警告,但在任何情況下都將繼續處理。忽視警告可能很危險。例如,如果確實需要對未解析的類或類成員進行處理,則處理後的代碼將無法正常運行。僅當知道自己在做什麼時才使用此選項!

    文件過濾器
    文件過濾器是逗號分隔的文件名列表,可以包含通配符。 支持以下通配符:
    ? 匹配名稱中的任何單個字符。
    *   匹配名稱的不包含包分隔符“.”或目錄分隔符"/"的任何部分。
    **  匹配名稱的任何部分,可能包含任意數量的包分隔符或目錄分隔符。
    例如,“java / **.class,javax / **.class” 匹配java和javax中的所有類文件。“ foo,*bar”匹配名稱foo和所有以bar結尾的名稱。

此外,名稱前可以帶有一個負號“!”。從匹配的文件名中排除該文件名。例如,

"!**.gif,images/** " # 匹配images目錄中的所有文件,gif文件除外。
"!foobar,*bar" #匹配所有以bar結尾的名稱,但foobar除外。

keep選項

keep選項用來在混淆規則中聲明需要保留的類和類成員,防止它們被刪除和重命名。一般的格式如下:

-keep選項   class_specification
class_specification是類和成員的模板,用來指定應用keep規則的若干類及其成員.

根據能否在壓縮階段被刪除和在混淆階段被重命名,keep選項分爲兩類:
第一類,不帶names,不能被刪除、不能被重命名:-keep、-keepclassmembers、-keepclasseswithmembers,分別對應 同時保留類和類成員、只保留類成員、根類據成員找到滿足條件的所有類而不用指定類名,保留類名和成員名。
第二類,帶names,不能被重命名:-keepnames、-keepclassmembernames 、-keepclasseswithmembernames,分別對應 同時保留類和類成員不被重命名、只保留類成員不被重命名、根類據成員找到滿足條件的所有類而不用指定類名,保留類名和成員名不被重命名。對於第二類,如果類沒有被調用到,則在壓縮階段就會被刪除。

如圖:
在這裏插入圖片描述

class_specification

class_specification是類和成員的模板,用來指定應用keep規則的若干類及其成員。格式如下:

[@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關鍵字將匹配項限制爲枚舉類。在接口或枚舉關鍵字之前加!將匹配分別限制爲不是接口或枚舉的類。

  • 每個類名必須完全合格,例如java.lang.String。可以將類名指定爲包含以下通配符的正則表達式:
    ?   匹配類名稱中的任何單個字符,但不匹配包分隔符"."。
     *   匹配除包分隔符之外的類名的任何部分。

    "mypackage.*" 與mypackage中的所有類匹配,但與子包中的所有類都不匹配。
    

    ** 匹配類名的任何部分,可能包含任意數量的包分隔符。

    **.Test 匹配除根包以外的所有包中的所有Test類。
    "mypackage.**" 與mypackage及其子包中的所有類匹配。
    
  • @annotationtype 可用於將類和類成員限制爲使用指定註釋類型進行註釋的成員。指定註釋類型就像類名一樣。字段和方法的指定與Java中的指定非常相似,註釋類型的方法參數列表不包含參數名稱。

  • 類名* 表示任何類,無論其包如何。

  • < init> 匹配任何構造函數
    < fields> 匹配任何字段
    < methods> 匹配任何方法
     *  匹配任何字段或方法。
    請注意,上述通配符沒有返回類型。僅< init>通配符具有參數列表

  • 字段和方法名稱可以包含以下通配符:
    ?匹配方法名稱中的任何單個字符。
    * 匹配方法名稱的任何部分。

  • 描述符中的類型可以包含以下通配符:
    % 匹配任何原始類型(“ boolean”,“ int”等,但不匹配“ void”)。
    ? 匹配名稱中的任何單個字符。
    *  與不包含包分隔符的名稱的任何部分匹配。
    ** 匹配類名的任何部分,可能包含任意數量的包分隔符。
    *** 匹配任何類型(原始或非原始,數組或非數組)。
    … 匹配任何類型的任意數量的參數。

1.  請注意,?,*** 通配符永遠與基本類型不匹配。
2. 此外,只有*** 通配符可以匹配任何維度的數組類型。
   例如,"**get*()"匹配"java.lang.Object getObject()",但不匹配" float getFloat()",也不匹配“”java.lang.Object [] getObjects()"。
3. 也可以使用構造函數的短類名(不帶包)或完整的類名來指定構造函數。與Java語言一樣,構造函數規範具有參數列表,但沒有返回類型。
 4. 允許組合多個類成員訪問修飾符標誌(例如public static)。

ProGuard其他需要注意的事項

  • 保留native方法
    對於native方法,則需要保留它們的名稱和類的名稱,以便它們可以鏈接到本地庫:
-keepclasseswithmembernames class * {
    native <methods>;
}
  • 保留枚舉
    如果程序代碼中包含枚舉類,則必須保留一些特殊方法。 Java 5中引入了枚舉。java編譯器將枚舉轉換爲具有特殊結構的類。 值得注意的是,這些類包含一些靜態方法的實現,運行時環境可以通過內省訪問。必須明確指定這些內容,以確保它們不會被刪除或混淆:
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

ProGuard的一些技術問題

使用ProGuard時,您應該注意一些技術問題,可以輕鬆避免或解決所有這些問題。ProGuard在處理代碼時,可能會打印出一些注意事項和非致命警告:

  • Note: … calls ‘(…)Class.forName(variable).newInstance()’

ProGuard會列出動態創建的類實例的所有類強制轉換,例如“(MyClass)Class.forName(variable).newInstance()”。我們可能需要使用“ -keep class MyClass”之類的選項來保留所提及的類,或者使用“ -keep class * implements MyClass”之類的選項來保留其實現。

  • Note: … accesses a field/method ‘…’ dynamically

ProGuard列出了許多構造,例如“ .getField(“ myField”)“。我們可能需要弄清楚所提到的類成員的定義位置,並使用“ -keep class MyClass {MyFieldType myField;}”之類的選項來保留它們。否則,ProGuard可能會刪除或混淆類成員。

  • 優化導致的意外錯誤
    通常是在優化步驟中ProGuard遇到了意外,可能不會恢復。可以使用-dontoptimize選項來避免這種情況

  • 保留註釋類
    如果要基於註釋保留類,則可能避免在壓縮步驟中刪除註釋類本身。您可以使用“ -keep @interface *”之類的選項將所有註釋類明確保留在程序代碼中。

  • ClassNotFoundException
    代碼可能正在調用Class.forName,試圖動態創建缺少的類。 ProGuard只能檢測常量名稱參數,例如Class.forName(“ mypackage.MyClass”)。對於像Class.forName(someClass)這樣的變量名參數,您必須使用適當的-keep選項來保留所有可能的類,例如:

"-keep class mypackage.MyClass" 
"-keep class * implements mypackage.MyInterface".
  • NoSuchMethodException
    代碼可能正在調用諸如myClass.getMethod之類的內容,試圖動態查找某些方法。而這些方法已經被混淆了。因此必須使用適當的-keep選項:
"-keep class mypackage.MyClass { void myMethod(); }"

更具體地說,如果報告爲丟失的方法是value或valueOf,則可能必須保留一些與枚舉有關的方法。

  • Disappearing annotations
    默認情況下,混淆步驟將刪除所有註釋。如果您的應用程序依賴註釋來正常運行,則應使用"-keepattributes * Annotation *" 明確保留它們。

  • Disappearing loops
    如果您的代碼包含空的繁忙等待循環,則ProGuard的優化步驟可能會將其刪除。如果與實際邏輯衝突,則必須使用-dontoptimize選項關閉優化。

  • ClassCastException: class not an enum, or
    IllegalArgumentException: class not an enum type
    應確保保留枚舉類型的特殊方法,運行時環境通過自省調用該方法。

  • ArrayStoreException: sun.reflect.annotation.EnumConstantNotPresentExceptionProxy
    可能正在處理涉及枚舉的註釋。 同樣,您應該確保保留枚舉類型的特殊方法。

  • 對於dex編譯器和Dalvik VM,預驗證是無關緊要的,因此我們可以使用-dontpreverify選項將其關閉。

  • -optimizations選項禁用Dalvik 1.0和1.5無法處理的某些算術簡化。Dalvik VM也無法處理(靜態字段)過度的過載。

總結一下,就是:

  • 需要動態訪問的類或類成員,需要保留
  • 慎用優化,甚至直接使用-dontoptimize禁用
  • Android中預驗證無效,使用-dontpreverify將其關閉
  • 根據需要保留註釋和枚舉

一份通用的ProGuard混淆文件

最後,提供一份較通用的ProGuard混淆文件參考。
我們保留了應用程序的AndroidManifest.xml文件可能引用的所有基本類。如果清單文件包含其他類和方法,可能還必須指定它們。

我們保留註釋,因爲它們可能由自定義RemoteView使用。

我們將使用典型的構造函數保留所有自定義View擴展和其他類,因爲它們可能是從XML佈局文件引用的。

我們還將所需的靜態字段保留在Parcelable或Serializable實現中,因爲可以通過自省訪問它們。

最後,我們保留了自動生成的R類的引用內部類的靜態字段,以使調用代碼通過自省訪問這些字段。

如果您使用的是Google的可選許可證驗證庫,則可以將其代碼與自己的代碼混淆。 您必須保留其ILicensingService接口以使庫正常工作:

-keep public interface com.android.vending.licensing.ILicensingService

如果您使用的是Android兼容性庫,則應添加以下行,以使ProGuard知道該庫引用了並非所有版本的API都可用的某些類:

-dontwarn android.support.**

“Exceptions”屬性必須保留,以使編譯器知道哪些方法可能引發異常。

僅當動態調用了其他任何非公共類或方法時,才應使用附加的-keep選項來指定它們。

對於可以從庫外部引用的任何內部類,也必須保留“ InnerClasses”屬性。否則,javac編譯器將無法找到內部類。

在JDK 5.0及更高版本中進行編譯時,必須具有“Signature”屬性才能訪問泛型。
最後,我們保留“ Deprecated”屬性和用於生成有用的堆棧跟蹤的屬性。

-keepattributes Exceptions,InnerClasses,Signature,Deprecated

此外,正規的第三方庫一般都會在接入文檔中寫好所需混淆規則,使用第三方庫時注意添加。
WebView中JavaScript調用的方法時,也需要保留。
Layout佈局使用的View構造函數、android:onClick等,也需要保留。

#指定要執行的優化遍數
-optimizationpasses 5

#混淆時不生成大小寫混合的類名,即全部小寫
-dontusemixedcaseclassnames

#指定不忽略非公共的庫的類
-dontskipnonpubliclibraryclasses

#指定不忽略包可見的庫類成員(字段和方法)。
-dontskipnonpubliclibraryclassmembers

#把混淆類中的方法名也混淆了
#爲需要混淆的類生成唯一的混淆名稱
-useuniqueclassmembernames

#關閉預驗證
-dontpreverify

# 打印過程日誌,在處理期間輸出更多信息
-verbose

#-dontshrink #禁用壓縮

#指定優化算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#關閉優化
-dontoptimize

#擴大類和類成員的訪問權限,使優化時允許訪問並修改有修飾符的類和類的成員
-allowaccessmodification

#四大組件和Application的子類不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

#如果使用的是Google的可選許可證驗證庫,則可以將其代碼與自己的代碼混淆。 必須保留其ILicensingService接口以使庫正常工作
-keep public interface com.android.vending.licensing.ILicensingService

#將混淆堆棧跟蹤文件來源重命名爲“SourceFile”
-renamesourcefileattribute SourceFile

#保護註解。如果代碼依賴註釋,則可能需要保留註釋,典型應用EventBus的事件接收回調
-keepattributes *Annotation*

#保留源文件名,變量名和行號,以產生有用的混淆堆棧跟蹤
-keepattributes SourceFile,LineNumberTable

#保留異常,內部類/接口,泛型,Deprecated不推薦的方法
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,EnclosingMethod

#如果引用了v4或者v7包, 不報警告,使ProGuard知道該庫引用了並非所有版本的API都可用的某些類
-dontwarn android.support.**

#保留native方法
-keepclasseswithmembernames class * {
   native <methods>;
}

#保留自定義View的類及構造函數,以使它們可以被XML佈局文件引用
-keep class * extends android.view.View {
   public <init>(android.content.Context);
   public <init>(android.content.Context, android.util.AttributeSet);
   public <init>(android.content.Context, android.util.AttributeSet, int);
}


#保留自定義View的get和set相關方法
-keepclassmembers public class * extends android.view.View {
  void set*(***);
  *** get*();
}

#保持Activity中View及其子類爲入參的方法,比如android:onClick
-keepclassmembers class * extends android.app.Activity {
  public void *(android.view.View);
}


#保留符合指定構造函數類型的自定義控件類,如果和下面的寫在一起,那麼只有同時有這兩類構造函數的類才滿足
-keepclasseswithmembers class * {
   public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
   public <init>(android.content.Context, android.util.AttributeSet, int);
}


#保留R文件的靜態成員,以使調用代碼通過自省訪問這些字段
-keepclassmembers class **.R$* {
   public static <fields>;
}

#保留枚舉
-keepclassmembers enum * {
   public static **[] values();
   public static ** valueOf(java.lang.String);
}

#保留實現了Parcelable接口的類中的靜態成員
-keep class * implements android.os.Parcelable {
 public static final android.os.Parcelable$Creator *;
}

#保持所有實現Serializable接口的類成員
-keepclassmembers class * implements java.io.Serializable {
   static final long serialVersionUID;
   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();
}

#Fragment不需要在AndroidManifest.xml中註冊,需要額外保護下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment

#指定不混淆指定的包名稱
-keeppackagenames com.milanac007.*

#指定包名下的文件都保留
-keep class com.milanac007..blecommsdk.**{*;}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章