溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android 開發(fā)應(yīng)該掌握的 Proguard 技巧

發(fā)布時間:2020-08-05 15:58:34 來源:網(wǎng)絡(luò) 閱讀:373 作者:Android飛魚 欄目:移動開發(fā)

Proguard介紹

Proguard被人們熟知的是它的混淆功能,根據(jù)Proguard幫助文檔的描述,Proguard可以對Java class 文件進(jìn)行shrink,optimize,obfuscate和preveirfy。obfuscate(混淆)只是其中之一。簡要的介紹下這四個功能:

壓縮(Shrink): 檢測和刪除沒有使用的類,字段,方法和特性

優(yōu)化(Optimize)?: 分析和優(yōu)化Java字節(jié)碼

混淆(Obfuscate): 使用簡短的無意義的名稱,對類,字段和方法進(jìn)行重命名

預(yù)檢(Preveirfy): 用來對Java class進(jìn)行預(yù)驗證(預(yù)驗證主要是針對JME開發(fā)來說的,Android中沒有預(yù)驗證過程,默認(rèn)是關(guān)閉)

補(bǔ)充說明:根據(jù)proguard-android-optimize.txt對optimize的描述,在Android中使用該功能是有潛在風(fēng)險的,并不能保證在所有版本的Dalvik虛擬機(jī)上正常運行,該選項默認(rèn)是關(guān)閉的,如果開啟,請做好全面的測試。在Android項目中,我們在相應(yīng)module下的build.gradle文件中會看到

?buildTypes?{
?release?{
?minifyEnabled?true
?proguardFiles?getDefaultProguardFile('proguard-android.txt'),?'proguard-rules.pro'
?}
?}

其中 minifyEnabled 為true是開啟Proguard的功能,false是關(guān)閉。

Proguard工作流程

Prouguard的工作流程如下圖所示:

Android 開發(fā)應(yīng)該掌握的 Proguard 技巧

可以看出, Proguard工作流程是對輸入的jars經(jīng)過shrink->optimize->obfuscate->preveirfy依次處理,圖中l(wèi)ibrary jars是input jars運行所依賴的包,比如Java運行時的rt.jar,Android運行時android.jar,這些jars在上述處理過程中不會有任何改變,僅是作為輸入jars的依賴。

大家可能會有一個疑問,Proguard是怎么知道哪些類,方法,成員變量等是無用的呢,這就要說到Entry Point(入口點),我們在配置文件(包括默認(rèn)的proguard-android.txt)中寫入的一系列-keep選項,都會作為Entry Point,Proguard把這些Entry Points作為搜索的入口,進(jìn)行遞歸檢索,以此來確定哪些部分未使用到。類似于hotspot虛擬機(jī)對可回收對象的判定,從GC Roots出發(fā),進(jìn)行可達(dá)性的判斷,不可達(dá)的為可回收對象。Entry Points非常重要,Proguard的壓縮,優(yōu)化,混淆功能是以Entry Point作為依據(jù)的(預(yù)檢不需要以此為依據(jù))。

在壓縮過程中,Proguard從Entry Points出發(fā),遞歸檢索,刪除那些沒有使用到的類和類的成員,在接下來的優(yōu)化過程中,那些非Entry Points的類和方法會被設(shè)置成private,static或final,沒有使用到的參數(shù)會被移除,有些方法可能會被標(biāo)記為內(nèi)聯(lián)的,在混淆過程中,會對非EntryPoint的類和類的成員進(jìn)行重命名,也就是用其它無意義的名稱代替。我們在配置文件中用-keep保留的部分屬于Entry Point,所以不會被重命名。

Proguard配置文件的依據(jù)

說起重命名,為什么需要保留一些類和類的成員(方法和變量)不被重命名呢 ? 原因是Proguard對class文件經(jīng)過一系列處理后,能保證功能上和原來是一樣的,但有些情況它卻不能良好的處理,比如我們代碼中有些功能依賴于它們原來的名字,如反射功能,native調(diào)用(函數(shù)簽名)等,如果換成其它名字,會出現(xiàn)找不到,不對應(yīng)的情況,可能引起程序崩潰,或者我們的對外提供了一些功能,必須保持原來的名字,才能保證其它依賴這些功能的模塊能正確的運行等。

這就是我們?yōu)槭裁匆渲?keep選項的原因之一,還有一個原因是我們要用-keep告訴Proguard程序的入口(帶有-keep的選項都會作為Entry Point),以此來確定哪些是未被使用的類和類的成員,方法等,并刪除它們,因此,我們要針對我們的項目配置對應(yīng)的選項。當(dāng)然Proguard不僅提供了-keep選項,還有一些其它配置選項,比如-dontoptimize 對輸入的Java class 文件不進(jìn)行優(yōu)化處理,-verbose 生成混淆后的映射文件等。下面介紹一下app中proguard文件的常用配置和項目中可能會用到的指令。更多詳細(xì)的用法,可以參考Proguard幫助文檔。

編寫Proguard配置文件

第1條是可以作為Android App的配置模板的(默認(rèn)的proguard-android.txt文件里的配置沒有列舉出來),基本所有的app都會用到。

通用配置

#代碼混淆壓縮比,在0~7之間,默認(rèn)為5,一般不做修改?-optimizationpasses?5#把混淆類中的方法名也混淆了-useuniqueclassmembernames#優(yōu)化時允許訪問并修改有修飾符的類和類的成員?-allowaccessmodification#?避免混淆內(nèi)部類、泛型、匿名類-keepattributes?InnerClasses,Signature,EnclosingMethod#拋出異常時保留代碼行號?-keepattributes?SourceFile,LineNumberTable#重命名拋出異常時的文件名稱為"SourceFile"-renamesourcefileattribute?SourceFile#保持所有實現(xiàn)?Serializable?接口的類成員-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();
}#保留我們使用的四大組件,自定義的Application等等這些類不被混淆?#因為這些子類都有可能被外部調(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?#保留support下的所有類及其內(nèi)部類
-keep?class?android.support.**?{*;}#?保留繼承的support類-keep?public?class?*?extends?android.support.v4.**
-keep?public?class?*?extends?android.support.v7.**
-keep?public?class?*?extends?android.support.annotation.**
#保留我們自定義控件(繼承自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);
}#Fragment不需要在AndroidManifest.xml中注冊,需要額外保護(hù)下-keep?public?class?*?extends?android.app.Fragment#?保持測試相關(guān)的代碼
-dontnote?junit.framework.**
-dontnote?junit.runner.**
-dontwarn?android.test.**
-dontwarn?android.support.test.**
-dontwarn?org.junit.**

下面是針對我們App的配置。

1. 實體類需要保留

我們需要保留實體類的get和set方法(反射會用到),boolean類型的get方法是isXXX,不要忘記保留。

?-keep?public?class?com.dev.example.entity.**?{
?public?void?set*(***);?public?***?get*();?public?***?is*();
?}

如果所有的實體類在一個包下的話,上面的配置只用寫一遍就可以了。可是實際中我們更多的是以業(yè)務(wù)來劃分包名的,于是我們還可以這樣配置(實體類的類名一定要含有"Model")

<pre >

-keep?public?class?**.*Model*.**?{
?public?void?set*(***);?public?***?get*();?public?***?is*();
}

2. 對內(nèi)部類的處理

如果項目中使用了內(nèi)部類,要對其進(jìn)行保留。

  1. 保留寫在某個類里面的所有內(nèi)部類。下面表示寫在類A里面的內(nèi)部類都會被保留($符號是用來分割內(nèi)部類與其母體的標(biāo)志),什么意思呢,比如類A里面有一個內(nèi)部類B,而B里面也有個內(nèi)部類C,這時,B和C都會被保留,以此類推,對多重嵌套的情況,都會被保留(當(dāng)然我們寫代碼也不會寫出這么深層級的內(nèi)部類出來),這里的內(nèi)部類包含靜態(tài)內(nèi)部類,非靜態(tài)內(nèi)部類,不包含匿名內(nèi)部類,如果是匿名內(nèi)部類,只會保留其方法和成員變量(其繼承的類或?qū)崿F(xiàn)的接口的名字會被混淆),另外如果對應(yīng)的類被保留,在該類里面定義的接口也會被保留,{*;}匹配該類里面的所有部分。

-keep?class?com.dev.example.A$*?{?*;?}
  1. 保留寫在某個內(nèi)部類里面所有的內(nèi)部類。這話聽著有點繞口,舉個例子,類A里面有個內(nèi)部類B,下面表示寫在類B里面的內(nèi)部類都會被保留。此時,類B像上面第一點所舉得類A一樣,有點遞歸意思在里面。還有就是此時類B的名字不會被混淆,但里面的方法和成員變量會被混淆,如果其它地方?jīng)]有對類B的方法和成員變量進(jìn)行保留的話。

-keep?class?com.dev.example.A$B$*?{?*;?}

3. 對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);
}

4. 保留js調(diào)用的原生方法

如果我們的app中涉及到和h6交互,需要保留js調(diào)用的原生方法。

#?Keep?JavascriptInterface-keepclassmembers?class?**?{
?@android.webkit.JavascriptInterface?public?*;
}

5. 對含有反射類的處理

有時候項目中有些類不是實體類,但仍然用到反射功能,如Class.forName("xxx"),這是我們需要保留的。比如這些類在com.dev.example包下,可以通過下面的配置進(jìn)行保留。

-keep?class?com.dev.example.*?{?*;?}

另外上面只是保留了該包下的類,如果該包下還有子包,則子包的類仍然會被混淆,

如果想保留該包下子包的類,我們可以如下配置(**能匹配本包和所含子包,其中子包也可以含有子包)

-keep?class?com.dev.example.**{?*;?}

6. 常見的自定義的配置

1.保留某個特定的類

#保留Test類-keep?public?class?com.dev.example.Test?{?*;?}

2.保留某個類的子類

#保留繼承了AbstractClass的子類-keep?class?*?extends?com.dev.example.AbstractClass{*;}

3.保留接口的實現(xiàn)類

#保留實現(xiàn)了Callable接口的類-keep?class?*?implements?Callable{*;}

4.保留類的特定部分

保留TaskRepository類的所有構(gòu)造方法,變量和普通方法。

-keep?class?com.dev.example.TaskRepository{
?<init>;?//匹配所有構(gòu)造器
?<fields>;?//匹配所有域
?<methods>;?//匹配所有方法}

還可以保留的更具體一點,如下所示

-keepclassmembers?com.dev.example.TaskRepository{?//?保留該類的修飾符是public且有一個參數(shù)(類型是String)的構(gòu)造方法
?public?<init>(java.lang.String);?//?保留該類的所有修飾符是public且返回類型void的方法
?public?void?*(**);?
?//?保留該類的具體某一個方法?
?public?String?getUserName();?
}

7. 對于第三方依賴庫的處理

下面給出幾個例子,用到具體第三發(fā)依賴庫的時候,對應(yīng)的文檔會給出相應(yīng)配置的。

#okhttp-dontwarn?com.squareup.okhttp.**
-dontwarn?com.squareup.okhttp3.**
-keep?class?com.squareup.okhttp3.**?{?*;}
-dontwarn?okio.**#retroift-dontwarn?retrofit2.**
-keep?class?retrofit2.**?{?*;?}
-keepattributes?Signature
-keepattributes?Exceptions#?fresco?SDK-keep,allowobfuscation?@interface?com.facebook.common.internal.DoNotStrip#?Do?not?strip?any?method/class?that?is?annotated?with?@DoNotStrip-keep?@com.facebook.common.internal.DoNotStrip?class?*-keepclassmembers?class?*?{
?@com.facebook.common.internal.DoNotStrip?*;
}#rx-dontwarn?rx.**
-keep?class?rx.**?{?*;}#keep?GSON?stuff-keep?class?sun.misc.Unsafe?{?*;?}
-keep?class?com.google.gson.**?{?*;?}#ButterKnife-keep?class?butterknife.**?{?*;?}
-dontwarn?butterknife.internal.**
-keep?class?**$ViewBinder?{?*;?}
-keepclasseswithmembernames?class?*?{
?@butterknife.*?<fields>;
}
-keepclasseswithmembernames?class?*?{
?@butterknife.*?<methods>;
}#enventbus-keep?class?org.greenrobot.eventbus.**?{?*;}
-dontwarn?org.greenrobot.eventbus.**
-keepclassmembers?class?**?{另外說一下
?public?void?onEvent*(**);
}#?Bugly-dontwarn?com.tencent.bugly.**
-keep?public?class?com.tencent.bugly.**{*;}#?aliyun?push-keepclasseswithmembernames?class?**?{
?native?<methods>;
}#?QQ?share?SDK-dontwarn?com.tencent.**
-keepnames?class?com.tencent.**?{*;}#?sina?share?SDK-dontwarn?com.sina.**
-keepnames?class?com.sina.**?{*;}#?umeng?SDK-keep?public?class?*?extends?com.umeng.**-dontwarn?com.umeng.**
-keep?class?com.umeng.**?{?*;?}

其它

還有關(guān)于多module項目的配置,一種方法是關(guān)閉子module的Proguard功能,在我們主app的proguard-rules.pro文件中配置所有module的配置選項。這樣會使主app的proguard配置文件變得比較雜亂,如果業(yè)務(wù)發(fā)展過程中,某個子module的功能不需要了,還要在主app的配置文件中找到對應(yīng)子module的配置,并刪除它們,不建議使用。另一種方式是各個module配置好自己的配置文件,要注意的是,子module中制定配置文件的方式如下所示:

buildTypes?{
?release?{
?consumerProguardFiles?'proguard-rules.pro'
?}
?}

子module是通過consumerProguardFiles來指定配置文件的,而不是proguardFiles。

在導(dǎo)出包時,如果發(fā)現(xiàn)有很多could not reference class之類的warning信息,確認(rèn)app在運行時和這些warning沒有任何關(guān)系,可以配置-dontwarn選項,就不會提示這些warning信息了。

到這里Proguard配置部分基本已經(jīng)說完了。Proguard是對class字節(jié)碼文件進(jìn)行操作的,有時我們還想對資源文件進(jìn)行混淆,比較成熟的是微信的資源混淆文件方案,由于本次討論的重點不是這個,不再多說。

檢查混淆和追蹤異常

開啟Proguard功能,則每次構(gòu)建時 ProGuard 都會輸出下列文件:

  • dump.txt

  • 說明 APK 中所有類文件的內(nèi)部結(jié)構(gòu)。

  • mapping.txt

  • 提供原始與混淆過的類、方法和字段名稱之間的轉(zhuǎn)換。

  • seeds.txt

  • 列出未進(jìn)行混淆的類和成員。

  • usage.txt

  • 列出從 APK 移除的代碼。

這些文件保存在/build/outputs/mapping/release/ 中。我們可以查看seeds.txt里面是否是我們需要保留的,以及usage.txt里查看是否有誤刪除的代碼。mapping.txt文件很重要,由于我們的部分代碼是經(jīng)過重命名的,如果該部分出現(xiàn)bug,對應(yīng)的異常堆棧信息里的類或成員也是經(jīng)過重命名的,難以定位問題。我們可以用 retrace 腳本(在 Windows 上為 retrace.bat;在 Mac/Linux 上為 retrace.sh)。它位于/tools/proguard/ 目錄中。該腳本利用 mapping.txt文件和你的異常堆棧文件生成沒有經(jīng)過混淆的異常堆棧文件,這樣就可以看清是哪里出問題了。使用 retrace 工具的語法如下:

retrace.bat|retrace.sh?[-verbose]?mapping.txt?[<stacktrace_file>]

例如:

retrace.bat?-verbose?mapping.txt?obfuscated_trace.txt

這篇文章參考了Proguard相關(guān)文檔和幾篇寫的好的博客,旨在介紹在Android中Proguard的使用,以及解釋大家在理解Proguard中可能會遇到的一些點,希望能有所幫助。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI