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