APK 如何做到包體積優化

關於 APK Size 的優化,網上有很多版本的介紹。但是因爲每個項目的背景、實現方式都不盡相同,導致各個項目之間能列出的共性相對較少。所以這裏主要分享一下我在項目中對包體積優化的一些嘗試。主要分兩部分:安裝包監控、安裝包大小優化。

安裝包監控

Android Studio 的 APK Analyser

這是 Android Studio 提供的一個 APK 檢測工具,通過它可以查看一個 APK 文件內部各項內容所佔的大小,並且按照大小排序顯示。因此我們很容易觀察到 APK 中哪一部分內容佔用了最大空間。APK Analyzer 的使用非常簡單,只要將需要分析的 APK 文件拖入 Android Studio 中即可,顯示內容類似下圖所示:

從上圖中可以很明顯看出部分圖片佔用了比較大的資源空間,因此可以針對性地對其做壓縮優化等操作。

實際上 APK Analyzer 的作用不光是查看 APK 大小,從它的名字也能看出它是用來分析 APK 的,因此可以使用它來分析一些優秀 APK 的目錄結構、代碼規範,甚至是使用了哪些動態庫技術等。

Matrix中 的 ApkChecker

ApkChecker 是騰訊開源框架 Matrix 的一部分,主要是用來對 Android 安裝包進行分析檢測,並輸出較爲詳細的檢測結果報告。正常情況下我們需要下載 Matrix 源碼,並單獨編譯 matrix-apk-cananry 部分。但是如果想快速使用 ApkChecker,可以直接在網上下載其 ApkChecker.jar 文件,然後創建一個配置文件 .json 即可。

官方的配置文件格式如下:

配置文件有幾個地方是需要我們去替換的。

apk:需要分析的 APK 文件的路徑;
mappingTxt:指定混淆 mapping 文件的路徑;
output:分析報告的輸出目錄;
rTx:APK 文件生成時,對應的 R 文件目錄。
ApkChecker 的好處是可以命令行使用,這樣我們可以很方便將其配置在自動化集成系統中,並對最終生成的 APK 文件進行分析,將產出報告發送到指定位置。這樣不管是程序開發人員或者是測試工程師,都可以很直觀地對當前版本 APK 有一個大概的評估。

注意:Matrix 也有一定的缺陷,比如使用 UnusedAssetTask 檢索 assets 中的資源,這個過程只是調用 DexFileFactory.loadDexFile 加載 dex 文件,所以只會去搜索 java 文件中的引用。如果在 assets 目錄下有一個 .json 文件,此 .json 文件中記錄 assets 文件夾中的其他圖片路徑,然後在 Java 代碼中通過 AssetManager 讀取這個 .json 文件之後,循環遍歷出它所引用的圖片,對於這種方式 Matrix 是檢測不到的,會將它置爲 unused。關於這方面我也給 Matrix 作者提過反饋,目前是沒有好的解決辦法。

安裝包優化實踐

刪除無用文件

使用 Lint 查看未引用資源。Lint 是一個靜態掃描工具,它可以識別出項目中沒有被任何代碼所引用到的資源文件。具體使用也很簡單,只要在 Android Studio 中點擊 Analyze -> Inspect Code,然後選中整個項目即可,如下所示:

如果項目中有未被使用資源,則 Lint 會在窗口 Inspection Result 中顯示,類似結果如下:

下面兩個選項可以在項目編譯時期減少被打包到 APK 中的文件, 使用 shrinkResources 能夠在項目編譯階段,刪除所有在項目中未被使用到的資源文件。但是需要將 minifyEnabled 選項設置爲 true。

使用 resConfig 限定國際化資源文件。有時候我們使用到的三方庫中可能會對各種國際化語言進行支持,但是我們自己的項目只支持某個語言,比如中文,那我們可以在 gradle 的 defaultConfig 中使用 resConfig 來限制打包到 APK 中的國際化資源文件,具體如下所示:

文件優化

關於靜態圖片優化
優先使用 VectorDrawable 圖片,如果 UI 無法提供 VectorDrawable 圖片,那麼 webp 格式是一個不錯的選擇。Android Studio 也支持直接將 png 或者 jpg 格式圖片轉化爲 webp 格式,如下所示:

關於動態圖片優化
實際上 webp 也可以作動態圖,只是目前對 webp 動圖支持的三方庫並不多,谷歌官方的 Glide 對 webp 支持也不是很友好。

但是谷歌推出了一套 C++ 依賴庫,上層開發人員可以基於此庫的基礎上使用 JNI 來解析 Animated webp 圖片,並將解析出來的每一幀封裝成一個 Bitmap,並根據解析出來的時間差值動態顯示相應的幀 Bitmap 即可。如果 JNI 不熟或者不想再花時間精力去實現 JNI 調用,可以考慮使用 GitHub 的 Android-WebP 。Android 開發人員只需使用 WebpImageView 控件並指定圖片路徑即可。

另外針對動態圖片,我們也做了其他方面的嘗試。做過遊戲開發的一般都比較熟悉 TextureAlas 這種圖片格式,就是將多個序列圖按照一定的排放位置合成到一張圖片中,比如以下圖片:

並且跟隨圖片一起生成的還有一個用來對其解析的文本配置文件。主要是用來識別合成圖中的路徑、每張幀圖片的序列、位置等。一般情況下配置文本的格式如下:

這套方案主要是借鑑了一個輕量級遊戲引擎 libgdx 的實現思路,解析上述的 TextureAtlas 圖片,將生成的 Texture 渲染到 Bitmap 上展示每一幀內容。具體代碼如下:

更多實現細節,可以參考 libgdx 在 GitHub上的介紹:Libgdx github wiki : Texture packer。

這套方案的優點是圖片壓縮效果比 webp 和 gif 更加顯著,生成的合成圖片比 webp 和 gif 更小。但是缺點是使用技術門檻較高,需要有一定 OpenGL 基礎。

關於引入三方庫
在 App 中會引入各種三方的”輪子”,但是在引入之前最好權衡一下是否需要將其代碼全部引入,造成不必要的代碼或者資源也被打包到 APK 中。

比如在我們項目中曾經使用到嗶哩嗶哩的 ijkplayer 庫,原因是我們實現的視頻渲染功能,在某些舊的廠商手機中無法正常播放。後來分析下來總結是廠商手機並沒有很好地支持谷歌最新的硬編碼格式,而使用 ijkplayer 的軟編碼恰恰能解決此問題。

但是 ijkplayer 是一套完整的視頻播放器,很多功能都不是必需的,這種情況下如果只是因爲解決一個在很小部分的手機上的 bug,而引入一個比較大的庫性價比是不高的,因此需要將 ijkplayer 中關於軟編碼的功能摘取出來放到項目中。

這種做法同樣適用於對 webp 動圖的實現方案,上文中有介紹我們使用了谷歌官方推薦的 libwebp,但是這個庫不光是爲了解析 webp 圖片,還有很大一部分代碼是爲了實現生成一個 webp 圖片,這部分代碼我們是不需要的,因此也需要將這部分代碼給刪除,最終編譯之後生成的 so 庫大小可以減少 1/ 3 左右。

關於 App Bundle
如果 App 是海外項目,那麼會舒服很多。因爲谷歌官方支持動態發佈。正常情況下我們的 APK 中爲了更好地適配屏幕、語言等,會在項目裏添加多套相應的資源文件,比如不同 hdpi 的 drawable,或者不同 CPU 下的 so 文件,最終打包生成的 APK 中會包含所有的資源文件。但是實際上一臺手機設備只會用到這其中的一套資源,這無形中就已經產生了一些不必要的資源浪費。而谷歌的 Dynamic Delivery 功能就天然地解決了這個問題,通過 Google Play Store 安裝 APK 時,會根據安裝設備的屬性,只選取相應的資源打包到 APK 文件中。

另外我們在項目中也使用了另一個 App Bundle 中比較好用的選項--Dynamic Asset Delivery。這個功能本來只是針對安裝包超過 100M 的 App,但是不影響我們使用這套方案進行安裝包優化。具體做法就是將大部分 assets 中的資源使用無損壓縮的方式,壓縮成一個 .obb 格式的文件,然後每次發佈 APK 時都將此 obb 文件設置爲 APK 的 bundle 文件,這樣也可以減少用戶實際的安裝包大小。

但是 App Bundle 目前只適合在 Google Play Store 上發佈的項目,國內目前還是通過各家的插件化方案來實現動態部署,一定程度上也可以算作減少安裝包大小的方案。

總結

這節主要介紹了我們平時在項目中關於安裝包優化的一些實踐總結,主要分兩方面:

安裝包的監控
主要介紹了幾個可以用來分析安裝包大小以及詳細內容的工具:Apk Analyzer 和 ApkChecker。實際上,在開發過程中,良好的編程習慣和嚴格的 code review 也是非常重要的。

安裝包優化實踐
主要思路就是刪減無用資源或者代碼,並對資源文件進行相應的壓縮優化。實際上除了資源文件,對於代碼部分也可以更進一步的優化,比如使用 Proguard,或者直接使用 R8 編譯方式。 只是因爲 R8 還處於實驗階段,我們項目中沒有過多的實踐過程。對於這一部分極力推薦你閱讀一下 Jake Wharton 的個人博客:jakewharton 中的相關介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章