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