ProGuard簡(jiǎn)介
在Android中一提起ProGuard磁携,我們就會(huì)認(rèn)為他是用來(lái)混淆代碼的业舍,殊不知ProGuard一共包括以下4個(gè)功能撤缴。
- 壓縮(Shrink):偵測(cè)并移除代碼中無(wú)用的類劫扒、字段捐顷、方法荡陷、和特性(Attribute)。
- 優(yōu)化(OPtimize):對(duì)字節(jié)碼進(jìn)行優(yōu)化迅涮,移除無(wú)用指令亲善。
- 混淆(Obfuscate):使用a、b逗柴、c蛹头、d這樣簡(jiǎn)短而無(wú)意義的名稱,對(duì)類、字段和方法進(jìn)行重命名渣蜗。
- 預(yù)檢(Preveirfy): 在java平臺(tái)上對(duì)處理后的代碼進(jìn)行預(yù)檢屠尊。
提示:如果僅僅是為了代碼混淆,ProGuard有一個(gè)兄弟產(chǎn)品DexGuard可以試試耕拷,地址點(diǎn)擊這里讼昆。
ProGuard是一個(gè)開(kāi)源項(xiàng)目在SourceForge上進(jìn)行維護(hù),地址點(diǎn)擊這里骚烧。
從上述地址下載ProGuard之后浸赫,能同時(shí)看到官方文檔和示例,不過(guò)是英文的赃绊,目前市面上沒(méi)有相應(yīng)的中文翻譯版既峡,也沒(méi)有一片詳盡的介紹文章。
如果你的項(xiàng)目已經(jīng)使用了摸個(gè)版本的ProGuard碧查,比如运敢,現(xiàn)在市面上最流行的是4.7版本,我建議不要進(jìn)行升級(jí)忠售。一切以穩(wěn)定為首传惠,如果一定要升級(jí)到最新版本,請(qǐng)?jiān)谑褂肞roGuard后稻扬,對(duì)項(xiàng)目的所有模塊進(jìn)行全功能的回歸測(cè)試卦方。
ProGuard工作原理
ProGuard由shrink、optimize泰佳、obfuscate和preverify四個(gè)步驟組成盼砍,每個(gè)步驟都是可選的,需要那些步驟都可以在腳本中配置乐纸。流程圖如下:
Input jars衬廷、Library jars
-shrink->Shrunk code
-optimize->Optim.code
-obfuscate->Obfusc.code
-preverify->Output >jars摇予、Library jars
這里我們引入Entry Point的概念汽绢。Entry Point實(shí)在ProGuard過(guò)程中不會(huì)被處理的類或方法。再壓縮的步驟中侧戴,ProGuard或從上述的EntryPoint開(kāi)始遞歸遍歷宁昭,搜索那些類和類成員在使用。對(duì)于沒(méi)有被使用的類和類的成員酗宋,就會(huì)在壓縮階段丟棄积仗。
接下來(lái)優(yōu)化的步驟中,那些非EntryPoint的類蜕猫、方法都會(huì)被設(shè)置為private寂曹、static或final,不使用的參數(shù)會(huì)被移除,此外隆圆,有些方法會(huì)被標(biāo)記為內(nèi)聯(lián)的漱挚。在混淆的步驟中,ProGuard會(huì)對(duì)非EntryPoint的類和方法進(jìn)行重命名渺氧。
如何寫一個(gè)ProGuard文件
下面介紹ProGuard.cfg混淆文件怎么寫旨涝。這是一個(gè)三步走的過(guò)程。
基本混淆
以下是混淆最基本的配置信息侣背,在任何App都要使用白华,可以作為模板使用,我為每行代碼都增加了注釋:
1.基本指令
# 代碼混淆壓縮比贩耐,在0~7之間弧腥,默認(rèn)為5,一般不下需要修改
-optimizationpasses 5
# 混淆時(shí)不使用大小寫混合,混淆后的類名為小寫
# windows下的同學(xué)還是加入這個(gè)選項(xiàng)吧(windows大小寫不敏感)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的庫(kù)的類
# 默認(rèn)跳過(guò)憔杨,有些情況下編寫的代碼與類庫(kù)中的類在同一個(gè)包下鸟赫,并且持有包中內(nèi)容的引用,此時(shí)就需要加入此條聲明
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共的庫(kù)的類的成員
-dontskipnonpubliclibraryclassmembers
# 不做預(yù)檢驗(yàn)消别,preverify是proguard的四個(gè)步驟之一
# Android不需要preverify抛蚤,去掉這一步可以加快混淆速度
-dontpreverify
# 有了verbose這句話,混淆后就會(huì)生成映射文件
# 包含有類名->混淆后類名的映射關(guān)系
# 然后使用printmapping指定映射文件的名稱
-verbose
-printmapping priguardMapping.txt
# 指定混淆時(shí)采用的算法寻狂,后面的參數(shù)是一個(gè)過(guò)濾器
# 這個(gè)過(guò)濾器是谷歌推薦的算法岁经,一般不改變
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保護(hù)代碼中的Annotation不被混淆
# 這在JSON實(shí)體映射時(shí)非常重要,比如fastJson
-keepattributes *Annotation*
# 避免混淆泛型
# 這在JSON實(shí)體映射時(shí)非常重要蛇券,比如fastJson
-keepattributes Signature
# 拋出異常時(shí)保留代碼行號(hào)
-keepattributes SourceFile,LineNumberTable
2.需要保留的東西
# 保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留了繼承自Activity缀壤、Application這些類的子類
# 因?yàn)檫@些子類有可能被外部調(diào)用
# 比如第一行就保證了所有Activity的子類不要被混淆
-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
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 如果有引用android-support-v4.jar包,可以添加下面這行
-keep public class com.null.test.ui.fragment.** {*;}
# 保留Activity中的方法參數(shù)是view的方法纠亚,
# 從而我們?cè)趌ayout里面編寫onClick就不會(huì)影響
-keepclassmembers class * extends android.app.Activity {
public void * (android.view.View);
}
# 枚舉類不能被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留自定義控件(繼承自View)不能被混淆
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(***);
*** get* ();
}
# 保留Parcelable序列化的類不能被混淆
-keep class * implements android.os.Parcelable{
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable 序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 對(duì)R文件下的所有類及其方法塘慕,都不能被混淆
-keepclassmembers class **.R$* {
*;
}
# 對(duì)于帶有回調(diào)函數(shù)onXXEvent的蒂胞,不能混淆
-keepclassmembers class * {
void *(**On*Event);
}
針對(duì)App的量身定制
1.保留實(shí)體類和成員不被混淆
對(duì)于實(shí)體图呢,要保留它們的get和set方法,對(duì)于boolean型get方法有的命名是isXXX類型骗随,不要遺漏
-keep class com.null.test.entities.** {
//全部忽略
*;
}
-keep class com.null.test.entities.** {
//忽略get和set方法
public void set*(***);
public *** get*();
public *** is*();
}
//以上兩種任意一種都行
一個(gè)項(xiàng)目中最好把所有的實(shí)體都放到同一個(gè)包下蛤织,這樣針對(duì)包混淆就行了,避免了實(shí)體混淆遺漏而造成的崩潰
2.內(nèi)嵌類
內(nèi)嵌類經(jīng)常容易被混淆鸿染,結(jié)果調(diào)用的時(shí)候?yàn)榭站捅罎⒘酥秆痢W詈玫霓k法就是不用內(nèi)嵌類(有點(diǎn)扯淡),如果MainActivity中使用了涨椒,就用如下代碼
-keep class com.null.test.MainActivity$* {
*;
}
$這個(gè)符號(hào)就是用來(lái)分割內(nèi)嵌類與其母體的標(biāo)志
3.對(duì)WebView的處理
如果項(xiàng)目中用到了WebView的復(fù)雜操作摊鸡,請(qǐng)加入以下代碼:
-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, java.lang.String);
}
4.對(duì)JavaScript的處理
-keepclassmembers class com.null.test.MainActivity$JSInterfacel {
<methods>;
}
針對(duì)第三方j(luò)ar包的解決方案
一般來(lái)說(shuō)第三方的SDK都是經(jīng)過(guò)ProGuard混淆了的绽媒。我們要做的就是避免其再次在我們的App混淆。
1.針對(duì)andoid-support-v4.jar的解決方案
-libraryjars ./libs/android-support-v4.jar
-dontwarn android.support.v4.**
-dontwarn **CompatHoneycomb
-dontwarn **CompatHoneycombMR2
-dontwarn **CompatCreatorHoneycombMR2
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
這里注意一個(gè)問(wèn)題就是免猾,很有可能在我們引用的其他包里面也會(huì)依賴v4包些椒,因兩個(gè)(或者以上)v4的版本是不一樣的,在運(yùn)行期間拋出NoClassDefFoundError異常掸刊。相應(yīng)的解決辦法就是都依賴同一個(gè)v4包就行了免糕。
2.其他第三方的jar包的解決方案
這個(gè)要取決第三方j(luò)ar包的混淆策略了。一般在其官方文檔上面都有混淆說(shuō)明忧侧。比如支付寶相應(yīng)的混淆規(guī)則就是:
-libraryjars ./libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.** {*;}
其他注意事項(xiàng)
1.確笔ぃ混淆不會(huì)對(duì)項(xiàng)目造成影響
- 測(cè)試要基于混淆包進(jìn)行
- 冒煙測(cè)試也要基于混淆包進(jìn)行
- 發(fā)版本前,要額外測(cè)試正式版的推送蚓炬、分享松逊、打點(diǎn)、二維碼掃描等功能
2.打包時(shí)忽略警告
當(dāng)在導(dǎo)出時(shí)肯夏,發(fā)現(xiàn)很多could not reference class之類的warning信息经宏,如果確認(rèn)app運(yùn)行中和那些引用沒(méi)有什么關(guān)系的話,就可以添加-dontwarn
標(biāo)簽驯击,就不會(huì)在提示這些warning信息了烁兰。如-dontwarn org.apache.**。
3.對(duì)于自定義類庫(kù)的混淆處理
對(duì)于自定義的類庫(kù)徊都,我們一般是保留類和類的成員沪斟。
4.使用annotation避免混淆
@Keep
@KeepPublicGettersSetters
public class Bean {
public boolean booleanProperty;
public int intProperty;
public String stringProperty;
public boolean isBooleanProperty() {
return booleanProperty;
}
}
//其中@KeepPublicGettersSetters我沒(méi)找到,大家慎用
5.在項(xiàng)目中制定混淆文件
eclipse是在project.properties文件最后加上proguard.config = proguard.cfg
android studio 是在build.gradle修改buildTypes如下:
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}