這篇文章對(duì)Android中的Proguard工具進(jìn)行詳細(xì)的介紹,內(nèi)容較為全面,其中包括Proguard的簡(jiǎn)介、工作流程以及文件的配置。通過(guò)這篇文章希望你能有所收獲。
在阿瓦提等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、成都外貿(mào)網(wǎng)站建設(shè)公司 網(wǎng)站設(shè)計(jì)制作按需網(wǎng)站開(kāi)發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),成都營(yíng)銷網(wǎng)站建設(shè),外貿(mào)營(yíng)銷網(wǎng)站建設(shè),阿瓦提網(wǎng)站建設(shè)費(fèi)用合理。Proguard被人們熟知的是它的混淆功能,根據(jù)Proguard幫助文檔的描述,Proguard可以對(duì)Java class 文件進(jìn)行shrink,optimize,obfuscate和preveirfy。obfuscate(混淆)只是其中之一。簡(jiǎn)要的介紹下這四個(gè)功能:
壓縮(Shrink): 檢測(cè)和刪除沒(méi)有使用的類,字段,方法和特性
優(yōu)化(Optimize) : 分析和優(yōu)化Java字節(jié)碼
混淆(Obfuscate): 使用簡(jiǎn)短的無(wú)意義的名稱,對(duì)類,字段和方法進(jìn)行重命名
預(yù)檢(Preveirfy): 用來(lái)對(duì)Java class進(jìn)行預(yù)驗(yàn)證(預(yù)驗(yàn)證主要是針對(duì)JME開(kāi)發(fā)來(lái)說(shuō)的,Android中沒(méi)有預(yù)驗(yàn)證過(guò)程,默認(rèn)是關(guān)閉)
補(bǔ)充說(shuō)明:根據(jù)proguard-android-optimize.txt對(duì)optimize的描述,在Android中使用該功能是有潛在風(fēng)險(xiǎn)的,并不能保證在所有版本的Dalvik虛擬機(jī)上正常運(yùn)行,該選項(xiàng)默認(rèn)是關(guān)閉的,如果開(kāi)啟,請(qǐng)做好全面的測(cè)試。在Android項(xiàng)目中,我們?cè)谙鄳?yīng)module下的build.gradle文件中會(huì)看到
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
其中 minifyEnabled 為true是開(kāi)啟Proguard的功能,false是關(guān)閉。
Prouguard的工作流程如下圖所示:
可以看出, Proguard工作流程是對(duì)輸入的jars經(jīng)過(guò)shrink->optimize->obfuscate->preveirfy依次處理,圖中l(wèi)ibrary jars是input jars運(yùn)行所依賴的包,比如Java運(yùn)行時(shí)的rt.jar,Android運(yùn)行時(shí)android.jar,這些jars在上述處理過(guò)程中不會(huì)有任何改變,僅是作為輸入jars的依賴。
大家可能會(huì)有一個(gè)疑問(wèn),Proguard是怎么知道哪些類,方法,成員變量等是無(wú)用的呢,這就要說(shuō)到Entry Point(入口點(diǎn)),我們?cè)谂渲梦募?包括默認(rèn)的proguard-android.txt)中寫(xiě)入的一系列-keep選項(xiàng),都會(huì)作為Entry Point,Proguard把這些Entry Points作為搜索的入口,進(jìn)行遞歸檢索,以此來(lái)確定哪些部分未使用到。類似于hotspot虛擬機(jī)對(duì)可回收對(duì)象的判定,從GC Roots出發(fā),進(jìn)行可達(dá)性的判斷,不可達(dá)的為可回收對(duì)象。Entry Points非常重要,Proguard的壓縮,優(yōu)化,混淆功能是以Entry Point作為依據(jù)的(預(yù)檢不需要以此為依據(jù))。
在壓縮過(guò)程中,Proguard從Entry Points出發(fā),遞歸檢索,刪除那些沒(méi)有使用到的類和類的成員,在接下來(lái)的優(yōu)化過(guò)程中,那些非Entry Points的類和方法會(huì)被設(shè)置成private,static或final,沒(méi)有使用到的參數(shù)會(huì)被移除,有些方法可能會(huì)被標(biāo)記為內(nèi)聯(lián)的,在混淆過(guò)程中,會(huì)對(duì)非EntryPoint的類和類的成員進(jìn)行重命名,也就是用其它無(wú)意義的名稱代替。我們?cè)谂渲梦募杏?keep保留的部分屬于Entry Point,所以不會(huì)被重命名。
說(shuō)起重命名,為什么需要保留一些類和類的成員(方法和變量)不被重命名呢 ? 原因是Proguard對(duì)class文件經(jīng)過(guò)一系列處理后,能保證功能上和原來(lái)是一樣的,但有些情況它卻不能良好的處理,比如我們代碼中有些功能依賴于它們?cè)瓉?lái)的名字,如反射功能,native調(diào)用(函數(shù)簽名)等,如果換成其它名字,會(huì)出現(xiàn)找不到,不對(duì)應(yīng)的情況,可能引起程序崩潰,或者我們的對(duì)外提供了一些功能,必須保持原來(lái)的名字,才能保證其它依賴這些功能的模塊能正確的運(yùn)行等。
這就是我們?yōu)槭裁匆渲?keep選項(xiàng)的原因之一,還有一個(gè)原因是我們要用-keep告訴Proguard程序的入口(帶有-keep的選項(xiàng)都會(huì)作為Entry Point),以此來(lái)確定哪些是未被使用的類和類的成員,方法等,并刪除它們,因此,我們要針對(duì)我們的項(xiàng)目配置對(duì)應(yīng)的選項(xiàng)。當(dāng)然Proguard不僅提供了-keep選項(xiàng),還有一些其它配置選項(xiàng),比如-dontoptimize 對(duì)輸入的Java class 文件不進(jìn)行優(yōu)化處理,-verbose 生成混淆后的映射文件等。下面介紹一下app中proguard文件的常用配置和項(xiàng)目中可能會(huì)用到的指令。更多詳細(xì)的用法,可以參考Proguard幫助文檔。
第1條是可以作為Android App的配置模板的(默認(rèn)的proguard-android.txt文件里的配置沒(méi)有列舉出來(lái)),基本所有的app都會(huì)用到。
通用配置
#代碼混淆壓縮比,在0~7之間,默認(rèn)為5,一般不做修改 -optimizationpasses 5#把混淆類中的方法名也混淆了-useuniqueclassmembernames#優(yōu)化時(shí)允許訪問(wèn)并修改有修飾符的類和類的成員 -allowaccessmodification# 避免混淆內(nèi)部類、泛型、匿名類-keepattributes InnerClasses,Signature,EnclosingMethod#拋出異常時(shí)保留代碼行號(hào) -keepattributes SourceFile,LineNumberTable#重命名拋出異常時(shí)的文件名稱為"SourceFile"-renamesourcefileattribute SourceFile#保持所有實(shí)現(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等等這些類不被混淆 #因?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 #保留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中注冊(cè),需要額外保護(hù)下-keep public class * extends android.app.Fragment# 保持測(cè)試相關(guān)的代碼 -dontnote junit.framework.** -dontnote junit.runner.** -dontwarn android.test.** -dontwarn android.support.test.** -dontwarn org.junit.**
下面是針對(duì)我們App的配置。
1. 實(shí)體類需要保留
我們需要保留實(shí)體類的get和set方法(反射會(huì)用到),boolean類型的get方法是isXXX,不要忘記保留。
-keep public class com.dev.example.entity.** { public void set*(***); public *** get*(); public *** is*(); }
如果所有的實(shí)體類在一個(gè)包下的話,上面的配置只用寫(xiě)一遍就可以了??墒菍?shí)際中我們更多的是以業(yè)務(wù)來(lái)劃分包名的,于是我們還可以這樣配置(實(shí)體類的類名一定要含有"Model")
<pre >
-keep public class **.*Model*.** { public void set*(***); public *** get*(); public *** is*(); }
2. 對(duì)內(nèi)部類的處理
如果項(xiàng)目中使用了內(nèi)部類,要對(duì)其進(jìn)行保留。
保留寫(xiě)在某個(gè)類里面的所有內(nèi)部類。下面表示寫(xiě)在類A里面的內(nèi)部類都會(huì)被保留($符號(hào)是用來(lái)分割內(nèi)部類與其母體的標(biāo)志),什么意思呢,比如類A里面有一個(gè)內(nèi)部類B,而B(niǎo)里面也有個(gè)內(nèi)部類C,這時(shí),B和C都會(huì)被保留,以此類推,對(duì)多重嵌套的情況,都會(huì)被保留(當(dāng)然我們寫(xiě)代碼也不會(huì)寫(xiě)出這么深層級(jí)的內(nèi)部類出來(lái)),這里的內(nèi)部類包含靜態(tài)內(nèi)部類,非靜態(tài)內(nèi)部類,不包含匿名內(nèi)部類,如果是匿名內(nèi)部類,只會(huì)保留其方法和成員變量(其繼承的類或?qū)崿F(xiàn)的接口的名字會(huì)被混淆),另外如果對(duì)應(yīng)的類被保留,在該類里面定義的接口也會(huì)被保留,{*;}匹配該類里面的所有部分。
-keep class com.dev.example.A$* { *; }
保留寫(xiě)在某個(gè)內(nèi)部類里面所有的內(nèi)部類。這話聽(tīng)著有點(diǎn)繞口,舉個(gè)例子,類A里面有個(gè)內(nèi)部類B,下面表示寫(xiě)在類B里面的內(nèi)部類都會(huì)被保留。此時(shí),類B像上面第一點(diǎn)所舉得類A一樣,有點(diǎn)遞歸意思在里面。還有就是此時(shí)類B的名字不會(huì)被混淆,但里面的方法和成員變量會(huì)被混淆,如果其它地方?jīng)]有對(duì)類B的方法和成員變量進(jìn)行保留的話。
-keep class com.dev.example.A$B$* { *; }
3. 對(duì)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. 對(duì)含有反射類的處理
有時(shí)候項(xiàng)目中有些類不是實(shí)體類,但仍然用到反射功能,如Class.forName("xxx"),這是我們需要保留的。比如這些類在com.dev.example包下,可以通過(guò)下面的配置進(jìn)行保留。
-keep class com.dev.example.* { *; }
另外上面只是保留了該包下的類,如果該包下還有子包,則子包的類仍然會(huì)被混淆,
如果想保留該包下子包的類,我們可以如下配置(**能匹配本包和所含子包,其中子包也可以含有子包)
-keep class com.dev.example.**{ *; }
6. 常見(jiàn)的自定義的配置
1.保留某個(gè)特定的類
#保留Test類-keep public class com.dev.example.Test { *; }
2.保留某個(gè)類的子類
#保留繼承了AbstractClass的子類-keep class * extends com.dev.example.AbstractClass{*;}
3.保留接口的實(shí)現(xiàn)類
#保留實(shí)現(xiàn)了Callable接口的類-keep class * implements Callable{*;}
4.保留類的特定部分
保留TaskRepository類的所有構(gòu)造方法,變量和普通方法。
-keep class com.dev.example.TaskRepository{ <init>; //匹配所有構(gòu)造器 <fields>; //匹配所有域 <methods>; //匹配所有方法}
還可以保留的更具體一點(diǎn),如下所示
-keepclassmembers com.dev.example.TaskRepository{ // 保留該類的修飾符是public且有一個(gè)參數(shù)(類型是String)的構(gòu)造方法 public <init>(java.lang.String); // 保留該類的所有修飾符是public且返回類型void的方法 public void *(**); // 保留該類的具體某一個(gè)方法 public String getUserName(); }
7. 對(duì)于第三方依賴庫(kù)的處理
下面給出幾個(gè)例子,用到具體第三發(fā)依賴庫(kù)的時(shí)候,對(duì)應(yīng)的文檔會(huì)給出相應(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 ** {另外說(shuō)一下 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項(xiàng)目的配置,一種方法是關(guān)閉子module的Proguard功能,在我們主app的proguard-rules.pro文件中配置所有module的配置選項(xiàng)。這樣會(huì)使主app的proguard配置文件變得比較雜亂,如果業(yè)務(wù)發(fā)展過(guò)程中,某個(gè)子module的功能不需要了,還要在主app的配置文件中找到對(duì)應(yīng)子module的配置,并刪除它們,不建議使用。另一種方式是各個(gè)module配置好自己的配置文件,要注意的是,子module中制定配置文件的方式如下所示:
buildTypes { release { consumerProguardFiles 'proguard-rules.pro' } }
子module是通過(guò)consumerProguardFiles來(lái)指定配置文件的,而不是proguardFiles。
在導(dǎo)出包時(shí),如果發(fā)現(xiàn)有很多could not reference class之類的warning信息,確認(rèn)app在運(yùn)行時(shí)和這些warning沒(méi)有任何關(guān)系,可以配置-dontwarn選項(xiàng),就不會(huì)提示這些warning信息了。
到這里Proguard配置部分基本已經(jīng)說(shuō)完了。Proguard是對(duì)class字節(jié)碼文件進(jìn)行操作的,有時(shí)我們還想對(duì)資源文件進(jìn)行混淆,比較成熟的是微信的資源混淆文件方案,由于本次討論的重點(diǎn)不是這個(gè),不再多說(shuō)。
開(kāi)啟Proguard功能,則每次構(gòu)建時(shí) ProGuard 都會(huì)輸出下列文件:
dump.txt
說(shuō)明 APK 中所有類文件的內(nèi)部結(jié)構(gòu)。
mapping.txt
提供原始與混淆過(guò)的類、方法和字段名稱之間的轉(zhuǎn)換。
seeds.txt
列出未進(jìn)行混淆的類和成員。
usage.txt
列出從 APK 移除的代碼。
這些文件保存在/build/outputs/mapping/release/ 中。我們可以查看seeds.txt里面是否是我們需要保留的,以及usage.txt里查看是否有誤刪除的代碼。mapping.txt文件很重要,由于我們的部分代碼是經(jīng)過(guò)重命名的,如果該部分出現(xiàn)bug,對(duì)應(yīng)的異常堆棧信息里的類或成員也是經(jīng)過(guò)重命名的,難以定位問(wèn)題。我們可以用 retrace 腳本(在 Windows 上為 retrace.bat;在 Mac/Linux 上為 retrace.sh)。它位于/tools/proguard/ 目錄中。該腳本利用 mapping.txt文件和你的異常堆棧文件生成沒(méi)有經(jīng)過(guò)混淆的異常堆棧文件,這樣就可以看清是哪里出問(wèn)題了。使用 retrace 工具的語(yǔ)法如下:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
關(guān)于Android中Proguard工具的介紹就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果喜歡這篇文章,不如把它分享出去讓更多的人看到。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
網(wǎng)頁(yè)題目:Android中Proguard工具介紹-創(chuàng)新互聯(lián)
瀏覽路徑:http://jinyejixie.com/article42/dphphc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、Google、ChatGPT、App開(kāi)發(fā)、品牌網(wǎng)站設(shè)計(jì)、小程序開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容