Gradle For Android(四)Gradle 編譯中神祕的混淆

傻小孩b發表於2016-11-07

Gradle For Android(二) 多渠道打包與簽名配置中說過在日常開發中進行打包apk的一些往事,打包測試、打包上傳應用商店,這些都避免不了。當然在這過程中,除了簽名打包,當然少不了程式碼的混淆了。程式碼混淆,說白了就是程式碼壓縮、程式碼混淆以及資源壓縮的優化。依靠 ProGuard,將所有類名、方法名重新命名為無意義的簡單名稱,增加了逆向工程難度。依靠Gradle外掛,移除了沒有使用的資源,減少了apk大小 。

Gradle For Android 導讀

正文:

一、Android Gradle 混淆中的配置

一般,在我們的application moudle中的gradle配置項buildTypes中,例如如下:

  release { //release型別
            minifyEnabled false
            // 啟用混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }複製程式碼

這裡minifyEnabled表示啟動混淆,一般啟動混淆編譯速度會比較慢,因此在編譯debug版本的時候一般預設不開啟。proguard-rules.pro這個表示該module預設的混淆檔案,我們可以直接將混淆內容寫在這裡。另外,這裡也要說下shrinkResources 這個欄位,當為true的時候,表示開啟資源壓縮,編譯時會去掉沒被使用的資原始檔。

修改後的配置程式碼如下:

  buildTypes {//表示構建型別 一般有release debug 兩種

        debug{
            buildConfigField 'String','STATE_TEST','"debug"'//buildConfigField
            resValue "string", "test_value", "AGradle_debug"//resValue
        }

        release { //release型別
            minifyEnabled true  // 啟用混淆
            shrinkResources true  // 資源壓縮
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            buildConfigField 'String','STATE_TEST','"release"' //buildConfigField
            resValue "string", "test_value", "AGradle_release" //resValue
        }

    }複製程式碼

二、混淆規則舉例

這裡貼下網上認為比較"通用"的混淆規則如下:

#指定壓縮級別
-optimizationpasses 5

#不跳過非公共的庫的類成員
-dontskipnonpubliclibraryclassmembers

#混淆時採用的演算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆類中的方法名也混淆了
-useuniqueclassmembernames

#優化時允許訪問並修改有修飾符的類和類的成員 
-allowaccessmodification

#將檔案來源重新命名為“SourceFile”字串
-renamesourcefileattribute SourceFile
#保留行號
-keepattributes SourceFile,LineNumberTable

#保持所有實現 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

# 保持測試相關的程式碼
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**複製程式碼

"通用",當然真正實現通用,除了上面的混淆規則,需要根據專案需要新增自定義的混淆規則

例如:

  • 第三方庫混淆規則。這個比較常見,直接接入官方說明文件。

  • model實體類,典型在轉化json的時候,必須保證model不被混淆,因此需加入--keep public class

JNI中呼叫的類以及方法不可被混淆

  • WebView中JavaScript呼叫的介面不混淆

  • AndroidMainfest、四大元件以及Application的子類不混淆

  • Parcelable的子類和Creator靜態成員變數不混淆,否則會產生Android.os.BadParcelableException異常

  • Layout佈局使用的View建構函式、android:onClick等。

三、混淆結果的檢測

在開啟混淆後,通過混淆配置打包後,會在目錄:

/outputs/mapping///..編譯型別>渠道名>複製程式碼

如圖所示:

Gradle  For Android(四)Gradle 編譯中神祕的混淆
gradle.png

下面大概說明下:

  • dump.txt :描述APK檔案中所有類的內部結構
  • mapping.txt :提供混淆前後類、方法、類成員等的對照表
  • seeds.txt:列出沒有被混淆的類和成員
  • usage.txt: 列出被移除的程式碼

我們可以根據 seeds.txt 檔案檢查未被混淆的類和成員中是否已包含所有期望保留的,再根據 usage.txt 檔案檢視是否有被誤移除的程式碼。建議讀者都試下去檢視下,必須有收穫~

四、自定義混淆規則

(一) proguard 引數
  • include {filename} 從給定的檔案中讀取配置引數

  • basedirectory {directoryname} 指定基礎目錄為以後相對的檔案名稱

  • injars {class_path} 指定要處理的應用程式jar,war,ear和目錄

  • outjars {class_path} 指定處理完後要輸出的jar,war,ear和目錄的名稱

  • libraryjars {classpath} 指定要處理的應用程式jar,war,ear和目錄所需要的程式庫檔案

  • dontskipnonpubliclibraryclasses 指定不去忽略非公共的庫類。

  • dontskipnonpubliclibraryclassmembers 不跳過非公共的庫的類成員

上面部分引數,日常移動開發的應用混淆中也不是經常使用到的,如果讀者想進一步深入瞭解每個引數實際作用,可以去官網進行查閱,這裡筆者不做太多說明哈。

######(二)keep 引數

  • keep {Modifier(屬性,例如public,以下說明一致)} {class_specification(具體類或者成員的位置,以下說明一致)} 防止類和成員被移除或者被重新命名

  • keepclassmembers {modifier} {class_specification} 防止成員被移除或者被重新命名

  • keepnames {class_specification} 防止成員被重新命名)

  • keepclassmembernames {class_specification} 防止擁有該成員的類和成員被移除或者被重新命名

  • keepclasseswithmembernames {class_specification} 防止擁有該成員的類和成員被重新命名

(三)混淆的必須知道的一些規則

1、一般規則形式:

[命令] [類] {
    [成員] 
}複製程式碼

:代表相關指定條件的類,例如

  • 具體的類
  • 訪問修飾符(publicprotectedprivate
  • 萬用字元*,匹配任意長度字元,但不含包名分隔符(.)
  • 萬用字元**,匹配任意長度字元,並且包含包名分隔符(.)
  • extends,即可以指定類的基類
  • implement,匹配實現了某介面的類
  • $,內部類

成員:代表指定類後符合指定條件的成員變數,例如

  • 匹配所有構造器
  • 匹配所有域
  • 匹配所有方法
  • 萬用字元*,匹配任意長度字元,但不含包名分隔符(.)
  • 萬用字元**,匹配任意長度字元,並且包含包名分隔符(.)
  • 萬用字元***,匹配任意引數型別
  • ,匹配任意長度的任意型別引數。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 這些方法。
  • 訪問修飾符(publicprotectedprivate

舉個例子,假如需要將name.huihui.test包下所有繼承Activitypublic類及其建構函式都保持住,可以這樣寫:

-keep public class name.huihui.test.** extends Android.app.Activity {
    
}複製程式碼

2、常用自定義混淆規則

  • 不混淆某個類
-keep public class name.huihui.example.Test { *; }複製程式碼
  • 不混淆某個包所有的類
-keep class name.huihui.test.** { *; }複製程式碼
  • 不混淆某個類的子類
-keep public class * extends name.huihui.example.Test { *; }複製程式碼
  • 不混淆所有類名中包含了“model”的類及其成員
-keep public class **.*model*.** {*;}複製程式碼
  • 不混淆某個介面的實現
-keep class * implements name.huihui.example.TestInterface { *; }複製程式碼
  • 不混淆某個類的構造方法
-keepclassmembers class name.huihui.example.Test { 
  public (); 
}複製程式碼
  • 不混淆某個類的特定的方法
-keepclassmembers class name.huihui.example.Test { 
  public void test(java.lang.String); 
}複製程式碼

五、自定義資源保持規則

前面說的通過shrinkResources true開啟資源壓縮後,未被使用的資源預設不會被打包進去。如果開發者是自定義保留指定的資原始檔,可以在res/raw/ 路徑下建立一個 xml 檔案,例如 keep.xml,進行自定義混淆規則。

一般屬性有:

  • tools:keep 定義哪些資源需要被保留(資源之間用“,”隔開)
  • tools:discard 定義哪些資源需要被移除(資源之間用“,”隔開)
  • tools:shrinkMode 開啟嚴格模式

例如:


複製程式碼

最後感謝@ 光源coder提供的混淆手冊,總結的不錯~

希望對有些開發者有幫助~具體檢視可以github上的demo,也歡迎加入開發交流群哈,詳情看個人簡介。下一篇是對gradle的混淆說明,歡迎讀者閱讀~

DEMO

Gradle For Android(四)Gradle編譯中神祕的混淆

傻小孩b mark共勉,寫給在成長路上奮鬥的你

相關文章