目錄:
一、為什么我們需要做 APK 的體積優(yōu)化?
二、APK 組成
三、APK分析
四、代碼體積優(yōu)化
五、資源體積優(yōu)化
六、so體積優(yōu)化
七、其他方案
八、包體積監(jiān)控
九、總結(jié)
一、為什么我們需要做 APK 的體積優(yōu)化?
1、下載轉(zhuǎn)化率
瘦身優(yōu)化 最主要的好處是對(duì)應(yīng)用 下載轉(zhuǎn)化率 的影響,包體積越小,用戶下載等待的時(shí)間也會(huì)越短,所以下載轉(zhuǎn)換成功率也就越高。所以,安裝包大小與下載轉(zhuǎn)化率的關(guān)系 大致是成反比 的,即安裝包越大,下載轉(zhuǎn)換率就越小。現(xiàn)在很多大型的 App 一般都會(huì)有一個(gè) Lite 版本的 App,這個(gè)也是出于下載轉(zhuǎn)化率方面的考慮。
2、渠道合作商的要求
此外,還有一個(gè)原因,當(dāng)我們的 App 做大之后,可能需要跟各個(gè)手機(jī)廠商合作預(yù)裝,這些 渠道合作商會(huì)對(duì)你的 App 做詳細(xì)的要求,只有達(dá)到相應(yīng)的要求后才允許你的 App 預(yù)裝到手機(jī)上。而且,越大的 App 其單價(jià)成本也會(huì)越高。所以,瘦身也是我們項(xiàng)目做大之后一定會(huì)遇到的一個(gè)問(wèn)題。
3、體積過(guò)大對(duì) App 性能的影響
- 安裝時(shí)間:安裝包越大,安裝時(shí)間越長(zhǎng)
- 運(yùn)行時(shí)內(nèi)存:Resource 資源、Library 以及 Dex 類加載都會(huì)占用應(yīng)用的一部分內(nèi)存。
- ROM 空間:安裝包越大,安裝后所占ROM空間越大。如果應(yīng)用的安裝包大小為 50MB,那么啟動(dòng)解壓之后很可能就已經(jīng)超過(guò) 100MB 了。
二、APK 組成
Android 項(xiàng)目最終會(huì)編譯成一個(gè) .apk 后綴的文件,實(shí)際上它就是一個(gè) 壓縮包。因此,它內(nèi)部還有很多不同類型的文件,這些文件,按照大小,共分為如下四類:
1、代碼相關(guān):
classes.dex,我們?cè)陧?xiàng)目中所編寫(xiě)的 java 文件,經(jīng)過(guò)編譯之后會(huì)生成一個(gè) .class 文件,而這些所有的 .class 文件呢,它最終會(huì)經(jīng)過(guò) dx 工具編譯生成一個(gè) classes.dex。
2、資源相關(guān):
res、assets、編譯后的二進(jìn)制資源文件 resources.arsc 和 清單文件 等等。res 和 assets 的不同在于 res 目錄下的文件會(huì)在 .R 文件中生成對(duì)應(yīng)的資源 ID,而 assets 不會(huì)自動(dòng)生成對(duì)應(yīng)的 ID,而是通過(guò) AssetManager 類的接口來(lái)獲取。此外,每當(dāng)在 res 文件夾下放一個(gè)文件時(shí),aapt 就會(huì)自動(dòng)生成對(duì)應(yīng)的 id 并保存在 .R 文件中,但 .R 文件僅僅只是保證編譯程序不會(huì)報(bào)錯(cuò),實(shí)際上在應(yīng)用運(yùn)行時(shí),系統(tǒng)會(huì)根據(jù) ID 尋找對(duì)應(yīng)的資源路徑,而 resources.arsc 文件就是用來(lái)記錄這些 ID 和 資源文件位置對(duì)應(yīng)關(guān)系 的文件。
3、So 相關(guān):
lib 目錄下的文件,這塊文件的優(yōu)化空間其實(shí)非常大。
4、META-INF:
它存放了應(yīng)用的簽名信息,不是我們的優(yōu)化方向,不做具體介紹。
根據(jù)apk內(nèi)的文件組成,我們可以得出我們的三大優(yōu)化方向:代碼(classes.dex)、資源(Assets、res、resources.arsc)、so庫(kù)。
三、APK分析
1.Android Studio 自帶的Analyze APK
Analyze APK 具有如下功能:
- 可以直觀地查看到 APK 的組成,比如大小、占比等等。
- 查看 dex 文件的組成,可以看到dex聲明多少個(gè)類,多少個(gè)方法。
- 對(duì)不同的 APK 進(jìn)行對(duì)比分析。
2.反編譯
- apktool工具:獲取資源文件,提取圖片文件,布局文件,還有一些XML的資源文件。
- dex2jar工具:將APK反編譯成Java源碼(將classes.dex轉(zhuǎn)化為jar文件)。
- jd-gui工具:查看dex2jar中轉(zhuǎn)換后的jar文件。
具體請(qǐng)參考:
Android APK反編譯教程
四、代碼體積優(yōu)化
1.ProGuard
在 Android SDK 里面集成了一個(gè)工具 — Proguard,它是一個(gè)免費(fèi)的 Java 類文件 壓縮、優(yōu)化、混淆、預(yù)先校驗(yàn) 的工具。它的主要作用大概可以概括為 兩點(diǎn),如下所示:
- 瘦身:它可以檢測(cè)并移除未使用到的類、方法、字段以及指令、冗余代碼,并能夠?qū)ψ止?jié)碼進(jìn)行深度優(yōu)化。最后,它還會(huì)將類中的字段、方法、類的名稱改成簡(jiǎn)短無(wú)意義的名字。
- 安全:增加代碼被反編譯的難度,一定程度上保證代碼的安全。
使用請(qǐng)參考:
Android 混淆介紹
2.Android Studio Lint 查找無(wú)用的代碼
ProGuard只是在編譯期幫我們刪除那些無(wú)用代碼,在項(xiàng)目里面這些代碼還是存在的。但是我們要更好地維護(hù)項(xiàng)目的話,一些無(wú)用的代碼始終還是需要?jiǎng)h除的。Lint工具就能夠幫我們找到那些無(wú)用的代碼,包括無(wú)用的類、方法和類屬性。
如圖,Code -> Inspect Code

直接點(diǎn)擊確認(rèn)

搜索結(jié)果如圖所示,在Java的Unused declaration 和 Kotlin 的 Unused symbol 里面就可以找到那些沒(méi)有用的類、方法和類屬性。不過(guò)對(duì)于那些被反射調(diào)用的類、方法或?qū)傩允菬o(wú)法檢測(cè)出來(lái)被調(diào)用的,這點(diǎn)需要注意一下。

3.三方庫(kù)處理
將圖片加載庫(kù)、網(wǎng)絡(luò)庫(kù)、數(shù)據(jù)庫(kù)以及其他基礎(chǔ)庫(kù)進(jìn)行統(tǒng)一,去掉冗余的庫(kù)。
同時(shí),在選擇第三方 SDK 的時(shí)候,我們可以將包大小作為選擇的指標(biāo)之一。在滿足我們的需求和性能要求的情況下,我們應(yīng)該盡可能地選擇那些比較小的庫(kù)來(lái)實(shí)現(xiàn)相同的功能。例如,對(duì)于圖片加載功能來(lái)說(shuō),Picasso、Glide、Fresco 它們都可以實(shí)現(xiàn),但是你引入 Fresco 之后會(huì)導(dǎo)致包大小增加很多,而 Picasso 卻只增加了不到 100kb,所以引入不同的三方 SDK 對(duì)包大小的影響是不一樣的。
如果我們引入三方庫(kù)的時(shí)候,可以只引入部分需要的代碼,而不是將整個(gè)包的代碼都引入進(jìn)來(lái)。很多庫(kù)的代碼結(jié)構(gòu)都設(shè)計(jì)的比較好,比如 Fresco,它將圖片加載的各個(gè)功能,如 webp、gif 功能進(jìn)行了剝離,它們都處于單個(gè)的庫(kù)當(dāng)中。如果我們只需要 Fresco 的 webp 功能,那我們可以將除 webp 之外的別的庫(kù)都給刪掉,這樣你引入的三方庫(kù)就很小了,包大小就降下來(lái)了。如果你引入的三方庫(kù) 沒(méi)有進(jìn)行過(guò)結(jié)構(gòu)剝離,就需要 修改源碼,只提取出來(lái)你需要的功能即可。

五、資源體積優(yōu)化
1、冗余資源優(yōu)化
使用 Lint 的 Remove Unused Resource
APK 的資源主要包括圖片、XML,與冗余代碼一樣,它也可能遺留了很多舊版本當(dāng)中使用而新版本中不使用的資源,這點(diǎn)在快速開(kāi)發(fā)的 App 中更可能出現(xiàn)。我們可以通過(guò)點(diǎn)擊右鍵,選中 Refactor,然后點(diǎn)擊 Remove Unused Resource => preview 可以預(yù)覽找到的無(wú)用資源,點(diǎn)擊 Do Refactor 可以去除冗余資源。
需要注意的,Android Lint 不會(huì)分析 assets 文件夾下的資源,因?yàn)?assets 文件可以通過(guò)文件名直接訪問(wèn),不需要通過(guò)具體的引用,Lint 無(wú)法判斷資源是否被用到。
使用 shrinkResources true 來(lái)開(kāi)啟資源壓縮
資源縮減只有在與代碼縮減配合使用時(shí)才能發(fā)揮作用。在代碼縮減器移除所有不使用的代碼后,資源縮減器便可確定應(yīng)用仍要使用的資源,當(dāng)您添加包含資源的代碼庫(kù)時(shí)尤其如此。您必須移除不使用的庫(kù)代碼,使庫(kù)資源變?yōu)槲匆觅Y源,因而可由資源縮減器移除。
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
2、移除未使用的備用資源
Gradle 資源縮減器只會(huì)移除未由應(yīng)用代碼引用的資源,這意味著,它不會(huì)移除用于不同設(shè)備配置的備用資源。如有必要,您可以使用 Android Gradle 插件的 resConfigs 屬性移除應(yīng)用不需要的備用資源文件。
以下代碼段展示了如何設(shè)置只保留英語(yǔ)和法語(yǔ)的語(yǔ)言資源:
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
3、圖片壓縮
對(duì)于圖片壓縮,我們可以在 tinypng 這個(gè)網(wǎng)站進(jìn)行圖片壓縮,但是如果 App 的圖片過(guò)多,一個(gè)個(gè)壓縮也是很麻煩的。因此,我們可以 使用 TinyPNG Image Optimizer 插件來(lái)對(duì)圖片進(jìn)行自動(dòng)化批量壓縮。TinyPNG Image Optimizer 插件一開(kāi)始使用時(shí)需要填寫(xiě) ApiKey, 申請(qǐng)入口如下 https://tinypng.com/developers

但是,需要注意的是,在 Android 的構(gòu)建流程中,AAPT 會(huì)使用內(nèi)置的壓縮算法來(lái)優(yōu)化 res/drawable/ 目錄下的 PNG 圖片,但這可能會(huì)導(dǎo)致本來(lái)已經(jīng)優(yōu)化過(guò)的圖片體積變大,因此,可以通過(guò)在 build.gradle 中 設(shè)置 cruncherEnabled 來(lái)禁止 AAPT 來(lái)優(yōu)化 PNG 圖片,代碼如下所示:
aaptOptions {
cruncherEnabled = false
}
4、使用針對(duì)性的圖片格式
首先,如果能用 VectorDrawable 來(lái)表示的話,則優(yōu)先使用 VectorDrawable;否則,看是否支持 WebP,支持則優(yōu)先用 WebP;如果也不能使用 WebP,則優(yōu)先使用 PNG,而 PNG 主要用在展示透明或者簡(jiǎn)單的圖片,對(duì)于其它場(chǎng)景可以使用 JPG 格式。簡(jiǎn)單來(lái)說(shuō)可以歸結(jié)為如下套路:
VD(純色icon)->WebP(非純色icon)->Png(更好效果) ->jpg(若無(wú)alpha通道)

注意點(diǎn):
- App的minSdkVersion高于14(Android 4.0+)的話,才可以選用WebP格式。
- 通過(guò)Android Studio 的 Vector Asset 可以把矢量圖轉(zhuǎn)換成 VectorDrawable,參考 Android Studio神器之Vector Asset
- 通過(guò)Android Studio 的 Conver to Webp 即可把png 轉(zhuǎn)換成 webp格式的圖片,使用方式:在Android Studio 中,把圖片資源選中,點(diǎn)擊鼠標(biāo)右鍵即可看到 Conver to Webp的選項(xiàng)。
5、資源混淆t
目前使用較新的Gradle 版本時(shí),發(fā)現(xiàn)打release包時(shí)資源已經(jīng)進(jìn)行了一定程度的混淆。大家可以直接更新到最新版本的Android Studio 版本和 gradle 版本來(lái)實(shí)現(xiàn)資源混淆。
AndResGuard目前已經(jīng)很久沒(méi)維護(hù)了,大家可以參考學(xué)習(xí)其原理。
6、盡量每張圖片只保留一份
比如說(shuō),我們統(tǒng)一只把圖片放到 xhdpi 這個(gè)目錄下,那么 在不同的分辨率下它會(huì)做自動(dòng)的適配,即 等比例地拉伸或者是縮小。
7、資源在線化
我們可以 將一些圖片資源放在服務(wù)器,然后 結(jié)合圖片預(yù)加載 的技術(shù)手段,這些 既可以滿足產(chǎn)品的需要,同時(shí)可以減小包大小。
六、so體積優(yōu)化
1、So 移除方案
So 是 Android 上的動(dòng)態(tài)鏈接庫(kù),在我們 Android 應(yīng)用開(kāi)發(fā)過(guò)程中,有時(shí)候 Java 代碼不能滿足需求,比如一些 加解密算法或者音視頻編解碼功能,這個(gè)時(shí)候就必須要通過(guò) C 或者是 C++ 來(lái)實(shí)現(xiàn),之后生成 So 文件提供給 Java 層來(lái)調(diào)用,在生成 So 文件的時(shí)候就需要考慮生成市面上不同手機(jī) CPU 架構(gòu)的文件。目前,Android 一共 支持7種不同類型的 CPU 架構(gòu),比如常見(jiàn)的 armeabi、armeabi-v7a、X86 等等。理論上來(lái)說(shuō),對(duì)應(yīng)架構(gòu)的 CPU 它的執(zhí)行效率是最高的,但是這樣會(huì)導(dǎo)致 在 lib 目錄下會(huì)多存放了各個(gè)平臺(tái)架構(gòu)的 So 文件,所以 App 的體積自然也就更大了。
因此,我們就需要對(duì) lib 目錄進(jìn)行縮減,我們 在 build.gradle 中配置這個(gè) abiFiliters 去設(shè)置 App 支持的 So 架構(gòu),其配置代碼如下所示:
defaultConfig {
ndk {
abiFilters "armeabi"
}
}
一般情況下,應(yīng)用都不需要用到 neon 指令集,我們只需留下 armeabi 目錄就可以了。因?yàn)?armeabi 目錄下的 So 可以兼容別的平臺(tái)上的 So,相當(dāng)于是一個(gè)萬(wàn)金油,都可以使用。但是,這樣 別的平臺(tái)使用時(shí)性能上就會(huì)有所損耗,失去了對(duì)特定平臺(tái)的優(yōu)化。
對(duì)于性能敏感的模塊,它使用到的 So,我們都放在 armeabi 目錄當(dāng)中隨著 Apk 發(fā)出去,然后我們?cè)诖a中來(lái)判斷一下當(dāng)前設(shè)備所屬的 CPU 類型,根據(jù)不同設(shè)備 CPU 類型來(lái)加載對(duì)應(yīng)架構(gòu)的 So 文件。這里我們舉一個(gè)小栗子,比如說(shuō)我們 armeabi 目錄下也加上了 armeabi-v7 對(duì)應(yīng)的 So,然后我們就可以在代碼當(dāng)中做判斷,如果你是 armeabi-v7 架構(gòu)的手機(jī),那我們就直接加載這個(gè) So,以此達(dá)到最佳的性能,這樣包體積其實(shí)也沒(méi)有增加多少,同時(shí)也實(shí)現(xiàn)了高性能的目的,比如 微信和騰訊視頻 App 里面就使用了這種方式。

七、其他方案
1、插件化
我們可以使用插件化的手段 對(duì)代碼結(jié)構(gòu)進(jìn)行調(diào)整,如果我們 App 當(dāng)中的每一個(gè)功能都是一個(gè)插件,并且都是可以從服務(wù)器下發(fā)下來(lái)的,那 App 的包體積肯定會(huì)小很多。
2、業(yè)務(wù)梳理
我們需要回顧過(guò)去的業(yè)務(wù),合理地去評(píng)估并刪除無(wú)用或者低價(jià)值的業(yè)務(wù)。簡(jiǎn)單來(lái)說(shuō)就是刪減無(wú)用或低價(jià)值的功能。
3、轉(zhuǎn)變開(kāi)發(fā)模式
如果所有的功能都不能移除,那就可能需要去轉(zhuǎn)變開(kāi)發(fā)模式,比如可以更多地 采用 H5、小程序 這樣開(kāi)發(fā)模式。
八、包體積監(jiān)控
包體積的監(jiān)控,主要可以從如下 三個(gè)緯度 來(lái)進(jìn)行:
- 大小監(jiān)控:通常是記錄當(dāng)前版本與上一個(gè)或幾個(gè)版本直接的變化情況,如果當(dāng)前版本體積增長(zhǎng)較大,則需要分析具體原因,看是否有優(yōu)化空間。通過(guò)Android Studio 自帶的Analyze APK就可以看到報(bào)體積和各個(gè)組成部分的體積大小。
- 依賴監(jiān)控:包括Jar、aar 依賴。
- 規(guī)則監(jiān)控:我們可以把包體積的監(jiān)控抽象為無(wú)用資源、大文件、重復(fù)文件、R 文件等這些規(guī)則。
包體積的 大小監(jiān)控 和 依賴監(jiān)控 都很容易實(shí)現(xiàn),而要實(shí)現(xiàn) 規(guī)則監(jiān)控 卻得花不少功夫,推薦使用 Matrix 中的 ApkChecker 來(lái)實(shí)現(xiàn)包體積的規(guī)則監(jiān)控。
九、總結(jié)
apk體積的優(yōu)化方向主要是代碼體積優(yōu)化、資源體積優(yōu)化和so體積優(yōu)化,本文總結(jié)各部分的一些優(yōu)化方式,都是比較基本的優(yōu)化方式,能實(shí)現(xiàn)上面提到的點(diǎn),我們的apk體積優(yōu)化已經(jīng)基本ok了。如果需要有需要更加深入的,可以參考下面所列出的本文所參考到的文章。
參考:
縮減、混淆處理和優(yōu)化應(yīng)用(官方文檔)
Android包體積優(yōu)化(常規(guī)、進(jìn)階、極致)
深入探索 Android 包體積優(yōu)化(匠心制作-上)
深入探索 Android 包體積優(yōu)化(匠心制作-下)