vivo 游戲中心包體積優化方案與實踐
一、包體積優化的必要性
安裝包大小與下載轉化率的關系大致是成反比的,即安裝包越大,下載轉換率就越差。Google 曾在2019的谷歌大會上給出過一個統計結論,包體積體大小每上升6MB,應用下載轉化率就會下降1%,在不同地區的表現可能會有所差異。
APK 減少10MB,在不同國家轉化率增長
(注:數據來自于 googleplaydev:Shrinking APKs, growing installs)
二、游戲中心 APK 組成
APK 包含以下目錄:
- META-INF/:包含 CERT.SF 、CERT.RSA 簽名文件、MANIFEST.MF 清單文件。
- assets/:包含應用的資源。
- res/:包含未編譯到 resources.arsc 中的資源。
- lib/:支持對應 CPU 架構的 so 文件。
- resources.arsc:資源索引文件。
- classes.dex:可以理解的dex文件就是項目代碼編譯為 class 文件后的集合。
- AndroidManifest.xml:包含核心 Android 清單文件。此文件列出了應用的名稱、版本、訪問權限和引用的庫文件。
發現占包體積比較大的主要是 lib、res、assets、resources 這幾個部分,優化主要也從這幾個方面入手。
三、包體積檢測工具
Matrix-ApkChecker 作為 Matrix 系統的一部分,是針對Android 安裝包的分析檢測工具,根據一系列設定好的規則檢測 APK 是否存在特定的問題,并輸出較為詳細的檢測結果報告,用于分析排查問題以及版本追蹤。
配置游戲中心的 Json,主要檢測 APK 是否經過了資源混淆、不含 Alpha 通道的 PNG 文件、未經壓縮的文件類型、冗余的文件、無用資源等信息。
對于生成的檢測文件進行分析,可以優化不少體積。
工具 Matrix Apkcheck 介紹:
https://github.com/Tencent/matrix/wiki/Matrix-Android-ApkChecker
四、包體積優化措施
4.1 不含 Alpha 通道的 PNG 大圖
項目中存在較多這種類型的圖,可以替換為 JPG 或者 WebP 圖,能減少不少體積。
4.2 代碼做減法
隨著業務的迭代,很多業務場景是不會再使用了,涉及到相關的資源和類文件都可以刪除掉,相應的 APK 中 res 和 dex 都會相應減少。游戲中心這次去掉了些經過迭代后沒有使用的業務場景和資源。
4.3 資源文件最少化配置
針對內銷的項目,本地的 string.xml 或者 SDK 中的 string.xml 文件中的多語言,是根本用不到的。這部分資源可以優化掉,能減少不少體積。
在APP的 build.gradle 中下添加 resConfigs "zh-rCN", "zh-rTW", "zh-rHK"。這樣配置不影響英文、中文、中國臺灣繁體、中國香港繁體語言的展示。
資源文件最少化配置前
資源文件最少化配置后
4.4 配置資源優化
很多項目為了適配各種尺寸的分辨率,同一份資源可能在不同的分辨率的目錄下放置了各種文件,然后現在主流的機型都是 xxh 分辨率,游戲游戲中心針對了內置的 APK,配置了優先使用"xxhdpi", "night-xxhdpi"。
這么配置如果 xxhdpi、night-xxhdpi 存在資源文件,就會優先使用該分辨率目錄下文件,如果不存在則會取原來分辨率目錄下子資源,能避免出現資源找不到的情形。
defaultConfig {
resConfigs isNotBaselineApk ? "" : ["xxhdpi", "night-xxhdpi"]
}
左右滑動查看完整代碼
4.5 內置包去除v1簽名
同樣對于內置包來說,肯定都是 Android 7 及以上的機型了,可以考慮去掉v1簽名。
signingConfigs {
gameConfig {
if (isNotBaselineApk) {
print("v1SigningEnabled true")
v1SigningEnabled true
} else {
print("v1SigningEnabled false")
v1SigningEnabled false
}
v2SigningEnabled true
}
}
去掉v1簽名后,上圖的三個文件在 APK 中會消失,也能較少 600k 左右的體積。
4.6 動效資源文件優化
發現項目中用了不少的 GIF、Lottie 文件、SVG 文件,占用了很大一部分體積。考慮將這部分替換成更小的動畫文件,目前游戲中心接入了 PAG 方案。替換了部分 GIF 圖和 Lottie 文件。
PAG 文件采用可擴展的二進制文件格式,可單文件集成圖片音頻等資源,導出相同的 AE 動效內容,在文件解碼速度和壓縮率上均大幅領先于同類型方案,大約為 Lottie 的0.5倍,SVG 的0.2倍。
實際上可能由于設計導出的 Lottie 或者 GIF 不規范,在導出 PAG 文件時會提醒優化點,實際部分資源的壓縮比率達到了80~90%,部分動效資源從幾百K降到了幾十K。
具體可以參考 PAG 官網:
https://github.com/Tencent/libpag/blob/main/README.zh_CN.md
游戲中心這邊將比較大的 GIF 圖,較多的 Lottie 圖做過 PAG 替換。
舉例:
(1)游戲中心的榜單排行頁上的頭圖,UI那邊導出的符合效果的 GIF 大小為701K,替換為 PAG 格式后同樣效果的圖大小為67K,只有原來的1/10不到。
(2)游戲中心的入口空間 Lottie 動效優化。
一份 Lottie 動效大概是這樣的,一堆資源問題加上 Json 文件。像上述動效的整體資源為112K,同樣的動效格式轉換為 PAG 格式后,資源大小變成6K,只有原大小的5%左右。之后新的動效會優先考慮使用 PAG。
4.7 編譯期間優化圖片
以游戲中心 App 為例,圖片資源約占用了25%的包體積,因此對圖片壓縮是能立桿見效的方式。
WebP 格式相比傳統的 PNG 、JPG 等圖片壓縮率更高,并且同時支持有損、無損、和透明度。
思路就是在是在 mergeRes 和 processRes 任務之間插入 WebP 壓縮任務,利用 Cwebp 對圖片在編譯期間壓縮。
(注:圖片來源于https://booster.johnsonlee.io/zh/guide/shrinking/png-compression.html#pngquant-provider)
已有的解決方法:
(1)可以采用滴滴的方案 booster,booster-task-compression-cwebp 。
參考鏈接:https://github.com/didi/booster
(2)公司內部官網模塊也有類似基于 booster 的插件,基于 booster 提供的 API 實現的圖片壓縮插件。壓縮過后需要對所有頁面進行一次點檢,防止圖片失真,針對失真的圖片,可以采用白名單的機制。
4.8 動態化加載so
同樣以游戲中心為例,so的占比達到了45.1%,可以對使用場景較少和較大的so進行動態化加載的策略,在需要使用的場景下載到本地,動態去加載。
使用的場景去服務端下載到本地加載的流程可以由以下流程圖表示。
流程可以歸納為下載、解壓、加載,主要問題就是解決so加載問題。
載入so庫的傳統做法是使用:
System.loadLibrary(library);
經常會出現 UnsatisfiedLinkError,Relinker 庫能大幅減小報錯的概率:
ReLinker.loadLibrary(context, "mylibrary")
具體可以參考:
https://github.com/KeepSafe/ReLinker
按需加載的情形,風險與收益是并存的,有很多情況需要考慮到,比如下載觸發場景、網絡環境、加載失敗是否有降級策略等等,也需要做好給用戶的提示交互。
4.9 內置包只放64位so
目前新上市的手機 CPU 架構都是arm64-v8a, 對應著 ARMV8 架構,所以在打包的時候針對內置項目,只打包64位so進去。
ndk {
if ("64" == localMultilib)
abiFilters "arm64-v8a"
else if ("32" == localMultilib)
abiFilters "armeabi"
else
abiFilters "armeabi", "arm64-v8a"
}
//其中localMultilib為配置項變量
String localMultilib = getLocalMultilib()
String getLocalMultilib() {
def propertyKey = "LOCAL_MULTILIB"
def propertyValue = rootProject.hasProperty(propertyKey) ? rootProject.getProperty(propertyKey) : "both"
println " --> ${project.name}: $propertyKey[$propertyValue], $propertyKey[${propertyValue.class}]"
return propertyValue
}
左右滑動查看完整代碼
4.10 開啟代碼混淆、移除無用資源、ProGuard 混淆代碼
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
shrinkResources 和 minifyEnabled 必須同時開啟才有效。
特別注意:這里需要強調一點的是開啟之后無用的資源或者圖片并沒有真正的移除掉,而是用了一個同名的占位符號。
可以通過 ProGuard 來實現的,ProGuard 會檢測和移除代碼中未使用的類、字段、方法和屬性,除此外還可以優化字節碼,移除未使用的代碼指令,以及用短名稱混淆類、字段和方法。
proguard-android.txt 是 Android 提供的默認混淆配置文件,在配置的 Android sdk /tools/proguard 目錄下,proguard-rules.pro是我們自定義的混淆配置文件,我們可以將我們自定義的混淆規則放在里面。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
左右滑動查看完整代碼
4.11 R文件內聯優化
如果我們的 App 架構如下:
編譯打包時每個模塊生成的 R 文件如下:
R_lib1 = R_lib1;
R_lib2 = R_lib2;
R_lib3 = R_lib3;
R_biz1 = R_lib1 + R_lib2 + R_lib3 + R_biz1(biz1本身的R)
R_biz2 = R_lib2 + R_lib3 + R_biz2(biz2本身的R)
R_app = R_lib1 + R_lib2 + R_lib3 + R_biz1 + R_biz2 + R_app(app本身R)
左右滑動查看完整代碼
可以看出各個模塊的R文件都會包含下層組件的R文件內容,下層的模塊生成的id除了自己會生成一個R文件外,同時也會在全局的R文件生成一個,R文件的數量同樣會膨脹上升。多模塊情況下,會導致 APK 中的 R 文件將急劇的膨脹,對包體積的影響很大。
由于App模塊目前的R文件中的資源ID全部是 final 的, Java 編譯器在編譯時會將 final 常量進行 inline 內聯操作,將變量替換為常量值,這樣項目中就不存在對于 App 模塊R文件的引用了,這樣在代碼縮減階段,App 模塊R文件就會被移除,從而達到包體積優化的目的。
基于以上原理,如果我們將 library 模塊中的資源 ID 也轉化為常量的話,那么 library 模塊的R文件也可以移除了,這樣就可以有效地減少我們的包體積。
現在有不少開源的R文件內聯方法,比如滴滴開源的 booster 與字節開源的 bytex 都包含了R文件內聯的插件。
booster 參考:
bytex 參考:
https://github.com/bytedance/ByteX/blob/master/access-inline-plugin/README-zh.md
五、優化效果
5.1 優化效果
上述優化措施均在游戲中心實際中采用,以游戲中心某個相同的版本為例子,前后體積對比如下圖所示:
(1)包體積優化的比例達到了31%,包體積下降了20M左右,從長久來說對應用的轉換率可以提升3%的點左右。
(2)啟動速度相對于未優化版本提升2.2%個點。
5.2 總結
(1)讀者想進行體積優化之前,需先分析下 APK 的各個模塊占比,主要針對占比高的部分進行優化,比如:游戲中心中 lib、res、assets、resources 占比較高,就針對性的進行了優化;
(2)動效方案的切換、so動態加載、編譯期間圖片優化等措施是長久的,相比于未進行優化,時間越長可能減少的體積越明顯;
(3)資源文件最小化配置、配置資源優化,簡單且效果顯著;
(4)后續會對 dex 進行進一步探索,目前項目中代碼基本上都在做加法,越來越復雜,很少有做減法,導致 dex 逐漸增大,目前還在探索怎么進一步縮小 dex 體積。