代碼混淆(Obfuscated code)亦稱花指令,是將計(jì)算機(jī)程序的代碼,轉(zhuǎn)換成一種功能上等價,但是難于閱讀和理解的形式的行為。
為什么要加代碼混淆
--------------------不想開源應(yīng)用,為了加大反編譯的成本,但是并不能徹底防止反編譯
開啟混淆
-
通常我們需要找到項(xiàng)目路徑下app目錄下的build.gradle文件
-
找到minifyEnabled這個配置,然后設(shè)置為true即可.
如下:
release{
minifyEnabled true//是否啟動混淆 ture:打開 false:關(guān)閉
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
proguard-rules.pro文件的作用
- 只要在工程應(yīng)用目錄的
gradle文件中設(shè)置minifyEnabled:true即可。然后我們就可以到proguard-rules.pro文件中加入我們的混淆規(guī)則了
proguard是什么
- Proguard是一個集文件壓縮,優(yōu)化,混淆和校驗(yàn)等功能的工具
- 它檢測并刪除無用的類,變量,方法和屬性
- 它優(yōu)化字節(jié)碼并刪除無用的指令.
- 它通過將類名,變量名和方法名重命名為無意義的名稱實(shí)現(xiàn)混淆效果.
- 最后它還校驗(yàn)處理后的代碼
混淆的常見配置
- Proguard關(guān)鍵字
| Proguard關(guān)鍵字 | 描述 |
|---|---|
| dontwarn | dontwarn是一個和keep可以說是形影不離,尤其是處理引入的library時. |
| keep | 保留類和類中的成員,防止被混淆或移除 |
| keepnames | 保留類和類中的成員,防止被混淆,成員沒有被引用會被移除 |
| keepclassmembers | 只保留類中的成員,防止被混淆或移除 |
| keepclassmembernames | 只保留類中的成員,防止被混淆,成員沒有引用會被移除 |
| keepclasseswithmembers | 保留類和類中的成員,防止被混淆或移除,保留指明的成員 |
| keepclasseswithmembernames | 保留類和類中的成員,防止被混淆,保留指明的成員,成員沒有引用會被移除 |
如:
(1)保留某個包下面的類以及子包
-keep public class com.droidyue.com.widget.**
(2)保留所有類中使用otto的public方法
# Otto
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
(3)保留Contants類的BOOK_NAME屬性
-keepclassmembers class com.example.admin.proguardsample.Constants {
public static java.lang.String BOOK_NAME;
}
(4)dontwarn:
引入的library可能存在一些無法找到的引用和其他問題,在build時可能會發(fā)出警告,如果我們不進(jìn)行處理,通常會導(dǎo)致build中止.因此為了保證build繼續(xù),我們需要使用dontwarn處理這些我們無法解決的library的警告.
#比如關(guān)閉Twitter sdk的警告,我們可以這樣做
-dontwarn com.twitter.sdk.**
- Proguard通配符
| Proguard通配符 | 描述 |
|---|---|
| <field> | 匹配類中的所有字段 |
| <method> | 匹配類中所有的方法 |
| <init> | 匹配類中所有的構(gòu)造函數(shù) |
| * | 匹配任意長度字符,不包含包名分隔符(.) |
| ** | 匹配任意長度字符,包含包名分隔符(.) |
| *** | 匹配任意參數(shù)類型 |
| ... | ... |
- 基本規(guī)則
//不混淆某個類
-keep public class name.huihui.example.Test { *; }
//不混淆某個類的子類
-keep public class * extends name.huihui.example.Test { *; }
//不混淆所有類名中包含了“model”的類及其成員
-keep public class **.*model*.** {*;}
//不混淆某個接口的實(shí)現(xiàn)
-keep class * implements name.huihui.example.TestInterface { *; }
//不混淆某個類的構(gòu)造方法
-keepclassmembers class name.huihui.example.Test {
public <init>();
}
//不混淆某個類的特定的方法
-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
//不混淆某個類的內(nèi)部類
-keep class name.huihui.example.Test$* {
*;
}
//兩個常用的混淆命令,注意:
//一顆星表示只是保持該包下的類名,而子包下的類名還是會被混淆;
//兩顆星表示把本包和所含子包下的類名都保持;
-keep class com.suchengkeji.android.ui.**
-keep class com.suchengkeji.android.ui.*
//用以上方法保持類后,你會發(fā)現(xiàn)類名雖然未混淆,但里面的具體方法和變量命名還是變了,
//如果既想保持類名,又想保持里面的內(nèi)容不被混淆,我們就需要以下方法了
//不混淆某個包所有的類
-keep class com.suchengkeji.android.bean.** { *; }
//在此基礎(chǔ)上,我們也可以使用Java的基本規(guī)則來保護(hù)特定類不被混淆,比如我們可以用extend,implement等這些Java規(guī)則。如下
# 保留我們使用的四大組件,自定義的Application等等這些類不被混淆
# 因?yàn)檫@些子類都有可能被外部調(diào)用
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
哪些不應(yīng)該混淆
- 使用了自定義控件那么要保證它們不參與混淆
- 使用了枚舉要保證枚舉不被混淆
- 對第三方庫中的類不進(jìn)行混淆
- 運(yùn)用了反射的類也不進(jìn)行混淆
- 使用了 Gson 之類的工具要使 JavaBean 類即實(shí)體類不被混淆
- 在引用第三方庫的時候,一般會標(biāo)明庫的混淆規(guī)則的,建議在使用的時候就把混淆規(guī)則添加上去,免得到最后才去找
- 有用到 WebView 的 JS 調(diào)用也需要保證寫的接口方法不混淆,原因和第一條一樣
- Parcelable 的子類和 Creator 靜態(tài)成員變量不混淆,否則會產(chǎn)生 Android.os.BadParcelableException 異常
- 使用的四大組件,自定義的Application* 實(shí)體類
- JNI中調(diào)用的類
- Layout布局使用的View構(gòu)造函數(shù)(自定義控件)、android:onClick等。
來一個傻瓜式混淆模板
#
#-------------------------------------------基本不用動區(qū)域----------------------------------------------
#
#
# -----------------------------基本 -----------------------------
#
# 指定代碼的壓縮級別 0 - 7(指定代碼進(jìn)行迭代優(yōu)化的次數(shù),在Android里面默認(rèn)是5,這條指令也只有在可以優(yōu)化時起作用。)
-optimizationpasses 5
# 混淆時不會產(chǎn)生形形色色的類名(混淆時不使用大小寫混合類名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的庫類(不跳過library中的非public的類)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可見的庫類的成員
-dontskipnonpubliclibraryclassmembers
#不進(jìn)行優(yōu)化,建議使用此選項(xiàng),
-dontoptimize
# 不進(jìn)行預(yù)校驗(yàn),Android不需要,可加快混淆速度。
-dontpreverify
# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的參數(shù)是一個過濾器
# 這個過濾器是谷歌推薦的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保護(hù)代碼中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 這在JSON實(shí)體映射時非常重要
-keepattributes Signature
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
#優(yōu)化時允許訪問并修改有修飾符的類和類的成員,這可以提高優(yōu)化步驟的結(jié)果。
# 比如,當(dāng)內(nèi)聯(lián)一個公共的getter方法時,這也可能需要外地公共訪問。
# 雖然java二進(jìn)制規(guī)范不需要這個,要不然有的虛擬機(jī)處理這些代碼會有問題。當(dāng)有優(yōu)化和使用-repackageclasses時才適用。
#指示語:不能用這個指令處理庫中的代碼,因?yàn)橛械念惡皖惓蓡T沒有設(shè)計(jì)成public ,而在api中可能變成public
-allowaccessmodification
#當(dāng)有優(yōu)化和使用-repackageclasses時才適用。
-repackageclasses ''
# 混淆時記錄日志(打印混淆的詳細(xì)信息)
# 這句話能夠使我們的項(xiàng)目混淆后產(chǎn)生映射文件
# 包含有類名->混淆后類名的映射關(guān)系
-verbose
#
# ----------------------------- 默認(rèn)保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些類不被混淆
#繼承activity,application,service,broadcastReceiver,contentprovider....不進(jìn)行混淆
-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下的所有類及其內(nèi)部類
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面聲明的類,最后這兩個類我們基本也用不上,是接入Google原生的一些服務(wù)時使用的。
#----------------------------------------------------
# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
#表示不混淆任何包含native方法的類的類名以及native方法名,這個和我們剛才驗(yàn)證的結(jié)果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
#這個主要是在layout 中寫的onclick方法android:onclick="onClick",不進(jìn)行混淆
#表示不混淆Activity中參數(shù)是View的方法,因?yàn)橛羞@樣一種用法,在XML中配置android:onClick=”buttonClick”屬性,
#當(dāng)用戶點(diǎn)擊該按鈕時就會調(diào)用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()方法,
#因?yàn)閷傩詣赢嬓枰邢鄳?yīng)的setter和getter的方法實(shí)現(xiàn),混淆了就無法工作了。
-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實(shí)現(xiàn)類中的CREATOR字段,
#毫無疑問,CREATOR字段是絕對不能改變的,包括大小寫都不能變,不然整個Parcelable工作機(jī)制都會失敗。
-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>;
}
# 對于帶有回調(diào)函數(shù)的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(項(xiàng)目中沒有可以忽略) -----------------------------
#
#webView需要進(jìn)行特殊處理
-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的交互進(jìn)行特殊處理
#我們需要確保這些js要調(diào)用的原生方法不能夠被混淆,于是我們需要做如下處理:
-keepclassmembers class com.ljd.example.JSInterface {
<methods>;
}
#
#---------------------------------實(shí)體類---------------------------------
#--------(實(shí)體Model不能混淆,否則找不到對應(yīng)的屬性獲取不到值)-----
#
-dontwarn com.suchengkeji.android.confusiondemo.md.**
#對含有反射類的處理
-keep class com.suchengkeji.android.confusiondemo.md.** { *; }
#
# ----------------------------- 其他的 -----------------------------
#
# 刪除代碼中Log相關(guān)的代碼
-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(...);
}
# 保持測試相關(guān)的代碼
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
#
# ----------------------------- 第三方 -----------------------------
#
-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.**{*;}
# 。。。。。。
下面來看看混淆和不混淆的區(qū)別(以本demo為例)
先看下我項(xiàng)目中的目錄:

項(xiàng)目中的目錄.png
上圖中的目錄很清楚,bean(md)中共兩個實(shí)體類只用
Person一個Activity和一個ListView的Adapter:ListAdapter在看看沒有混淆反編譯后的目錄:

未混淆的目錄.png
上圖可以清楚地看到,為混淆的代碼,可以清楚明確的看到代碼中的結(jié)構(gòu)、方法,反編譯后容易被拿到源碼
在看看混淆后的目錄:

混淆后目錄.png
可以從混淆后的目錄看到,代碼明顯暈未混淆的不同,屬性方法基本都用啊,b,c...表示了,更不容易看懂代碼結(jié)果和方法,這就減少了源碼的外泄等
注意:
有人可能想問為什么我的項(xiàng)目中的md(bean)文件中有一個Datas實(shí)體類,混淆后為什么目錄沒見到,因?yàn)樵诨煜渲弥虚_啟了移除無用的resource文件所以在沒引用到該資源的情況下在打包時會自動刪除該資源
應(yīng)用添加混淆就是不想開源應(yīng)用,為了加大反編譯的成本,但是并不能徹底防止反編譯構(gòu)建時Proguard都會輸出:dump.txt(說明APK中所有類文件的內(nèi)部結(jié)構(gòu))、mapping.txt(提供原始與混淆過的類、方法和字段名稱之間的轉(zhuǎn)換)、seeds.txt(列出未進(jìn)行混淆的類和成員)、usage.txt(列出從APK移除的代碼),輸出路徑為:
F:\...\ConfusionDemo\app\build\outputs\mapping
更多關(guān)于Proguard混淆,直戳去官網(wǎng)
2021-04-01 補(bǔ)充帶 throws 的異常拋出方法混淆:
最近開發(fā)中在打包生產(chǎn)一個
.jar文件的時候,方法本事是有異常拋出,但是打包后沒有異常拋出,經(jīng)過查找就是一句代碼的問題,如下,在混淆中這樣添加
-keepattributes Exceptions