網(wǎng)站建設、成都網(wǎng)站設計的開發(fā),更需要了解用戶,從用戶角度來建設網(wǎng)站,獲得較好的用戶體驗。成都創(chuàng)新互聯(lián)公司多年互聯(lián)網(wǎng)經(jīng)驗,見的多,溝通容易、能幫助客戶提出的運營建議。作為成都一家網(wǎng)絡公司,打造的就是網(wǎng)站建設產(chǎn)品直銷的概念。選擇成都創(chuàng)新互聯(lián)公司,不只是建站,我們把建站作為產(chǎn)品,不斷的更新、完善,讓每位來訪用戶感受到浩方產(chǎn)品的價值服務。
在 Android 日常開發(fā)過程中,混淆是我們開發(fā) App 的一項必不可少的技能。只要是我們親身經(jīng)歷過 App 打包上線的過程,或多或少都需要了解一些代碼混淆的基本操作。那么,混淆到底是什么?它的好處有哪些?具體效果如何?別急,下面我們來一一探索它的"獨特"魅力。
喜歡的朋友們麻煩點個關(guān)注,你的支持是對我最大的動力!我會定期分享Android知識點及解析,還會不斷更新的BATJ面試專題,歡迎大家前來探討交流,如有好的文章也歡迎投稿。
代碼混淆(Obfuscated code)是將程序中的代碼以某種規(guī)則轉(zhuǎn)換為難以閱讀和理解的代碼的一種行為。
混淆的好處就是它的目的:令 APK 難以被逆向工程,即很大程度上增加反編譯的成本。此外,Android 當中的"混淆"還能夠在打包時移除無用資源,顯著減少 APK 體積。最后,還能以變通方式避免 Android 中常見的 64k方法數(shù)引用的限制。
我們先來看一下混淆前后的 APK 結(jié)構(gòu)對比:
從上面兩張圖可以看出:經(jīng)過混淆處理之后,我們的 APK 中包名、類名、成員名等都被替換為隨機、無意義的名稱,增加了代碼閱讀和理解的困難程度,提高了反編譯的成本。細心的小伙伴可能又會注意到:混淆前后 APK 的體積竟然從 2.7M 減小到了 1.4M,體積縮減了近一倍!真的有這么神奇嗎?哈哈,確實是這么神奇,讓我們慢慢來揭開它的神秘面紗吧。
在 Android 中,我們平常所說的"混淆"其實有兩層意思,一個是 Java 代碼的混淆,另外一個是資源的壓縮。其實這兩者之間并沒有什么關(guān)聯(lián),只不過習慣性地放在一起來使用。那么,說了這么多,Android 平臺上到底該如何開啟混淆呢?
...... android?{ ????buildTypes?{ ????????release?{ ????????????minifyEnabled?true ????????????shrinkResources?true ????????????proguardFiles?getDefaultProguardFile('proguard-android.txt'),?'proguard-rules.pro' ????????} ????} }
以上就是開啟混淆的基本操作了,通過 minifyEnabled
設置為 true
來開啟混淆。同時,可以設置 shrinkResources
為 true
來開啟資源的壓縮。不難看出,我們一般在打 release 包時才啟用混淆,因為混淆會增加額外的編譯時間,所以不建議在 debug 模式下啟用。此外,需要注意的是:只有在啟用混淆的前提下開啟資源壓縮才會有效!以上代碼中的 proguard-android.txt
表示 Android 系統(tǒng)為我們提供的默認混淆規(guī)則文件,而 proguard-rules.pro
則是我們想要自定義的混淆規(guī)則,至于如何自定義混淆規(guī)則我們將在接下來會講到。
其實,Java 平臺為我們提供了 Proguard混淆工具來幫助我們快速地對代碼進行混淆。根據(jù) Java 官方介紹,Proguard 對應的具體中文定義如下:
它是一個包含代碼文件壓縮、優(yōu)化、混淆和校驗等功能的工具
它能夠檢測并刪除無用的類、變量、方法和屬性
它能夠優(yōu)化字節(jié)碼并刪除未使用的指令
它能夠?qū)㈩?、變量和方法的名字重命名為無意義的名稱從而達到混淆效果
最后,它還會校驗處理后的代碼,主要針對 Java 6 及以上版本和 Java ME
Android 中,編譯器為我們提供了另外一項強大的功能:資源的壓縮。資源壓縮能夠幫助我們移除項目及依賴倉庫中未使用到的資源,有效地降低了apk包的大小。由于資源壓縮與代碼混淆是協(xié)同工作,所以,如果需要開啟資源的壓縮,切記要先開啟代碼混淆,否則會出現(xiàn)以下問題:
ERROR:?Removing?unused?resources?requires?unused?code?shrinking?to?be?turned?on.?See?http://d.android.com/r/tools/shrink-resources.html?for?more?information.Affected?Modules:?app
當我們開啟了資源壓縮之后,系統(tǒng)會默認替我們移除所有未使用的資源,假如我們需要保留某些特定的資源,可以在我們項目中創(chuàng)建一個被 <resources>
標記的 XML 文件(如 res/raw/keep.xml
),并在 tools:keep
屬性中指定每個要保留的資源,在 tools:discard
屬性中指定每個要舍棄的資源。這兩個屬性都接受逗號分隔的資源名稱列表。同樣,我們可以使用字符 *
作為通配符。如:
<?xml?version="1.0"?encoding="utf-8"?><resources?xmlns:tools="http://schemas.android.com/tools" ????tools:keep="@layout/activity_video*,@layout/dialog_update_v2" ????tools:discard="@layout/unused_layout,@drawable/unused_selector"?/>
正常情況下,資源壓縮器可準確判定系統(tǒng)是否使用了資源。不過,如果您的代碼(包含庫)調(diào)用 Resources.getIdentifier()
,這就表示您的代碼將根據(jù)動態(tài)生成的字符串查詢資源名稱。這時,資源壓縮器會采取防御性行為,將所有具有匹配名稱格式的資源標記為可能已使用,無法移除。例如,以下代碼會使所有帶 img_
前綴的資源標記為已使用:
String?name?=?String.format("img_%1d",?angle?+?1); res?=?getResources().getIdentifier(name,?"drawable",?getPackageName());
這時,我可以開啟資源的嚴格審查模式,只會保留確定已使用的資源。
Gradle 資源壓縮器只會移除未被應用引用的資源,這意味著它不會移除用于不同設備配置的備用資源。必要時,我們可以使用 Android Gradle 插件的 resConfigs
屬性來移除您的應用不需要的備用資源文件(常見的有用于國際化支持的 strings.xml
,適配用的 layout.xml
等):
android?{ ????defaultConfig?{ ????????...????????//保留中文和英文國際化支持 ????????resConfigs?"en",?"zh" ????} }
品嘗完了以上"配菜",下面讓我們來品味一下本文的"主菜":自定義混淆規(guī)則。首先,我們來了解一下常見的混淆命令。
這里說的 keep命令指的是一系列以 -keep開頭的命令,它主要用來保留 Java 中不需要進行混淆的元素。以下是常見的 -keep 命令:
-keep
作用:保留指定的類和成員,防止被混淆處理。例如:
#?保留包:com.moos.media.entity?下面的類以及類成員-keep?public?class?com.moos.media.entity.**#?保留類:NumberProgressBar-keep?public?class?com.moos.media.widget.NumberProgressBar?{*;}
-keepclassmembers
作用:保留指定的類的成員(變量/方法),它們將不會被混淆。如:
#?保留類的成員:MediaUtils類中的特定成員方法 -keepclassmembers?class?com.moos.media.MediaUtils?{ ????public?static?***?getLocalVideos(android.content.Context);????public?static?***?getLocalPictures(android.content.Context); }
-keepclasseswithmembers
作用:保留指定的類和其成員(變量/方法),前提是它們在壓縮階段沒有被刪除。與-keep
使用方式類似:
#?保留類:BaseMediaEntity?的子類-keepclasseswithmembers?public?class?*?extends?com.moos.media.entity.BaseMediaEntity{*;}#?保留類:OnProgressBarListener接口的實現(xiàn)類-keep?public?class?*?implements?com.moos.media.widget.OnProgressBarListener?{*;}
@Keep
除了以上方式,你也可以選擇使用 @Keep
注解來保留期望代碼,防止它們被混淆處理。比如,我們通過 @Keep
修飾一個類來保留它不被混淆:
@Keep data?class?CloudMusicBean(var?createDate:?String, ??????????????????????????var?id:?Long, ??????????????????????????var?name:?String, ??????????????????????????var?url:?String, ??????????????????????????val?imgUrl:?String)
同樣地,我們也可以讓 @Keep
來修飾方法或者字段進而保留它們。
dontwarn
-dontwarn
命令一般在我們引入新的 library 時會使用到,常用于處理 library 中無法解決的警告。如:
-keep?class?twitter4j.**?{?*;?} -dontwarn?twitter4j.**
其他的命令用法可參考 Android 系統(tǒng)提供的默認混淆規(guī)則:
#混淆時不生成大小寫混合的類名-dontusemixedcaseclassnames#不跳過非公共的庫的類-dontskipnonpubliclibraryclasses#混淆過程中記錄日志-verbose#關(guān)閉預校驗-dontpreverify#關(guān)閉優(yōu)化-dontoptimize#保留注解-keepattributes?*Annotation*#保留所有擁有本地方法的類名及本地方法名-keepclasseswithmembernames?class?*?{ ????native?<methods>; }#保留自定義View的get和set方法-keepclassmembers?public?class?*?extends?android.view.View?{ ???void?set*(***); ???***?get*(); }#保留Activity中View及其子類入?yún)⒌姆椒?,??onClick(android.view.View)-keepclassmembers?class?*?extends?android.app.Activity?{ ???public?void?*(android.view.View); }#保留枚舉-keepclassmembers?enum?*?{ ????**[]?$VALUES; ????public?*; }#保留序列化的類-keepclassmembers?class?*?implements?android.os.Parcelable?{ ??public?static?final?android.os.Parcelable$Creator?CREATOR; }#保留R文件的靜態(tài)成員-keepclassmembers?class?**.R$*?{ ????public?static?<fields>; } -dontwarn?android.support.** -keep?class?android.support.annotation.Keep-keep?@android.support.annotation.Keep?class?*?{*;} -keepclasseswithmembers?class?*?{ ????@android.support.annotation.Keep?<methods>; } -keepclasseswithmembers?class?*?{ ????@android.support.annotation.Keep?<fields>; } -keepclasseswithmembers?class?*?{ ????@android.support.annotation.Keep?<init>(...); }
更多混淆命令可以參考文章:Proguard 最全混淆規(guī)則說明,這里就不做詳細講解了。
我們在了解了混淆的基本命令之后,很多人應該還是一頭霧水:到底哪些內(nèi)容該混淆?其實,我們在使用代碼混淆時,ProGuard 對我們項目中大部分代碼進行了混淆操作,為了防止編譯時出錯,我們應該通過 keep
命令保留一些元素不被混淆。所以,我們只需要知道哪些元素不應該被混淆:
項目中難免可能會用到枚舉類型,然而它不能參與到混淆當中去。原因是:枚舉類內(nèi)部存在 values
方法,混淆后該方法會被重新命名,并拋出 NoSuchMethodException
。慶幸的是,Android 系統(tǒng)默認的混淆規(guī)則中已經(jīng)添加了對于枚舉類的處理,我們無需再去做額外工作。想了解更多枚舉內(nèi)部細節(jié)可以去查看源碼,篇幅有限不再細說。
被反射使用的類、變量、方法、包名等不應該被混淆處理。原因在于:代碼混淆過程中,被反射使用的元素會被重命名,然而反射依舊是按照先前的名稱去尋找元素,所以會經(jīng)常發(fā)生 NoSuchMethodException
和 NoSuchFiledException
問題。
實體類即我們常說的"數(shù)據(jù)類",當然經(jīng)常伴隨著序列化與反序列化操作。很多人也應該都想到了,混淆是將原本有特定含義的"元素"轉(zhuǎn)變?yōu)闊o意義的名稱,所以,經(jīng)過混淆的"洗禮"之后,序列化之后的 value
對應的 key
已然變?yōu)闆]有意義的字段,這肯定是我們不希望的。同時,反序列化的過程創(chuàng)建對象從根本上來說還是借助于反射,混淆之后 key
會被改變,所以也會違背我們預期的效果。
Android 中的四大組件同樣不應該被混淆。原因在于:
四大組件使用前都需要在 AndroidManifest.xml
文件中進行注冊聲明,然而混淆處理之后,四大組件的類名就會被篡改,實際使用的類與 manifest
中注冊的類并不匹配,故而出錯。
其他應用程序訪問組件時可能會用到類的包名加類名,如果經(jīng)過混淆,可能會無法找到對應組件或者產(chǎn)生異常。
當 JNI 調(diào)用的 Java 方法被混淆后,方法名會變成無意義的名稱,這就與 C++ 中原本的 Java 方法名不匹配,因而會無法找到所調(diào)用的方法。
自定義控件不需要被混淆
JavaScript 調(diào)用 Java 的方法不應混淆
Java 的 native 方法不應該被混淆
項目中引用的第三方庫也不建議混淆
代碼經(jīng)過 ProGuard 混淆處理后,想要讀取 StackTrace(堆棧追蹤)信息就會變得很困難。由于方法名稱和類的名稱都經(jīng)過混淆處理,即使程序發(fā)生崩潰問題,也很難定位問題所在。幸運的是,ProGuard 為我們提供了補救的措施,在著手進行之前,我們先來看一下 ProGuard 每次構(gòu)建后生成了哪些內(nèi)容。
混淆構(gòu)建完成之后,會在 <module-name>/build/outputs/mapping/release/
目錄下生成以下文件:
dump.txt
說明 APK 內(nèi)所有類文件的內(nèi)部結(jié)構(gòu)。
mapping.txt
提供混淆前后的內(nèi)容對照表,內(nèi)容主要包含類、方法和類的成員變量。
seeds.txt
羅列出未進行混淆處理的類和成員。
usage.txt
羅列出從 APK 中移除的代碼。
了解完混淆構(gòu)建完畢后輸出的內(nèi)容之后,我們現(xiàn)在就來看一下之前的問題:混淆處理后,StackTrace 定位困難。如何來恢復 StackTrace 的定位能力呢?系統(tǒng)為我們提供了 retrace工具,結(jié)合上文提到的 mapping.txt
文件,就可以將混淆后的崩潰堆棧追蹤信息還原成正常情況下的 StackTrace信息。主要有兩種方式來恢復 StackTrace,為了方便理解,我們以下面這段崩潰信息為例,借助兩種方式分別來還原:
?java.lang.RuntimeException:?Unable?to?start?activity? ?????Caused?by:?kotlin.KotlinNullPointerException ????????at?com.moos.media.ui.ImageSelectActivity.k(ImageSelectActivity.kt:71) ????????at?com.moos.media.ui.ImageSelectActivity.onCreate(ImageSelectActivity.kt:58) ????????at?android.app.Activity.performCreate(Activity.java:6237) ????????at?android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
通過 retrace 腳本工具
首先我們要進入到 Android SDK 路徑的 /tools/proguard/bin
目錄中,這里以 Mac 系統(tǒng)為例,可以看到如下內(nèi)容:
可以看到如上三個文件,而 proguardgui.sh
才是我們需要的 retrace
腳本(Windows系統(tǒng)下為 proguardgui.bat
)。Windows 系統(tǒng)中只需要雙擊腳本 proguardgui.bat
即可運行,至于 Mac 系統(tǒng),如果你沒有做任何配置,只需要將 proguardgui.sh
腳本拖動到 Mac 自帶的終端中,回車鍵即可運行。接著,我們會看到如下界面:
選擇 ReTrace
欄 ,并添加我們項目中混淆生成的 mapping.txt
文件所在位置,然后將我們的混淆后的崩潰信息復制到 Obfuscated stack trace
那一欄,點擊 ReTrace!
按鈕即可還原出我們的崩潰日志信息,結(jié)果如上圖所示,我們之前的混淆日志:at com.moos.media.ui.ImageSelectActivity.k(ImageSelectActivity.kt:71)
被還原成了 at com.moos.media.ui.ImageSelectActivity.initView(ImageSelectActivity.kt:71)
。ImageSelectActivity.k
是我們混淆后的方法名,ImageSelectActivity.initView
則是最初未混淆前的方法名,借助于 ReTrace 工具的幫助,我們就可以像以前一樣很快定位到崩潰代碼區(qū)域了。
通過 retrace 命令行
我們先要將崩潰信息復制到 txt
格式的文件(如:proguard_stacktrace.txt)中保存,然后執(zhí)行以下命令即可(MAC系統(tǒng)):
retrace.sh?-verbose?mapping.txt?proguard_stacktrace.txt
如果你是 windows 系統(tǒng),可以執(zhí)行以下命令:
retrace.bat?-verbose?mapping.txt?proguard_stacktrace.txt
最終還原的結(jié)果和之前效果一樣:
也許你通過以上兩種方式在對 stackTrace 進行恢復時,發(fā)現(xiàn) Unknown Source
問題:
值得注意的是,記得在混淆規(guī)則中加上如下配置來提升我們的 StackSource 查找效率:
#?保留源文件名和具體代碼行號-keepattributes?SourceFile,LineNumberTable
此外,我們每次使用 ProGuard 創(chuàng)建發(fā)布構(gòu)建時都都會覆蓋之前版本的 mapping.txt
文件,因此我們每次發(fā)布新版本時都必須小心地保存一個副本。通過為每個發(fā)布構(gòu)建保留一個 mapping.txt
文件副本,我們就可以在用戶提交的已混淆的 StackTrace 來對舊版本應用的問題進行調(diào)試和修復。
經(jīng)過上文的介紹,我們知道,APK 在經(jīng)過代碼混淆處理后,包名、類名、成員名被轉(zhuǎn)化為無意義、難以理解的名稱,增加反編譯的成本。Android ProGuard 為我們提供了默認的"混淆字典",即將元素名稱轉(zhuǎn)為英文小寫字母的形式。那么,我們可以定義自己的混淆字典嗎?賣個關(guān)子,我們先來看一張效果圖:
這個波操作是不是有點"出類拔萃"了?哈哈,就不賣關(guān)子了,其實很簡單,只要生成一套自己的 txt
格式的混淆字典,然后在混淆規(guī)則 Proguard-rules.pro
中應用一下即可:
本文中使用的混淆字典可以在此處查看并下載
當然,大家也可以自己去定制化自己的"混淆字典",增加反編譯的難度。
一路走下來,我們可以發(fā)現(xiàn),從混淆技術(shù)的必要性和優(yōu)點來看,它還是很值得我們?nèi)ド钊雽W習和研究的,本文帶大家領(lǐng)略的僅僅是"冰山一角"。由于本人的技術(shù)水平有限,若大家發(fā)現(xiàn)有問題或者闡述不當之處,歡迎指出并修正。喜歡的朋友們也麻煩點個關(guān)注,你的支持是對我最大的動力!我會定期分享Android知識點及解析,還會不斷更新的BATJ面試專題,歡迎大家前來探討交流,如有好的文章也歡迎投稿。
分享標題:Android開發(fā):請你吃一頓史上最全的Android混淆大餐
URL標題:http://jinyejixie.com/article18/jojggp.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、全網(wǎng)營銷推廣、靜態(tài)網(wǎng)站、微信公眾號、面包屑導航、建站公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)