最近在做Android應用的混淆,踩了一些坑,這裏記錄分享下個人的心得。
混淆介紹
首先先簡單說一下什麼是混淆和混淆的作用,其實這個搜索下可以找到一堆官方的說法等等,這裏簡單口語敘述一下,混淆就是把代碼替換成a、b、c基本字母組成的代碼,比如一個方法名爲:function(),混淆後可能會被替換成a()。
混淆的好處:
- 代碼混淆後閱讀性降低,反編譯後破譯程序難度提高
- 混淆後字節數減少,減少了應用了體積
前者只能說有一點作用,後者則需要看代碼的數量
當然不能忽視混淆的缺點:
- 混淆後,測試不充分可能導致某些功能不能使用
開啓混淆
混淆在android Studio的項目中默認是關閉的,其中控制開關和規則配置文件分別由項目moudle中的build.gradle和proguard-rules.pro控制,如下圖所示:
其中build.gradle中
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
其中
minifyEnabled false
- 1
- 1
表示不開啓混淆,可以改爲
minifyEnabled true
- 1
- 1
開啓混淆,開啓混淆後可以添加一句:
shrinkResources true
- 1
- 1
表示去掉沒有引用的資源,可以減少應用的體積,但這個只有在混淆開啓後纔有效。
混淆規則文件
可以從上述的代碼看出,Android自帶一個混淆規則文件:
proguard-android.txt
- 1
- 1
這個文件在SDK目錄下,裏面有一些默認自帶的規則,而我們今天需要配置的自定義配置的文件,即上面所述的
proguard-rules.pro
- 1
- 1
混淆規則基本語法
混淆文件採用白名單法,意思是不在白名單裏面的都要混淆。
混淆規則的基本符號:
# 代表行註釋符
- 表示一條規則的開始
- 1
- 2
- 1
- 2
一般規則用連起來單詞表示,主要有:
keep 保留,例如keepattributes:表示保留屬性
dont 不要,例如dontwarn:表示不要提示警告
ignore 忽略,例如ignorewarning:表示忽略警告
- 1
- 2
- 3
- 1
- 2
- 3
混淆配置文件不檢查規則是否重複,如果兩條規則衝突,則採用白名單的,比如設置了開啓優化和不優化兩個選項後,無論順序,最終都會執行不優化的操作。
優化控制
這個是用於控制混淆是否開啓優化代碼,例如一些if/else語句可以被簡化等這些操作:
# 不優化
-dontoptimize
# 代碼循環優化次數,0-7,默認爲5
-optimizationpasses 5
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
值得注意的是默認混淆配置文件開啓了-dontoptimize
。
優化進階
開啓優化後可以設置下面的規則,assumenosideeffects表示指定的代碼無效,可以優化,最終效果表現爲不執行。
# 混淆時所採用的優化規則
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 關閉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(...);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
基本混淆規則
下面這些一般混淆規則都要加入,其中前兩個在默認文件中已經配置:
# 包名不使用大小寫混合 aA Aa
-dontusemixedcaseclassnames
# 不混淆第三方引用的庫
-dontskipnonpubliclibraryclasses
# 不做預校驗
-dontpreverify
# 忽略警告
-ignorewarning
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
輸出混淆記錄
混淆後由於閱讀困難性提高,所以爲了方便自己查閱,可以輸出mapping對應文件,可以利用AndroidSDK\tools\proguard\bin中的proguardgui.bat打開混淆工具,利用retrace結合mapping和stacktrace調試遇到的錯誤
# 混淆後生產映射文件 map 類名->轉化後類名的映射
# 存放在app\build\outputs\mapping\release中
-verbose
# 混淆前後的映射
-printmapping mapping.txt
# apk 包內所有 class 的內部結構
-dump class_files.txt
# 未混淆的類和成員
-printseeds seeds.txt
# 列出從 apk 中刪除的代碼
-printusage unused.txt
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
保留源代碼行號
即使使用retrace工具,還是很難定位到錯誤的時候,可以暫時先保留行號,觀察錯誤修改後再關閉掉
# 拋出異常時保留代碼行號
# 這個最後release的時候關閉掉
-keepattributes SourceFile,LineNumberTable
- 1
- 2
- 3
- 1
- 2
- 3
基本組件白名單
Android中的基本組件不能混淆,爲了方便,下面提供了兼容性比較高的規則:
-keep public class * extends android.app.Fragment
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Support包規則
# 如果有引用v4包可以添加下面這行
-keep public class * extends android.support.v4.app.Fragment
# 如果引用了v4或者v7包
-dontwarn android.support.**
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
不混淆本地方法
本地方法不能混淆,這個規則在默認配置文件中有:
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
WebView混淆規則
使用了WebView的js功能則開啓下面規則,這個規則在自定義規則文件中已經用註釋說明了:
# WebView使用javascript功能則需要開啓
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
註解、泛型和反射混淆
下面是混淆規則:
# 保護註解
-keepattributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}
# 泛型與反射
-keepattributes Signature
-keepattributes EnclosingMethod
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
有些註解可能不能被混淆,需要手動混淆一下
內部類混淆
# 不混淆內部類
-keepattributes InnerClasses
- 1
- 2
- 1
- 2
第三方混淆參考規則
Gson
# gson
-dontwarn com.google.**
-keep class com.google.gson.** {*;}
- 1
- 2
- 3
- 1
- 2
- 3
otto
# otto混淆規則
-keepattributes *Annotation*
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
universal-image-loader
-dontwarn com.nostra13.universalimageloader.**
-keep class com.nostra13.universalimageloader.** {*;}
- 1
- 2
- 1
- 2
友盟統計
# 友盟統計
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
# 友盟統計5.0.0以上SDK需要
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 友盟統計R.java刪除問題
-keep public class com.gdhbgh.activity.R$*{
public static final int *;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
OkHttp
# OkHttp
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** {*;}
-keep interface com.squareup.okhttp.** {*;}
-dontwarn okio.**
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
nineoldandroids
-dontwarn com.nineoldandroids.*
-keep class com.nineoldandroids.** {*;}
- 1
- 2
- 1
- 2
支付寶
# 支付寶
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{
public *;
}
-keep class com.alipay.sdk.app.AuthTask{
public *;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Socket.io
# socket.io
-keep class socket.io-client.
-keepclasseswithmembers,allowshrinking class socket.io-client.* {*;}
-keep class io.socket.
-keepclasseswithmembers,allowshrinking class io.socket.* {*;}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
JPUSH
# jpush
-dontwarn cn.jpush.**
-keep class cn.jpush.** {*;}
# protobuf(jpush依賴)
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
友盟分享
這個只有部分熱門的SDK,具體可以參考分享文檔:
# 友盟分享
-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**
-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**
-keep public class com.umeng.socialize.* {*;}
-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.umeng.socialize.handler.**
-keep class com.umeng.socialize.handler.*
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
-keep class com.tencent.** {*;}
-dontwarn com.tencent.**
-keep public class com.umeng.soexample.R$*{
public static final int *;
}
-keep class com.tencent.open.TDialog$*
-keep class com.tencent.open.TDialog$* {*;}
-keep class com.tencent.open.PKDialog
-keep class com.tencent.open.PKDialog {*;}
-keep class com.tencent.open.PKDialog$*
-keep class com.tencent.open.PKDialog$* {*;}
-keep class com.sina.** {*;}
-dontwarn com.sina.**
-keep class com.alipay.share.sdk.** {*;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
個人遇到的一些坑
網絡層混淆
混淆要注意,一般網絡層都不進行混淆,可以經過劃分包後直接不混淆網絡層的包:
-keep class com.xxx.xxx.http.** {*;}
- 1
- 1
數據模型混淆
所有bean都不要混淆,可以使用下面的:
-keep class * implements java.io.Serializable {*;}
-keepclassmembers class * implements java.io.Serializable {*;}
- 1
- 2
- 1
- 2
但是有時候上述代碼可能導致應用卡住,沒用任何錯誤提示,所以我建議採用分包模式,把所有bean放在一個包中,直接對該包加白名單:
-keep class com.xxx.xxx.domain.** {*;}
- 1
- 1
XML映射問題
如果你遇到一些控件無法Inflate,報NullPointException,比如ListView,NavigationView等等,這個問題花了我幾個小時自己研究出了規則:
-keep class org.xmlpull.v1.** {*;}
- 1
- 1
混淆規則編寫方法
如果混淆後報錯,通過retrace後找到錯誤的問題後可以直接編寫規則來去掉混淆,但是如果報的錯誤莫名其妙,而且報錯的類沒有混淆,那麼你可以採用極端的方法:
加入下面規則:
-keep class *.** {*;}
- 1
- 1
這條規則表示不混淆所有類及其中所有代碼,加了這條規則之後,
還不能運行表示是其他問題,例如註解,內部類等等,
可以運行後,可以通過反編譯,尋找所有包名,記錄下來,把上述規則改爲:
-keep class android.** {*;}
-keep class com.** {*;}
-keep class io.** {*;}
-keep class org.** {*;}
...
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
一個個去掉檢查是否有報錯,例如查到
-keep class com.** {*;}
- 1
- 1
加了就不報錯,則可以繼續一級級往下檢查。
但要注意,有時候可能是幾個包混合問題。