Android SDK 自帶了混淆工具 Proguard。它位于 SDK 根目錄 \tools\proguard 下面。如果開啟了混淆,Proguard 默認情況下會對所有代碼,包括第三方包都進行混淆(可能需要編寫混淆規則來保持不能被混淆的部分)。
安卓apk反編譯、作為 Android 開發者,如果你不想開源你的應用,那么在應用發布前,就需要對代碼進行混淆處理,從而讓我們代碼即使被反編譯,也難以閱讀。
默認開啟,用以減小應用體積,移除未被使用的類和成員,并且會在優化動作執行之后再次執行(因為優化后可能會再次暴露一些未被使用的類和成員)。
-dontshrink 關閉壓縮
默認開啟,在字節碼級別執行優化,讓應用運行的更快。
-dontoptimize 關閉優化
-optimizationpasses n 表示 proguard 對代碼進行迭代優化的次數,Android 一般為5。
默認開啟,增大反編譯難度,類和類成員會被隨機命名(像 a、b()、c之類的),除非用 keep 保護。
-dontobfuscate:關閉混淆。
在 app 的 gradle 文件中修改,在 android - buildTypes - release(debug)下:
android {...buildTypes {release {...//可優化字節碼zipAlignEnabled trueminifyEnabled trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}...
}
‘proguard-android.txt’ 是AndroidStudio默認自動導入的規則,這個文件位于Android SDK根目錄\tools\proguard\proguard-android.txt。這里面是一些比較常規的不能被混淆的代碼規則。
'proguard-rules.pro’是針對我們自己的項目需要特別定義混淆規則,它位于項目根目錄下面,里面的內容需要我們自己編寫。
zipAlignEnabled true 這個在打包時需要設置為true,能優化我們的java字節碼,提高運行效率。zipAlign 可以讓安裝包中的資源按4字節對齊,這樣可以減少應用在運行時的內存消耗。像Google Play 還強制要求開發者上傳的應用必須是經過 zipAlign 的,
從上面說明知道,我們要編寫的混淆規則就是寫在 proguard-rules.pro 文件中,在 AS 左邊列表中很好找到,每個 module 都有自己的 proguard-rules.pro 文件,我們對應著改就可以。
下面說說簡單的混淆規則,好多文章都講的不是清楚:
-keep class cn.hadcn.test.*
-keep class cn.hadcn.test.**
一顆星表示只是保持該包下的類名,而子包下的類名還是會被混淆;兩顆星表示把本包和所含子包下的類名都保持;
用以上方法保持類后,你會發現類名雖然未混淆,但里面的具體方法和變量命名還是變了,這時如果既想保持類名,又想保持里面的內容不被混淆,我們就需要以下方法了:
-keep class com.silence.test.* {*;}
-keep class com.silence.test.** {*;}
在此基礎上,我們也可以使用 Java 的基本規則來保護特定類不被混淆,比如我們可以用 extend,implement 等這些 Java 規則。如下例子就避免所有繼承 Activity 的類被混淆
-keep public class * extends android.app.Activity
如果我們要保留一個類中的內部類不被混淆則需要用$符號,如下例子表示保持 TestFragment 內部類 MyClass 中的所有 public 內容不被混淆。
-keep class com.silence.TestFragment$MyClass{public*;}
再者,如果一個類中你不希望保持全部內容不被混淆,而只是希望保護類下的特定內容(包含類名),就可以使用
<init>;//匹配所有構造器
<fields>;//匹配所有域
<methods>;//匹配所有方法方法
你還可以在或前面加上 private 、public、native 等來進一步指定不被混淆的內容,如
-keep class cn.hadcn.test.One{public<methods>;}
表示 One 類下的所有public方法都不會被混淆,當然你還可以加入參數,比如以下表示用JSONObject 作為入參的構造函數不會被混淆
-keep class cn.hadcn.test.One{public<init>(org.json.JSONObject);}
應該注意到上面我們都保持類(使用 keep),實際還可以僅保持類成員,但是簡單使用的話,上面應該足夠了,其他的下面列舉一下:
作用范圍 | 保持所指定類、成員 | 所指定類、成員在壓縮階段沒有被刪除,才能被保持 |
---|---|---|
類和類成員 | -keep | -keepnames |
僅類成員 | -keepclassmembers | -keepclassmembernames |
類和類成員(前提是成員都存在) | -keepclasseswithmembers | -keepclasseswithmembernames |
#混淆時不生成大小寫混合的類名
-dontusemixedcaseclassnames
#不忽略非公共的類庫
-dontskipnonpubliclibraryclasses
#混淆過程中打印詳細信息
-verbose#關閉優化
-dontoptimize
#不預校驗
-dontpreverify# Annotation注釋不能混淆
-keepattributes *Annotation*
#對于NDK開發 本地的native方法不能被混淆
-keepclasseswithmembernames class * {native <methods>;
}
#保持View的子類里面的set、get方法不被混淆(*代替任意字符)
-keepclassmembers public class * extends android.view.View {void set*(***);*** get*();
}#保持Activity子類里面的參數類型為View的方法不被混淆,如被XML里面應用的onClick方法
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {public void *(android.view.View);
}#保持枚舉類型values()、以及valueOf(java.lang.String)成員不被混淆
-keepclassmembers enum * {public static **[] values();public static ** valueOf(java.lang.String);
}#保持實現Parcelable接口的類里面的Creator成員不被混淆
-keepclassmembers class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator CREATOR;
}#保持R類靜態成員不被混淆
-keepclassmembers class **.R$* {public static <fields>;
}#不警告support包中不使用的引用
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
#保持使用了Keep注解的方法以及類不被混淆
-keepclasseswithmembers class * {@android.support.annotation.Keep <methods>;
}
#保持使用了Keep注解的成員域以及類不被混淆
-keepclasseswithmembers class * {@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {@android.support.annotation.Keep <init>(...);
}
上面默認的規則中指示了些需要保持不能別混淆的代碼,包括:
繼承至 Android 組件(Activity, Service…)的類。
自定義控件,繼承至 View 的類(被xml文件引用到的,名字已經固定了的)
enum 枚舉
實現了 android.os.Parcelable 接口的
Android R文件
數據庫驅動…
Android support 包等
Android 的注釋不能混淆
-keepattributes *Annotation*
對于 NDK 開發 本地的 native 方法不能被混淆
-keepclasseswithmembernames class * { native <methods>; }
自定義控件不進行混淆
枚舉類不被混淆
反射類不進行混淆
實體類不被混淆
JS調用的Java方法
四大組件不進行混淆
JNI 中調用類不進行混淆
Layout布局使用的View
構造函數、android:onClick
等
Parcelable
的子類和 Creator
靜態成員變量不混淆
第三方開源庫或者引用其他第三方的SDK包不進行混淆
對于特定的項目還有很多不能被混淆的,需要我們自己寫規則來指示,將在下面來說明:
#壓縮級別0-7,Android一般為5(對代碼迭代優化的次數)
-optimizationpasses 5 #不使用大小寫混合類名
-dontusemixedcaseclassnames #混淆時記錄日志
-verbose#不警告org.greenrobot.greendao.database包及其子包里面未應用的應用
-dontwarn org.greenrobot.greendao.database.**
-dontwarn rx.**
-dontwarn org.codehaus.jackson.**
......
#保持jackson包以及其子包的類和類成員不被混淆
-keep class org.codehaus.jackson.** {*;}
#--------重要說明-------
#-keep class 類名 {*;}
#-keepclassmembers class 類名{*;}
#一個*表示保持了該包下的類名不被混淆;
# -keep class org.codehaus.jackson.*
#二個**表示保持該包以及它包含的所有子包下的類名不被混淆
# -keep class org.codehaus.jackson.**
#------------------------
#保持類名、類里面的方法和變量不被混淆
-keep class org.codehaus.jackson.** {*;}
#不混淆類ClassTwoOne的類名以及類里面的public成員和方法
#public 可以換成其他java屬性如private、public static 、final等
#還可以使<init>表示構造方法、<methods>表示方法、<fields>表示成員,
#這些前面也可以加public等java屬性限定
-keep class com.dev.demo.two.ClassTwoOne {public *;
}
#不混淆類名,以及里面的構造函數
-keep class com.dev.demo.ClassOne {public <init>();
}
#不混淆類名,以及參數為int 的構造函數
-keep class com.dev.demo.two.ClassTwoTwo {public <init>(int);
}
#不混淆類的public修飾的方法,和private修飾的變量
-keepclassmembers class com.dev.demo.two.ClassTwoThree {public <methods>;private <fields>;
}
#不混淆內部類,需要用$修飾
#不混淆內部類ClassTwoTwoInner以及里面的全部成員
-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}
......
更多混淆模板說明,可以參考下面這篇博客:
https://www.jianshu.com/p/90feb5c50cce
一般我們開發的時候會自己寫一個 Log 封裝類,去解決正式版日志的開關,但是其實使用混淆文件也能起到正式版不輸出日志的用處,還能直接作用到代碼上,不用怕忘記刪 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(...);
}
成功打包后會在目錄 app\build\outputs\mapping\release 下生成幾個文件:
dump.txt 混淆后類的內部結構說明;
mapping.txt 混淆前與混淆后名稱對應關系;
seeds.txt 經過了一系列keep語句的保持,沒有被混淆的類,成員的名稱列表文件。
usage.txt 經過壓縮后被刪除的沒有使用的代碼,方法…等的名稱的列表文件
retrace工具:
混淆反推工具為 etrace.sh( Mac 平臺)或者 retrace.bat(Windows平臺),該工具在sdk根目錄\tools\proguard\bin\retrace.sh ( Windows 平臺類似);
命令格式:
./retrace.sh [mapping.txt目錄] [崩潰日歷目錄]
retrace.bat [mapping.txt目錄] [崩潰日歷目錄])
使用說明
第一步:保存 Crash 日志如下:截取日志保存為 txt 文件如:bug.txt。
03-21 03:09:32.389: E/AndroidRuntime(3582): FATAL EXCEPTION: main
03-21 03:09:32.389: E/AndroidRuntime(3582): Process: com.dev.demo, PID: 3582
03-21 03:09:32.389: E/AndroidRuntime(3582): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
03-21 03:09:32.389: E/AndroidRuntime(3582): at com.dev.demo.two.b.b(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582): at com.dev.demo.a.a.printTest(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582): at com.dev.demo.MainActivity.i(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582): at com.dev.demo.MainActivity.a(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582): at com.dev.demo.MainActivity$1.onClick(Unknown Source)
第二步:刪掉Crash日志中 **03-21 03:09:32.389: E/AndroidRuntime(3582)😗*部分,否則無法反推還原。刪除后如下:
FATAL EXCEPTION: main
Process: com.dev.demo, PID: 3582
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
at com.dev.demo.two.b.b(Unknown Source)
at com.dev.demo.a.a.printTest(Unknown Source)
at com.dev.demo.MainActivity.i(Unknown Source)
at com.dev.demo.MainActivity.a(Unknown Source)
at com.dev.demo.MainActivity$1.onClick(Unknown Source)
第三步:打開命令窗口輸入命令:如 😗*./retrace.sh mapping.txt bug.txt ** 如下:
按確認后得到結果如下:
FATAL EXCEPTION: main
Process: com.dev.demo, PID: 3582
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
at com.dev.demo.two.ClassTwoThree.void test()(Unknown Source)
at com.dev.demo.one.ClassOneOne.void printTest()(Unknown Source)
at com.dev.demo.MainActivity.void printWifi()(Unknown Source)
at com.dev.demo.MainActivity.void access$000(com.dev.demo.MainActivity)(Unknown Source)
at com.dev.demo.MainActivity$1.void onClick(android.view.View)(Unknown Source) ```
除了有 retrace.sh 工具外還有可視化工具,和 retrace.sh 同目錄下 proguardgui.sh,這是一塊 Progurad 的可視化工具(Windows平臺類似):
有時為了Crash日志更容易定位可以在規則里面添加:
-keepattributes SourceFile, LineNumberTable
這樣Crash日志里面就能保留類名稱和行號了。
以上就是我對混淆文件的一些總結,收集了一些資料整合了一下,具體參考文章如下:
希望本文對讀者有所幫助!
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态