毫無疑問,混淆是打包過程中重要的流程之一,在沒有特殊原因的情況下,所有 app 都應該開啟混淆。
首先,這里說的的混淆其實是包括了代碼壓縮、代碼混淆以及資源壓縮等的優化過程。依靠 ProGuard,混淆流程將主項目以及依賴庫中未被使用的類、類成員、方法、屬性移除,這有助于規避64K方法數的瓶頸;同時,將類、類成員、方法重命名為無意義的簡短名稱,增加了逆向工程的難度。而依靠 Gradle 的 Android 插件,我們將移除未被使用的資源,可以有效減小 apk 安裝包大小。
本文由兩部分構成,部分給出混淆的佳實踐,力求讓零基礎的新手都可以直接使用混淆;第二部分會介紹一下混淆的整體、自定義混淆規則的語法與實踐、自定義資源保持的規則等。
一般情況下,app module 的 build.gradle 文件默認會有如下結構:
因為開啟混淆會使編譯時間變長,所以debug模式下不應該開啟。我們需要做的是:
1. 將release下minifyEnabled的值改為true,打開混淆;
2. 加上shrinkResources true,打開資源壓縮。
修改后文件內容如下:
在 app module 下默認生成了項目的自定義混淆規則文件 proguard-rules.pro,多方調研后,一份適用于大部分項目的混淆規則佳實踐如下:
真正通用的、需要添加的就是上面這些,除此之外,需要每個項目根據自身的需求添加一些混淆規則:
- 第三方庫所需的混淆規則。正規的第三方庫一般都會在接入文檔中寫好所需混淆規則,使用時注意添加。
- 在運行時動態改變的代碼,例如反射。比較典型的例子就是會與 json 相互轉換的實體類。假如項目命名規范要求實體類都要放在model包下的話,可以添加類似這樣的代碼把所有實體類都保持住:-keep public class **.*Model*.** {*;}
- JNI中調用的類。
- WebView中JavaScript調用的方法
- Layout布局使用的View構造函數、android:onClick等。
混淆過的包必須進行檢查,避免因混淆引入的bug。
一方面,需要從代碼層面檢查。使用上文的配置進行混淆打包后在<module-name>/build/outputs/mapping/release/ 目錄下會輸出以下文件:
- dump.txt
描述APK文件中所有類的內部結構
- mapping.txt
提供混淆前后類、方法、類成員等的對照表
- seeds.txt
列出沒有被混淆的類和成員
- usage.txt
列出被移除的代碼
我們可以根據 seeds.txt 文件檢查未被混淆的類和成員中是否已包含所有期望保留的,再根據 usage.txt 文件查看是否有被誤移除的代碼。
另一方面,需要從測試方面檢查。將混淆過的包進行全方面測試,檢查是否有 bug 產生。
混淆后的類、方法名等等難以閱讀,這固然會增加逆向工程的難度,但對追蹤線上 crash 也造成了阻礙。我們拿到 crash 的堆棧信息后會發現很難定位,這時需要將混淆反解。
在 <sdk-root>/tools/proguard/ 路徑下有附帶的的反解工具(Window 系統為 proguardgui.bat,Mac 或Linux 系統為 proguardgui.sh)。
這里以 Window 平臺為例。雙擊運行 proguardgui.bat 后,可以看到左側的一行菜單。點擊 ReTrace,選擇該混淆包對應的 mapping 文件(混淆后在 <module-name>/build/outputs/mapping/release/ 路徑下會生成mapping.txt 文件,它的作用是提供混淆前后類、方法、類成員等的對照表),再將 crash 的 stack trace 黏貼進輸入框中,點擊右下角的 ReTrace ,混淆后的堆棧信息就顯示出來了。
以上使用 GUI 程序進行操作,另一種方式是利用該路徑下的 retrace 工具通過命令行進行反解,命令是
例如:
1) 所有在 AndroidManifest.xml 涉及到的類已經自動被保持,因此不用特意去添加這塊混淆規則。(很多老的混淆文件里會加,現在已經沒必要)
2) proguard-android.txt 已經存在一些默認混淆規則,沒必要在 proguard-rules.pro 重復添加,該文件具體規則見附錄1:
Android中的“混淆”可以分為兩部分,一部分是 Java 代碼的優化與混淆,依靠 proguard 混淆器來實現;另一部分是資源壓縮,將移除項目及依賴的庫中未被使用的資源(資源壓縮嚴格意義上跟混淆沒啥關系,但一般我們都會放一起講)。
代碼混淆是包含了代碼壓縮、優化、混淆等一系列行為的過程。如上圖所示,混淆過程會有如下幾個功能:
1. 壓縮。移除無效的類、類成員、方法、屬性等;
2. 優化。分析和優化方法的二進制代碼;根據proguard-android-optimize.txt中的描述,優化可能會造成一些潛在風險,不能保證在所有版本的Dalvik上都正常運行。
3. 混淆。把類名、屬性名、方法名替換為簡短且無意義的名稱;
4. 預校驗。添加預校驗信息。這個預校驗是作用在Java平臺上的,Android平臺上不需要這項功能,去掉之后還可以加快混淆速度。
這四個流程默認開啟。
在 Android 項目中我們可以選擇將“優化”和“預校驗”關閉,對應命令是-dontoptimize、-dontpreverify(當然,默認的 proguard-android.txt 文件已包含這兩條混淆命令,不需要開發者額外配置)。
資源壓縮將移除項目及依賴的庫中未被使用的資源,這在減少 apk 包體積上會有不錯的效果,一般建議開啟。具體做法是在 build.grade 文件中,將 shrinkResources 屬性設置為 true。需要注意的是,只有在用minifyEnabled true開啟了代碼壓縮后,資源壓縮才會生效。
資源壓縮包含了“合并資源”和“移除資源”兩個流程。
“合并資源”流程中,名稱相同的資源被視為重復資源會被合并。需要注意的是,這一流程不受shrinkResources屬性控制,也無法被禁止, gradle 必然會做這項工作,因為假如不同項目中存在相同名稱的資源將導致錯誤。gradle 在四處地方尋找重復資源:
- src/main/res/ 路徑
- 不同的構建類型(debug、release等等)
- 不同的構建渠道
- 項目依賴的第三方庫
合并資源時按照如下優先級順序:
舉個例子,假如重復資源同時存在于main文件夾和不同渠道中,gradle 會選擇保留渠道中的資源。
同時,如果重復資源在同一層次出現,比如src/main/res/ 和 src/main/res2/,則 gradle 無法完成資源合并,這時會報資源合并錯誤。
“移除資源”流程則見名知意,需要注意的是,類似代碼,混淆資源移除也可以定義哪些資源需要被保留,這點在下文給出。
在上文“混淆配置”中有這樣一行代碼
這行代碼定義了混淆規則由兩部分構成:位于 SDK 的 tools/proguard/ 文件夾中的 proguard-android.txt 的內容以及默認放置于模塊根目錄的 proguard-rules.pro 的內容。前者是 SDK 提供的默認混淆文件(內容見附錄1),后者是開發者自定義混淆規則的地方。
在部分 Android 混淆佳實踐中已介紹部分需要使用到的混淆命令,這里不再贅述。需要特別介紹的是與保持相關元素不參與混淆的規則相關的幾種命令:
| 命令 | 作用 |
|---|---|
| -keep | 防止類和成員被移除或者被重命名 |
| -keepnames | 防止類和成員被重命名 |
| -keepclassmembers | 防止成員被移除或者被重命名 |
| -keepnames | 防止成員被重命名 |
| -keepclasseswithmembers | 防止擁有該成員的類和成員被移除或者被重命名 |
| -keepclasseswithmembernames | 防止擁有該成員的類和成員被重命名 |
形如:
“類”代表類相關的限定條件,它將終定位到某些符合該限定條件的類。它的內容可以使用:
- 具體的類
- 訪問修飾符(public、protected、private)
- 通配符*,匹配任意長度字符,但不含包名分隔符(.)
- 通配符**,匹配任意長度字符,并且包含包名分隔符(.)
- extends,即可以指定類的基類
- implement,匹配實現了某接口的類
- $,內部類
“成員”代表類成員相關的限定條件,它將終定位到某些符合該限定條件的類成員。它的內容可以使用:
- 匹配所有構造器
- 匹配所有域
- 匹配所有方法
- 通配符*,匹配任意長度字符,但不含包名分隔符(.)
- 通配符**,匹配任意長度字符,并且包含包名分隔符(.)
- 通配符***,匹配任意參數類型
- …,匹配任意長度的任意類型參數。比如void test(…)就能匹配任意 void test(String a) 或者是void test(int a, String b) 這些方法。
- 訪問修飾符(public、protected、private)
舉個例子,假如需要將name.huihui.test包下所有繼承Activity的public類及其構造函數都保持住,可以這樣寫:
用shrinkResources true開啟資源壓縮后,所有未被使用的資源默認被移除。假如你需要定義哪些資源必須被保留,在 res/raw/ 路徑下創建一個 xml 文件,例如 keep.xml。
通過一些屬性的設置可以實現定義資源保持的需求,可配置的屬性有:
- tools:keep 定義哪些資源需要被保留(資源之間用“,”隔開)
- tools:discard 定義哪些資源需要被移除(資源之間用“,”隔開)
- tools:shrinkMode 開啟嚴格模式
當代碼中通過 Resources.getIdentifier() 用動態的字符串來獲取并使用資源時,普通的資源引用檢查就可能會有問題。例如,如下代碼會導致所有以“img_”開頭的資源都被標記為已使用。
我們可以設置 tools:shrinkMode 為 strict 來開啟嚴格模式,使只有確實被使用的資源被保留。
以上就是自定義資源保持規則相關的配置,舉個例子:
一些替代資源,例如多語言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我們不需要使用又不想刪除掉時,可以使用資源壓縮將它們移除。
我們使用 resConfig 屬性來指定需要支持的屬性,例如
其他未顯式聲明的語言資源將被移除。
proguard-android.txt文件內容
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。