Android混淆精煉詳解+常用庫混淆+通用混淆模板
一、前期基礎知識詳解
筆者最近開發一個專案,是在開源專案基礎上構築的,在最近一個版本釋出前夕,出現了一個奇怪的問題:在測試手機上執行的應用,功能正常,但是在打出release正式包之後,測試時發現上傳功能失效,不能上傳資料,即debug包功能正常,release包功能出問題。測試了很久最後確認是混淆檔案出了問題,之前專案中使用的混淆檔案是原來開源專案帶的,而上傳功能是後來自己寫的,而加了功能之後,沒有對修改混淆檔案。上傳的功能中用到了Gson來實現。
使用Gson是需要處理混淆檔案的。而且有很多其他的常用框架或者庫在使用時都需要對混淆進行處理。今天就趁著這篇部落格梳理一下混淆這一知識點。
1)啟用混淆檔案
開發中直接使用Android Studio即可實現啟用混淆檔案。Android Studio自身整合Java語言的ProGuard作為壓縮,優化和混淆工具,配合Gradle構建工具使用很簡單,只需要在工程應用目錄的gradle檔案中設定minifyEnabled為true即可。然後我們就可以到proguard-rules.pro檔案中加入我們的混淆規則。
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
2)ProGuard作用
①壓縮(Shrinking):預設開啟,用以減小應用體積,移除未被使用的類和成員,會在優化動作執行之後再次執行。
②優化(Optimization):預設開啟,在位元組碼級別執行優化,讓應用執行的更快。
③混淆(Obfuscation):預設開啟,增大反編譯難度,類和類成員會被隨機命名,除非用keep保護。
3)Proguard生成檔案
在Android Studio中啟用Proguard對程式碼進行混淆,在打出正式包之後,會在build/outputs/mapping/release目錄下生成四個檔案。
①dump.txt:列出生成的APK檔案中所有class檔案的內部結構;
②mapping
③seeds.txt:列出為混淆的類和成員;
④usage.txt:列出從apk檔案中剝離的程式碼;
其中mapping.txt檔案中寫明瞭混淆的規則,有心人可根據這個檔案把混淆後的程式碼反推匯出原來專案中的程式碼,所以這個檔案要保護好。
(ProGuard無法對字串進行混淆,所以在有些反編譯得到專案中使用關鍵字http/https搜尋有時會得到一些重要資訊,比如競品使用了什麼API介面,提交資料到哪個網址等等,字串尚且有如此大的威力,遑論被推匯出的原始程式碼。)
所以,一般來說,檔案被混淆的越厲害越無規律越安全,但在很多時候(事實上是每一個正式專案),我們是不能放任ProGuard混淆所有檔案的,這時,我們就需要自主處理混淆檔案,掌握混淆規則,避免一些地方被混淆導致程式執行出錯。
二、上混淆檔案,分析混淆規則
總體來說,混淆檔案中我們自主編寫的混淆規則大概有四類:
1)通用混淆配置,基本上每個專案都會使用到,這段配置幾乎是固定的;
# 程式碼混淆壓縮比,在0~7之間
-optimizationpasses 5
# 混合時不使用大小寫混合,混合後的類名為小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 不做預校驗,preverify是proguard的四個步驟之一,Android不需要preverify,去掉這一步能夠加快混淆速度。
-dontpreverify
-verbose
#google推薦演算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、內部類、泛型、匿名類
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 重新命名丟擲異常時的檔名稱
-renamesourcefileattribute SourceFile
# 丟擲異常時保留程式碼行號
-keepattributes SourceFile,LineNumberTable
# 處理support包
-dontnote android.support.**
-dontwarn android.support.**
# 保留四大元件,自定義的Application等這些類不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留列舉類不被混淆
-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 *;
}
#第三方jar包不被混淆
-keep class com.github.test.** {*;}
#保留自定義的Test類和類成員不被混淆
-keep class com.lily.Test {*;}
#保留自定義的xlog資料夾下面的類、類成員和方法不被混淆
-keep class com.test.xlog.** {
<fields>;
<methods>;
}
#assume no side effects:刪除android.util.Log輸出的日誌
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
#保留Keep註解的類名和方法
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
@android.support.annotation.Keep *;
}
2)常用庫和框架的混淆配置;
使用依賴庫之前需先檢視文件,決定是否需要處理,在此列出一些常用的需要配置混淆的庫;(注:請記住常用的需要編寫混淆檔案的庫!)
Android混淆配置總結-持續更新
整理Android最全的混淆規則大全(最新的開源框架混淆)
Gson
Butterknife
SlidingMenu
ImageLoader
Okhttputils
Glide
picasso
okhttp
okio
EventBus
dagger
rxJava
retrofit2
greendao
fresco
crashlytics
fastjson
litepal
注意,這些庫或者框架的混淆檔案不是所見即所得,不可以直接複製過去使用,而要看清楚文件的說明再進行操作,把一些路徑名包名換成對應專案的。
3)專案中使用了第三方SDK;
ShareSDK
環信SDK
支付寶支付
微信支付
高德地圖
友盟推送
極光推送
融雲
注意:使用了第三方SDK時,閱讀最新的第三方提供的文件即可拿到混淆檔案,按照文件操作即可。
4)自定義混淆規則,保證一些類、類方法、類名不被混淆;
很多時候,我們在專案中使用了一些功能,需要有明確的指定,這個時候檔案或者檔案中的某一部分是不能被混淆的,比如說自定義view,在xml中使用的,自定義view是帶有完整路徑名的,如果此時沒有處理混淆檔案,那麼一個個路徑名變成了a.b.c,那麼專案就無法運行了。以下是常用的自定義的混淆規則:
①常見的混淆指令
optimizationpasses
dontoptimize
dontusemixedcaseclassnames
dontskipnonpubliclibraryclasses
dontpreverify
dontwarn
verbose
optimizations
keep
keepnames
keepclassmembers
keepclassmembernames
keepclasseswithmembers
keepclasseswithmembernames
②獨立列出與保持相關檔案不參與混淆的幾種命令
keep 保留類和類中的成員,防止被混淆或被移除。
keepnames 保留類和類中的成員,防止被混淆,但當成員沒有被引用時會被移除。
keepclassmembers 只保留類中的成員,防止被混淆或被移除。
keepclassmembernames 只類中的成員,防止被混淆,但當成員沒有被引用時會被移除。
keepclasseswithmembers 保留類和類中的成員,防止被混淆或被移除。前提是指明的類中的成員必須存在。如果不存在,還是會被混淆。
keepclasseswithmembernames 保留類和類中的成員,防止被混淆,但當成員沒有被引用時會被移除。前提是指明的類中的成員必須存在。如果不存在,還是會被混淆。
③保持元素不參與混淆的規則
[保持命令] [類] {
[成員]
}
不混淆某個類
-keep public class com.biaobiao.example.Test { *; }
不混淆某個包的所有類
-keep class com.biaobiao.test.** { *; }
不混淆某個類的子類
-keep public class * extends com.biaobiao.example.Test { *; }
不混淆所有類名中包含了“model”的類及其成員
-keep public class * extends com.biaobiao.example.Test { *; }
不混淆某個介面的實現
-keep class * implements com.biaobiao.example.TestInterface { *; }
不混淆某個類的構造方法
-keepclassmembers class com.biaobiao.example.Test {
public <init>();
}
不混淆某個類的特定的方法
-keepclassmembers class com.biaobiao.example.Test {
public void test(java.lang.String);
}
不混淆某個類的內部類
-keep class com.biaobiao.example.Test$* {
*;
}
三、混淆檔案通用模板
# 指定程式碼的壓縮級別 0 - 7(指定程式碼進行迭代優化的次數,在Android裡面預設是5,這條指令也只有在可以優化時起作用。)
-optimizationpasses 5
# 混淆時不會產生形形色色的類名(混淆時不使用大小寫混合類名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的庫類(不跳過library中的非public的類)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可見的庫類的成員
-dontskipnonpubliclibraryclassmembers
#不進行優化,建議使用此選項,
-dontoptimize
# 不進行預校驗,Android不需要,可加快混淆速度。
-dontpreverify
# 遮蔽警告
-ignorewarnings
# 指定混淆是採用的演算法,後面的引數是一個過濾器
# 這個過濾器是谷歌推薦的演算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保護程式碼中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 這在JSON實體對映時非常重要
-keepattributes Signature
# 丟擲異常時保留程式碼行號
-keepattributes SourceFile,LineNumberTable
#優化時允許訪問並修改有修飾符的類和類的成員,這可以提高優化步驟的結果。
# 比如,當內聯一個公共的getter方法時,這也可能需要外地公共訪問。
# 雖然java二進位制規範不需要這個,要不然有的虛擬機器處理這些程式碼會有問題。當有優化和使用-repackageclasses時才適用。
#指示語:不能用這個指令處理庫中的程式碼,因為有的類和類成員沒有設計成public ,而在api中可能變成public
-allowaccessmodification
#當有優化和使用-repackageclasses時才適用。
-repackageclasses ''
# 混淆時記錄日誌(列印混淆的詳細資訊)
# 這句話能夠使我們的專案混淆後產生對映檔案
# 包含有類名->混淆後類名的對映關係
-verbose
# ----------------------------- 預設保留 -----------------------------
# 保持哪些類不被混淆
#繼承activity,application,service,broadcastReceiver,contentprovider....不進行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-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
-keep public class * extends android.view.View
-keep class android.support.** {*;}## 保留support下的所有類及其內部類
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面宣告的類,最後這兩個類我們基本也用不上,是接入Google原生的一些服務時使用的。
#----------------------------------------------------
# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
#表示不混淆任何包含native方法的類的類名以及native方法名,這個和我們剛才驗證的結果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
#這個主要是在layout 中寫的onclick方法android:onclick="onClick",不進行混淆
#表示不混淆Activity中引數是View的方法,因為有這樣一種用法,在XML中配置android:onClick=”buttonClick”屬性,
#當用戶點選該按鈕時就會呼叫Activity中的buttonClick(View view)方法,如果這個方法被混淆的話就找不到了
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#表示不混淆列舉中的values()和valueOf()方法,列舉我用的非常少,這個就不評論了
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#表示不混淆任何一個View中的setXxx()和getXxx()方法,
#因為屬性動畫需要有相應的setter和getter的方法實現,混淆了就無法工作了。
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#表示不混淆Parcelable實現類中的CREATOR欄位,
#毫無疑問,CREATOR欄位是絕對不能改變的,包括大小寫都不能變,不然整個Parcelable工作機制都會失敗。
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 這指定了繼承Serizalizable的類的如下成員不被移除混淆
-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();
}
# 保留R下面的資源
#-keep class **.R$* {
# *;
#}
#不混淆資源類下static的
-keepclassmembers class **.R$* {
public static <fields>;
}
# 對於帶有回撥函式的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 保留我們自定義控制元件(繼承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#
#----------------------------- WebView(專案中沒有可以忽略) -----------------------------
#
#webView需要進行特殊處理
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
#在app中與HTML5的JavaScript的互動進行特殊處理
#我們需要確保這些js要呼叫的原生方法不能夠被混淆,於是我們需要做如下處理:
-keepclassmembers class com.ljd.example.JSInterface {
<methods>;
}
#
#---------------------------------實體類---------------------------
#--------(實體Model不能混淆,否則找不到對應的屬性獲取不到值)-----
#
-dontwarn com.suchengkeji.android.confusiondemo.md.**
#對含有反射類的處理
-keep class com.suchengkeji.android.confusiondemo.md.** { *; }
#
# ----------------------------- 其他的 -----------------------------
#
# 刪除程式碼中Log相關的程式碼
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# 保持測試相關的程式碼
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
#
# ----------------------------- 第三方庫、框架、SDK -----------------------------
#
-dontwarn com.orhanobut.logger.**
-keep class com.orhanobut.logger.**{*;}
-keep interface com.orhanobut.logger.**{*;}
-dontwarn com.google.gson.**
-keep class com.google.gson.**{*;}
-keep interface com.google.gson.**{*;}
四、其他一些混淆注意事項
①jni方法不可混淆,因為這個方法需要和native方法保持一致;
-keepclasseswithmembernames class * { # 保持native方法不被混淆
native <methods>;
}
②反射用到的類不混淆(否則反射可能出現問題);
③AndroidMainfest中的類不混淆,所以四大元件和Application的子類和Framework層下所有的類預設不會進行混淆。自定義的View預設也不會被混淆;所以像網上貼的很多排除自定義View,或四大元件被混淆的規則在Android Studio中是無需加入的;
④與服務端互動時,使用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);
}