在 Gradle For Android(二) 多渠道打包與簽名配置中說過在日常開發中進行打包apk的一些往事,打包測試、打包上傳應用商店,這些都避免不了。當然在這過程中,除了簽名打包,當然少不了程式碼的混淆了。程式碼混淆,說白了就是程式碼壓縮、程式碼混淆以及資源壓縮的優化。依靠 ProGuard,將所有類名、方法名重新命名為無意義的簡單名稱,增加了逆向工程難度。依靠Gradle外掛,移除了沒有使用的資源,減少了apk大小 。
Gradle For Android 導讀
- Gradle For Android (一) 基礎定義與依賴管理篇
- Gradle For Android(二) 多渠道打包與簽名配置
- Gradle For Android(三)Gradle優化與靈活的使用技巧
正文:
一、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///..編譯型別>渠道名> 複製程式碼
如圖所示:
下面大概說明下:
- 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、一般規則形式:
[命令] [類] {
[成員]
}複製程式碼
類:代表相關指定條件的類,例如
- 具體的類
- 訪問修飾符(
public
、protected
、private
) - 萬用字元
*
,匹配任意長度字元,但不含包名分隔符(.) - 萬用字元
**
,匹配任意長度字元,並且包含包名分隔符(.) extends
,即可以指定類的基類implement
,匹配實現了某介面的類- $,內部類
成員:代表指定類後符合指定條件的成員變數,例如
- 匹配所有構造器
- 匹配所有域
- 匹配所有方法
- 萬用字元
*
,匹配任意長度字元,但不含包名分隔符(.) - 萬用字元
**
,匹配任意長度字元,並且包含包名分隔符(.) - 萬用字元
***
,匹配任意引數型別 …
,匹配任意長度的任意型別引數。比如void test(…)就能匹配任意void test(String a)
或者是void test(int a, String b)
這些方法。- 訪問修飾符(
public
、protected
、private
)
舉個例子,假如需要將name.huihui.test
包下所有繼承Activity
的public
類及其建構函式都保持住,可以這樣寫:
-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共勉,寫給在成長路上奮鬥的你