混淆介紹
Proguard是一個Java類檔案壓縮器、優化器、混淆器、預校驗器。壓縮環節會檢測以及移除沒有用到的類、欄位、方法以及屬性。優化環節會分析以及優化方法的位元組碼。混淆環節會用無意義的短變數去重新命名類、變數、方法。這些步驟讓程式碼更精簡,更高效,也更難被逆向(破解)。
混淆後預設會在工程目錄app/build/outputs/mapping/release(debug)下生成一個mapping.txt檔案,這就是混淆規則,我們可以根據這個檔案把混淆後的程式碼反推回源本的程式碼,所以這個檔案很重要,注意保護好。原則上,程式碼混淆後越亂越無規律越好,但有些地方我們是要避免混淆的,否則程式執行就會出錯。
ProGuard常用操作
-
後面有備註Proguard官方文件,其他騷操作自行檢視即可
-
壓縮(Shrinking):預設開啟,用以減小應用體積,移除未被使用的類和成員,並且會在優化動作執行之後再次執行(因為優化後可能會再次暴露一些未被使用的類和成員)。
-dontshrink #關閉壓縮 複製程式碼
-
優化(Optimization):預設開啟,在位元組碼級別執行優化,讓應用執行的更快。
-dontoptimize #關閉優化 -optimizationpasses n #表示proguard對程式碼進行迭代優化的次數,Android一般為5 複製程式碼
-
混淆(Obfuscation):預設開啟,增大反編譯難度,類和類成員會被隨機命名,除非用keep保護。
-dontobfuscate #關閉混淆 複製程式碼
-
一顆星表示只是保持該包下的類名,而子包下的類名還是會被混淆;
-keep class com.thc.test.* 複製程式碼
-
兩顆星表示把本包和所含子包下的類名都保持;
-keep class com.thc.test.** 複製程式碼
(上面兩種方式保持類後,會發現類名雖然未混淆,但裡面的具體方法和變數命名還是變了)
-
既可以保持該包下的類名,又可以保持類裡面的內容不被混淆;
-keep class com.thc.test.*{*;} 複製程式碼
-
既可以保持該包及子包下的類名,又可以保持類裡面的內容不被混淆;
-keep class com.thc.test.**{*;} 複製程式碼
-
保持某個類名不被混淆(但是內部內容會被混淆)
-keep class com.xlpay.sqlite.cache.BaseDaoImpl 複製程式碼
-
保持某個類的 類名及內部的所有內容不會混淆
-keep class com.xlpay.sqlite.cache.BaseDaoImpl{*;} 複製程式碼
-
保持類中特定內容,而不是所有的內容可以使用如下:
-keep class com.thc.gradlestudy.MyProguardBean{ <init>; #匹配所有構造器 <fields>;#匹配所有域 <methods>;#匹配所有方法 } 複製程式碼
上面就保持住了MyProguardBean這個類中的所有的構造方法、變數、和方法
-
可以在或前面加上private 、public、native等來進一步指定不被混淆的內容
-keep class com.xlpay.sqlite.cache.BaseDaoImpl{ public <methods>;#保持該類下所有的共有方法不被混淆 public *;#保持該類下所有的共有內容不被混淆 private <methods>;#保持該類下所有的私有方法不被混淆 private *;#保持該類下所有的私有內容不被混淆 public <init>(java.lang.String);#保持該類的String型別的構造方法 } 複製程式碼
-
在方法後加入引數,限制特定的方法(經測試:僅限於構造方法可以混淆)
-keep class com.thc.gradlestudy.MyProguardBean{ public <init>(String); } 複製程式碼
-
要保留一個類中的內部類不被混淆需要用 $ 符號
#保持ProguardTest中的MyClass不被混淆 -keep class com.xlpay.sqlite.cache.ProguardTest$MyClass{*;} 複製程式碼
-
使用Java的基本規則來保護特定類不被混淆,比如用extends,implement等這些Java規則,如下:保持Android底層元件和類不要混淆
-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.view.View 複製程式碼
-
如果不需要保持類名,只需要保持該類下的特定方法保持不被混淆,需要使用keepclassmembers,而不是keep,因為keep方法會保持類名。
#保持ProguardTest類下test(String)方法不被混淆 -keepclassmembernames class com.xlpay.sqlite.cache.ProguardTest{ public void test(java.lang.String); } 複製程式碼
-
如果擁有某成員,保留類和類成員
-keepclasseswithmembernames class com.xlpay.sqlite.cache.ProguardTest 複製程式碼
注意事項
-
jni方法不可混淆,因為native方法是要完整的包名_類名_方法名來定義的,不能修改,否則找不到;
#保持native方法不被混淆 -keepclasseswithmembernames class * { native <methods>; } 複製程式碼
-
反射用到的類混淆時需要注意:只要保持反射用到的類名和方法即可,並不需要將整個被反射到的類都進行保持
-
AndroidMainfest中的類不混淆,所以四大元件和Application的子類和Framework層下所有的類預設不要進行混淆。自定義的View預設也不會被混淆
-
與服務端互動時,使用GSON、fastjson等框架解析服務端資料時,所寫的JSON物件類不混淆,否則無法將JSON解析成對應的物件;
-
使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆檔案中加入對應的混淆規則;
-
有用到WebView的JS呼叫也需要保證寫的介面方法不混淆,原因和第一條一樣;
-
Parcelable的子類和Creator靜態成員變數不混淆,否則會產生Android.os.BadParcelableException異常;
-keep class * implements Android.os.Parcelable { # 保持Parcelable不被混淆 public static final Android.os.Parcelable$Creator *; } 複製程式碼
-
使用enum型別時需要注意避免以下兩個方法混淆,因為enum類的特殊性,以下兩個方法會被反射呼叫,見第二條規則。
-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } 複製程式碼
-
建議:
釋出一款應用除了設minifyEnabled為ture,你也應該設定zipAlignEnabled為true,像Google Play強制要求開發者上傳的應用必須是經過zipAlign的,zipAlign可以讓安裝包中的資源按4位元組對齊,這樣可以減少應用在執行時的記憶體消耗。
混淆情況記錄:
例子中使用:classA和classB,在加混淆的情況下多種結果:
-
如果classA沒有被keep,則不會看到classA的class檔案
-
如果classA沒有被keep,classB被保持,同時classB引用到了classA,這個時候能夠看到被混淆的classA的class檔案,如顯示為a
-
如果classA中通過反射,獲取到classB,那麼classB的類名及反射用到的方法必須keep住
-
jar包混淆,暴露出的類、方法、方法的引數需要keep住
-
情況說明:工程Demo依賴了 小米渠道的依賴,小米依賴又依賴了Common,對Common進行混淆但是不對小米渠道混淆,那麼小米的依賴中使用到的Common中的類都需要keep住