為了盡可能減⼩應⽤的⼤⼩,我們應該在釋出版本中移除不使⽤的代碼和資源。 另外還存在兩個優化⽅向可以⽤來縮減應⽤程式的占⽤空間,⼀項是使⽤混淆處理功能,該功能會縮短應⽤的類 和成員的名稱;另⼀項是使⽤優化功能,該功能會采⽤更積極的政策來進⼀步減⼩應⽤的⼤⼩。本⽂将介紹如何通過APK的資源優化來減輕應⽤程式的占⽤空間從⽽節省⽤戶資源。
提出問題
⾸先使⽤
友盟+推出的産品U-APM來實際測試應⽤程式在不同的裝置上的使⽤情況:

從圖中可以看出應⽤程式在啟動時間上還存在優化空間,下⼀步我們将讀取應⽤程式的記憶體配置設定,确定能從哪些⽅向⼊⼿對資源進⾏優化,從⽽加快啟動時間。
為了對以上⼏個名額進⾏詳細分析, 我們使⽤了Android 窗⼝上的Memory 頁籤, 它将向我 們顯示随時間在堆上配置設定的資料量:
圖中顯示發⽣了 GC 事件, 删除了未使⽤的對象并釋放了堆上的空間。
為了調查目前在堆中配置設定的内容, 我們可以使⽤左側的堆轉儲按鈕。 這将對堆中目前配置設定的内容 進⾏快照, 并将其顯示在 Android Studio 内的特殊報告螢幕中:
在左側, 我們看到堆中執行個體的直⽅圖, 按類名分組。 對于每⼀個, 都有配置設定的對象數量、 這些實 例的⼤⼩ (淺層⼤⼩) 以及這些對象在記憶體中保留的⼤⼩。 後者告訴我們如果這些執行個體被釋放 , 可以釋放多少記憶體。 這個視圖讓我們對應⽤程式的記憶體占⽤有⼀個重要的了解, 幫助我們識别⼤ 型資料結構和對象關系。 這些資訊可以幫助我們建構更⾼效的資料結構, 解開對象連接配接以減少保 留的資源, 并最終盡可能地減少資源占⽤。
随後, 我們使⽤單個布局⽂件建構⼀個最⼩的 APK,以計算布局⽂件的名稱在 Android APK 中 出現次數。
使⽤ Gradle 建構 Android 應⽤程式隻需要⼀個AndroidManifest.xml⽂件。 我們可以添加⼀個 虛拟布局。
. ├── build.gradle └── src └── main ├── AndroidManifest.xml └── res └── layout └── home_view.xml |
運⾏gradle assembleRelease将産⽣⼀個隻有 2,118 位元組的釋出版 APK。 我們可以使⽤轉儲其 内容xxd并查找home_view位元組序列。
根據此輸出,在 APK 中存在 3 次未壓縮的路徑和 1 次未壓縮的僅名稱。
zip ⽂件是⼀個⽂件條⽬清單,後跟所有可⽤條⽬的⽬錄。每個條⽬都包含⽂件路徑,⽬錄也是如此。這說明了輸出中的第⼀次出現(條⽬标題)和最後⼀次出現(⽬錄記錄)。
輸出中出現的中間兩次來⾃resources.arsc⽂件内部,該⽂件是資源排序的資料庫。它的内容是
可⻅的,因為該⽂件在 APK 中未壓縮。運⾏aapt dump --values resources
build/outputs/apk/release/app-release-unsigned.apk顯示home_view記錄及其到路徑的映射:
APK 包含classes.dex⽂件中第五次出現的名稱。它沒有顯示在xxd輸出中,因為⽂件被壓縮了。運⾏baksmali dump <(unzip -p build/outputs/apk/release/app-release-unsigned.apkclasses.dex)顯示 dex ⽂件的字元串表,其中包含以下條⽬home_view:
這是⽤于将R.layout布局名稱映射到唯⼀整數值的類中的字段。順便說⼀下,該整數是
resources.arsc資料庫的索引,⽤于查找相關⽂件名以讀取其 XML 内容。
總結⼀下我們問題的答案,對于每個資源⽂件,完整路徑出現 3 次,名稱出現兩次。
優化資源
Android Gradle 插件 4.2 引⼊了⼀個android.enable ResourceOptimizations=true标志,它将運⾏針對資源的優化。這會aapt optimize在合并的資源和resources.arsc⽂件打包到 APK 之前調⽤它們的指令。優化僅适⽤于釋出版本,⽆論是否minifyEnabled設定為 true ,都會運⾏。添加标志後,gradle.properties我們可以使⽤漫反射來⽐較兩個 APK以檢視其效果。輸出很⻓,是以我們将按部分分解。
⾸先是 APK 中内容的差異。“壓縮”列是 APK 内的成本,“未壓縮”列是提取時的成本。該res類别代表我們的單個資源⽂件,其⼤⼩下降了 28 個位元組。該arsc類别⽤于resource.arsc本身,顯然,這⾥産⽣了⼀定的優化。
這兩部分代表資源資料庫的代碼和内容。沒有變化,我們可以推斷優化沒有影響
R.layout.home_view字段和home_view資源條⽬。
最後顯示了優化的效果。我們的布局資源的⽂件名被明顯截斷并移出layout/⽂件夾。
在 Gradle 項⽬中,XML 的⽂件夾和⽂件名是有意義的。⽂件夾是資源類型,名稱對應.arsc⽂件中⽣成的字段和資源條⽬。但是,如果這些⽂件位于 APK 中,⽂件路徑就變得毫⽆意義。資源優化通過使名稱盡可能短來進⾏優化。
輸出aapt dump資源資料庫也反映了⽂件更改:
APK 中路徑的所有三個出現現在都更短,從⽽節省了 36 位元組。雖然 36 位元組是⼀個⾮常⼩的數字,但整個⼆進制⽂件隻有 2,118 位元組。36 位元組的節省了 1.7% 的資源。
Nick Butcher 的Plaid應⽤程式有 734 個資源⽂件。除了數量之外,資源⽂件的名稱更具描述性(這是說它們更⻓的⼀種奇特⽅式)。home_viewPlaid 包含的名稱是searchback_stem_search_to_back.xml、attrs_elastic_drag_dismiss_frame_layout、 和
designer_news_story_description.xml。
沒有資源優化的建構與啟⽤它的建構進⾏⽐較:
資源優化使 APK ⼤⼩節省了 0.76%。
Uwe Trottmann 的SeriesGuide應⽤程式有 1044 個資源⽂件。與 Plaid 不同,它沒有本機庫,這應該會讓優化效果更好。
我再次将項⽬更新到 AGP 4.2并⽐較兩個版本:
在這⾥,資源優化能夠将 APK ⼤⼩減少 2.0%!
Chris Banes 的Tivi應⽤程式有⼀個使⽤Jetpack Compose 編寫的重要⼦集,這意味着整體資源更少。目前建構仍包含 776 個資源⽂件。
通過使⽤ Compose,Tivi 已經在使⽤最新的 AGP 4.2。通過兩個快速建構,我們可以看到資源優化的影響:
我們再⼀次達到了 APK ⼤⼩縮減 2.0% 的程度
APK簽名
APK 簽名有多個版本,如果您的版本minSdkVersion低于 24,則需要在簽名時包含版本V1。
V1 簽名使⽤Java 的.jar 簽名規範,該規範将每個⽂件作為⽂件中的⽂本條⽬單獨簽名METAINF/
MANIFEST.MF。
在為原始單布局應⽤程式建立和配置密鑰庫後,轉儲清單⽂件unzip -c
build/outputs/apk/release/app-release.apk META-INF/MANIFEST.MF顯示以下簽名:
每個⽂件的完整路徑出現,使每個資源路徑的總出現次數達到四次。由于較短的名稱将再次導緻此⽂件包含更少的位元組,是以資源優化在簽名中具有更⼤的影響。
________________________________________
根據資料顯示,這⼀⽅法可以節省 1-3% 的APK ⼤⼩。根據實際測試,這個範圍似乎是正确的。最終節省的費⽤将取決于 APK 中資源⽂件的⼤⼩和數量。
作者:杜洪林