貨拉拉Android 包體積優化實踐

語言: CN / TW / HK

作者簡介
muye,貨拉拉客戶端架構師,貨拉拉App Android端技術負責人,在Android App效能優化、穩定性提升等方向有豐富經驗

背景介紹

為什麼要做包體積優化?主要出於以下幾方面的考慮

  1. 下載轉化率
    (1)很多應用市場流量保護限制是40M
    (2)很多大型 App 一般都會有一個 Lite 版本的 App,也是出於下載轉化率方面的考慮

  2. 對app效能的影響
    (1)安裝時間:比如 檔案拷貝、Library 解壓,並且,在編譯 ODEX 的時候,特別是對於 Android 5.0 和 6.0 系統來說,耗費的時間比較久,而 Android 7.0 之後有了 混合編譯,所以還可以接受。最後,App 變大後,其 簽名校驗 的時間也會變長。
    (2)執行時記憶體:Resource 資源、Library 以及 Dex 類載入都會佔用應用的一部分記憶體。
    (3)ROM 空間:如果應用的安裝包大小為 50MB,那麼啟動解壓之後很可能就已經超過 100MB 了

  3. CDN流量費用增加
    安裝包體積越大,單個apk下載流量越大

優化思路分析

apk主要由以下4個部分構成
(1)程式碼相關的dex檔案
(2)資源相關的resources.arsc和清單檔案等
(3)so相關的lib目錄下的檔案
(4)系統簽名檔案

所以我們的優化思路如下:

  1. 從apk整體優化
    (1)外掛化
    動態載入安裝包中的部分程式碼、資源、so
    (2)動態資源載入
    動態載入安裝包中的資源、so

  2. 程式碼相關的dex檔案優化
    (1)程式碼混淆
    使用更短的混淆欄位來混淆原始的類名、方法名、變數名
    (2)刪除未使用的程式碼
    完全沒使用的程式碼、只使用了一小部分卻引入了整體功能的程式碼
    (3)刪除重複的程式碼
    完全重複的程式碼、部分方法重複的程式碼
    (4)sdk優化
    重複功能的sdk(比如圖片載入庫)、只使用了部分功能卻引入了完整的sdk
    (5)dex壓縮
    使用更高壓縮率的演算法、可以替換成常量的位元組碼
    (6)多dex關聯優化
    減少跨Dex呼叫的冗餘資訊
    (7)位元組碼優化
    方法內聯、常量內聯、優化多餘賦值指令、getter和setter方法內聯、刪除日誌等

  3. 資源相關的檔案優化
    (1)shrinkResources
    將專案中沒有使用的圖片、xml資原始檔使用系統自帶的資源替換
    (2)刪除未使用的資源
    (3)刪除重複資源
    (4)AndResGuard-資源混淆
    使用更短的混淆名來混淆原始的資源名
    (5)屬性程式碼替換shape xml檔案
    (6)語言資源優化
    去掉多餘的國際語言資源
    (7)圖片資源優化
    圖片格式優化、圖片壓縮、圖片解析度優化
    (8)本地圖片轉網圖
    編譯時將本地的圖片從apk中刪除,改成網路動態載入的方式

  4. so相關的檔案優化
    移除多餘的so架構、移除除錯符號

工欲善其事必先利其器,為了實現我們的優化效果,我們引入和開發部分工具、外掛來幫助我們做apk、程式碼、資源的相關分析

貨拉拉Android 包體積優化思維導圖

image.png

APK構成

  1. APK構成

image.png

apk各個部分的詳細資訊:

image.png

  1. dex簡介

Dex 是 Android 系統的可執行檔案,包含 應用程式的全部操作指令以及執行時資料。因為 Dalvik 是一種針對嵌入式裝置而特殊設計的 Java 虛擬機器,所以 Dex 檔案與標準的 Class 檔案在結構設計上有著本質的區別。當 Java 程式被編譯成 class 檔案之後,還需要使用 dx 工具將所有的 class 檔案整合到一個 dex 檔案中,這樣 dex 檔案就將原來每個 class 檔案中都有的共有資訊合成了一體,這樣做的目的是 保證其中的每個類都能夠共享資料,這在一定程度上 降低了資訊冗餘,同時也使得 檔案結構更加緊湊。與傳統 jar 檔案相比,Dex 檔案的大小能夠縮減 50% 左右

image.png

  1. apk打包過程

(1)簡化版

image.png

(2)詳細版

image.png

Apk分析工具

  1. zip解壓

直接把apk字尾改成zip,然後解壓縮
解壓之後可以看到apk的各個組成部分

image.png

  1. Android Studio自帶的Analyze APK

直接在Android Studio中點選開啟apk檔案即可,開啟之後可以看到各個部分的大小

image.png

還可以做apk的對比分析,可以看到新舊版本的體積對比,更直接客觀的看出新版本哪部分體積增加了及哪部分體積減少了

image.png

另外,還可以點開每個dex檔案,檢視裡面的具體類資訊

  1. 反編譯工具APKTool

APKTool主要包含三個部分:apktool、dex2jar、jd-gui,作用分別如下:

  • apktool
    作用:資原始檔獲取,可以提取出圖片檔案和佈局檔案進行使用檢視

  • dex2jar 作用:將apk反編譯成java原始碼(classes.dex轉化成jar檔案)

  • jd-gui 作用:檢視APK中classes.dex轉化成出的jar檔案,即原始碼檔案

反編譯命令如下:

java -jar apktool_2.3.4.jar apktool d app-release.apk

image.png

反編譯後可以得到smali碼,通過jd-gui可以開啟如下:

image.png

  1. class-shark

(1)android-classshark 是一個 面向 Android 開發人員的獨立二進位制檢查工具,它可以 瀏覽任何的 Android 可執行檔案,並且檢查出資訊,比如類的介面、成員變數等等,此外,它還可以支援多種格式,比如說 APK、Jar、Class、So 以及所有的 Android 二進位制檔案如清單檔案等等

(2)傳送門

https://github.com/google/android-classyshark

(3)使用方式

雙擊開啟 ClassShark.jar,拖動我們的 APK 到它的工作空間即可。接下來,我們就可以看到 Apk 的分析介面了,這裡我們點選 classes 下的 classes.dex,在分析介面 左邊 可以看到該 dex 的方法數和檔案大小,並且,最下面還顯示出了該 dex 中包含有 Native Call 的類

image.png

(4)點選左上角的 Methods count 還可以切換到 方法數環形圖示統計介面,我們不僅可以 直觀地看到各個包下的方法數和相對大小,還可以看到各個子包下的方法數和相對大小

  1. nimbledroid

(1)nibledroid 是美國哥倫比亞大學的博士創業團隊研發出來的分析 Android App 效能指標的系統,分析的方式有靜態和動態兩種方式

(2)傳送門

https://nimbledroid.com/

(3)靜態分析:可以分析出APK安裝包中大檔案排行榜,Dex 方法數和知名第三方 SDK 的方法數及佔程式碼整體的比例

(4)動態分析:可以給出 冷啟動時間, 列出 Block UI 的具體方法, 記憶體佔用, 以及 Hot Methods, 從這些分析報告中, 可以 定位出具體的優化點

  1. ApkChecker

(1)簡介

ApkChecker是微信APM系統Matrix中的一個針對android安裝包的分析檢測工具

針對android安裝包的分析檢測工具,根據一系列設定好的規則檢測apk是否存在特定的問題,並輸出較為詳細的檢測結果報告,用於分析排查問題以及版本追蹤。

(2)傳送門

https://github.com/Tencent/matrix/wiki/Matrix-Android-ApkChecker

(3)怎麼使用

Matrix-ApkChecker以一個jar包的形式提供使用,通過命令列執行 java -jar ApkChecker.jar 即可執行

(4)能統計啥

fileSize 列出超過一定大小的檔案,可按檔案字尾過濾,並且按檔案大小排序

--min 檔案大小最小閾值,單位是KB

--order 按照檔案大小升序(asc)或者降序(desc)排列

--suffix 按照檔案字尾過濾,使用","作為多個檔案字尾的分隔符

countMethod 統計方法數

group 輸出結果按照類名(class)或者包名(package)來分組

checkResProguard 檢查是否經過了資源混淆(AndResGuard)

findNonAlphaPng 發現不含alpha通道的png檔案

min png檔案大小最小閾值,單位是KB

checkMultiLibrary 檢查是否包含多個ABI版本的動態庫

uncompressedFile 發現未經壓縮的檔案型別(即該型別的所有檔案都未經壓縮)

suffix 按照檔案字尾過濾,使用","作為多個檔案字尾的分隔符

countR 統計apk中包含的R類以及R類中的field count

duplicatedFile 發現冗餘的檔案,按照檔案大小降序排序

checkMultiSTL 檢查是否有多個動態庫靜態連結了STL

toolnm nm工具的路徑

unusedResources 發現apk中包含的無用資源

rTxt R.txt檔案的路徑(如果在全域性引數中給定了--input,則可以省略)

ignoreResources 需要忽略的資源,使用","作為多個資源名稱的分隔符

unusedAssets 發現apk中包含的無用assets檔案

ignoreAssets 需要忽略的assets檔案,使用","作為多個檔案的分隔符

unstrippedSo 發現apk中未經裁剪的動態庫檔案

(5)檢測結果示例

image.png

程式碼分析工具

1、Proguard

proguard程式碼混淆時會生成dump、mapping、seeds、usage4個檔案,如下:

以seeds.txt為例,會列出當前混淆規則下沒有被混淆的類和成員,為後續進一步混淆優化提供指導

2、lint分析外掛

Android Studio自帶lint分析外掛,以分析未使用宣告為例

Analyze -> Run Inspection by Name -> unused declaration

此外還可以分析專案中未使用的class和resources資源等

3、自定義lint分析外掛

除了AS自帶的lint外掛,還可以自定義lint外掛,自定義lint外掛可以掃描以下幾類檔案

(1)JavaScanner / JavaPsiScanner / UastScanner:掃描 Java 原始檔

(2)XmlScanner:掃描 XML 檔案

(3)ClassScanner:掃描 class 檔案

(4)BinaryResourceScanner:掃描二進位制資原始檔

(5)ResourceFolderScanner:掃描資原始檔夾

(6)GradleScanner:掃描 Gradle 指令碼

(7)OtherFileScanner:掃描其他型別檔案

以掃描java原始檔為例,以下掃描專案中所有的Log日誌程式碼:

4、自定義gradle外掛

(1)自定義gradle外掛的三種方式

Build script:在build.gradle構建指令碼中直接使用,只能在本檔案內使用;

buildSrc project:新建一個名為buildSrc的Module使用,只能在本專案中使用;

Standalone project:在獨立的Module中使用,可以釋出到本地或者遠端倉庫供其他專案使用。

(2)支援的語言

可以使用多種語言來實現Gradle外掛,其實只要最終被編譯為JVM位元組碼的都可以,常用的有GroovyJavaKotlin

(3)Build script示例

(4)buildSrc project和Standalone project使用

本質上實現方式一樣

首先自定義外掛類implements Plugin

實現void apply(Project project)方法註冊自定義的Transform類;

然後自定義類extends Transform,在transform()方法中實現自定義邏輯,例如可以自定義

ClassVisitor類訪問和修改類的相關屬性,自定義MethodVisitor類訪問和修改方法的相關屬性

5、coverage外掛

(1)簡介

coverage外掛是由位元組跳動開源的線上無用程式碼分析工具

(2)原理

由於程式碼設計不合理以及keep規則限制等原因,靜態程式碼檢查無法找出所有的無用程式碼。

我們可以從使用者的角度去分析,對每個類插樁,執行時將資訊上報到伺服器。基於大量使用者上報,使用者沒有用到的類可以被定義為無用類。

在抖音專案中,我們發現了1/6的無用類,不包含其引用的資源,共計3M(dex大小20M),如果能全部刪除,將減少5%包大小

(3)傳送門

https://github.com/bytedance/ByteX/blob/master/coverage/README-zh.md

6、pmd檢測重複程式碼

(1)簡介

PMD是一個靜態原始碼分析器。它找到常見的程式設計缺陷,如未使用的變數,空的catch塊,不必要的物件建立等等。它主要關注Java和Apex,但支援其他六種語言。

PMD具有許多內建檢查(在PMD術語,規則中),這些檢查在規則參考中針對每種語言進行了記錄。我們還支援廣泛的API來編寫您自己的規則,您可以使用Java或作為自包含的XPath查詢來執行。

在整合到構建過程中時,PMD最有用。

(2)支援的4種執行方式

作為Maven的目標

作為Ant任務

作為Gradle任務

從命令列

(3)傳送門

https://pmd.sourceforge.io/pmd-5.4.1/usage/cpd-usage.html

(4)檢測結果示例

使用命令列方式:

./run.sh cpd --language java --minimum-tokens 100 --files /Users/xxxx/Work/code/DeliciousFood/Classes > ~/Desktop/codeCheck.txt

7、Simian檢測重複程式碼

(1)Simian是一個可跨平臺使用的重複程式碼檢測工具,能夠檢測程式碼片段中除了空格、註釋及換行外的內容是否完全一致,且支援的語言包括:

  • Java
  • C#
  • C++
  • C
  • Objective-C
  • JavaScript (ECMAScript)
  • COBOL, ABAP
  • Ruby
  • Lisp
  • SQL
  • Visual Basic
  • Groovy
  • Swift

(2)傳送門

http://www.harukizaemon.com/simian/get_it_now.html

(3)simian檢測結果示例

程式碼體積優化

1、程式碼優化小建議

(1)時刻保持良好的程式設計習慣,去除重複或者不用的程式碼,慎用第三方庫,選用體積小的第三方SDK

(2)儘量不要使用自動生成的程式碼的sdk 比如butterknife和viewbinding、databinding

(3)減少ENUM的使用,避免使用列舉 單個列舉會使應用的 classes.dex 檔案增加大約 1.0 到 1.4KB 的大小 請考慮使用 @IntDef 註釋

2、程式碼混淆Proguard

(1)作用

混淆器的 作用 不僅僅是 保護程式碼,它也有 精簡編譯後程序大小 的作用,其 通過縮短變數和函式名以及丟失部分無用資訊等方式,能使得應用包體積減小。

i 瘦身:它可以檢測並移除未使用到的類、方法、欄位以及指令、冗餘程式碼,並能夠對位元組碼進行深度優化。最後,它還會將類中的欄位、方法、類的名稱改成簡短無意義的名字。

ii 安全:增加程式碼被反編譯的難度,一定程度上保證程式碼的安全。

(2)程式碼混淆形式

程式碼混淆的形式主要有 三種,如下所示:

i:將程式碼中的各個元素,比如類、函式、變數的名字改變成無意義的名字。例如將 hasValue 轉換成單個的字母 a。這樣,反編譯閱讀的人就無法通過名字來猜測用途。

ii:重寫 程式碼中的 部分邏輯,將它變成 功能上等價,但是又 難以理解 的形式。比如它會 改變迴圈的指令、結構體。

iii:打亂程式碼的格式,比如多加一些空格或刪除空格,或者將一行程式碼寫成多行,將多行程式碼改成一行。

(3)Proguard踩坑經驗

i:如果專案首次混淆,可能需要全域性掃描所有的類和包名,可以先全量keep,然後再逐包放開混淆

ii:EventBus的java、kotlin的onEvent的坑

iii:沒有序列化的內部屬性類也需要keep

3、sdk優化

(1)sdk接入標準

i:不要為了某個小功能就隨意引入sdk,可以考慮原始碼接入

ii:郵件通知稽核sdk是否接入

(2)選擇第三方 SDK 的時候,我們可以將包大小作為選擇的指標之一,我們應該 儘可能地選擇那些比較小的庫來實現相同的功能

(3)不要選擇重複功能的sdk,如果有,可以考慮去掉其他的 Picasso、Glide、Fresco

(4)某些庫支援部分功能分離,不需要引入整個包 比如 Fresco,它將圖片載入的各個功能,如 webp、gif 功能進行了剝離,它們都處於單個的庫當中

4、刪除重複的程式碼

可以使用上面的pmd和simian工具掃描出重複的程式碼

5、刪除未使用的程式碼

可以使用上面的coverage外掛來輔助統計出未使用的程式碼

6、dex壓縮

(1)內聯R Field

通過內聯 R Field 來進一步對程式碼進行瘦身,此外,它也解決了 R Field 過多導致 MultiDex 65536 的問題。要想實現內聯 R Field,我們需要 通過 Javassist 或者 ASM 位元組碼工具在構建流程中內聯 R Field

實現原理:

android 中的 R 檔案,除了 styleable 型別外,所有欄位都是 int 型變數/常量,且在執行期間都不會改變。所以可以在編譯時,記錄 R 中所有欄位名稱及對應值,然後利用 ASM 工具遍歷所有 Class,將除 R$styleable.class 以外的所有 R.class 刪除掉,並且在引用的地方替換成對應的常量

使用工具:ThinRPlugin(美麗說團隊開源)

外掛部分實現:

image.png (2)dex壓縮--XZ Utils

i:XZ Utils 是具有高壓縮率的免費通用資料壓縮軟體,它同 7-Zip 一樣,都是 LZMA Utils 的後繼產品,內部使用了 LZMA/LZMA2 演算法。LZMA 提供了高壓縮比和快速解壓縮,因此非常適合嵌入式應用

ii:缺點 壓縮 Dex 的方式,那麼首次生成 ODEX 的時間可能就會超過1分鐘

iii:傳送門

https://tukaani.org/xz/

7、多dex關聯優化-ReDex

(1)背景

Dex 的方法數就會超過65536個,因此,必須採用 mutildex 進行分包,但是此時每一個 Dex 可能會呼叫到其它 Dex 中的方法,這種 跨 Dex 呼叫的方式會造成許多冗餘資訊 (1)多餘的 method id:跨 Dex 呼叫會導致當前dex保留被呼叫dex中的方法id,這種冗餘會導致每一個dex中可以存放的class變少,最終又會導致編譯出來的dex數量增多,而dex資料的增加又會進一步加重這個問題。 (2)其它跨dex呼叫造成的資訊冗餘:除了需要多記錄被呼叫的method id之外,還需多記錄其所屬類和當前方法的定義資訊,這會造成 string_ids、type_ids、proto_ids 這幾部分資訊的冗餘。

(2) ReDex方案

為了減少跨 Dex 呼叫的情況,我們必須 儘量將有呼叫關係的類和方法分配到同一個 Dex 中。但是各個類相互之間的呼叫關係是非常複雜的,所以很難做到最優的情況。所幸的是,ReDex 的 CrossDexDefMinimizer 類分析了類之間的呼叫關係,並 使用了貪心演算法去計算區域性的最優解(編譯效果和dex優化效果之間的某一個平衡點)。使用 "InterDexPass" 配置項可以把互相引用的類儘量放在同個 Dex,增加類的 pre-verify,以此提升應用的冷啟動速度

(3)ReDex的5個功能

Interdex:類重排和檔案重排、Dex 分包優化。其中對於類重排和檔案重排,Google 在 Android 8.0 的時候引入了 Dexlayout,它是一個用於分析 dex 檔案,並根據配置檔案對其進行重新排序的庫。與 ReDex 類似,Dexlayout 通過將經常一起訪問的部分 dex 檔案集中在一起,程式可以因改進檔案位置從而擁有更好的記憶體訪問模式,以節省 RAM 並縮短啟動時間。不同於ReDex的是它使用了執行時配置資訊對 Dex 檔案的各個部分進行重新排序。因此,只有在應用執行之後,並在系統空閒維護的時候才會將 dexlayout 整合到 dex2oat 的裝置進行編譯

Oatmeal:直接生成 Odex 檔案

StripDebugInfo:去除 Dex 中的 Debug 資訊

原始碼中 access-marking 模組:刪除 Java access 方法

原始碼中 type-erasure 模組:型別擦除。

(4)傳送門

https://fbredex.com/docs/installation

資源體積優化

1、圖片格式優化

(1)如果能用VectorDrawable來表示的話優先使用VectorDrawable,如果支援WebP則優先用WebP,而PNG主要用在展示透明或者簡單的圖片,而其它場景可以使用JPG格式。針對每種圖片格式也有各類的優化手段和優化工具。

(2)使用向量圖片

可以使用向量圖形來建立獨立於解析度的圖示和其他可伸縮圖片。使用向量圖片能夠有效的減少App中圖片所佔用的大小,向量圖形在Android中表示為VectorDrawable物件。 使用VectorDrawable物件,100位元組的檔案可以生成螢幕大小的清晰影象,但系統渲染每個VectorDrawable物件需要大量的時間,較大的影象需要更長的時間才能出現在螢幕上。 因此只有在顯示小影象時才考慮使用向量圖形。有關使用VectorDrawable的更多資訊,請參閱 Working with Drawables

(3)使用WebP

如果App的minSdkVersion高於14(Android 4.0+)的話,可以選用WebP格式,因為WebP在同畫質下體積更小(WebP支援透明度,壓縮比比JPEG更高但顯示效果卻不輸於JPEG,官方評測quality引數等於75均衡最佳), 可以通過PNG到WebP轉換工具來進行轉換。

2、圖片壓縮

(1)png格式圖片可以在tinyPng網站上壓縮,或者使用pngcrushpngquantzopflipng等工具壓縮

而不會丟失影象質量。所有這些工具都可以減少PNG檔案大小,同時保持影象質量。

pngcrush工具特別有效:此工具在PNG過濾器和zlib(Deflate)引數上迭代,使用過濾器和引數的每個組合來壓縮影象。然後選擇產生最小壓縮輸出的配置

(2)JPEG檔案,可以使用packJPGguetzli等工具將JPEG檔案壓縮的更小,這些工具能夠在保持圖片質量不變的情況下,把圖片檔案壓縮的更小。guetzli工具更是能夠在圖片質量不變的情況下,將檔案大小降低35%

3、開啟資源壓縮shrinkResources

(1)Android的編譯工具鏈中提供了一款資源壓縮的工具,可以通過該工具來壓縮資源,如果要啟用資源壓縮,可以在build.gradle檔案中將shrinkResources true

(2)需要注意的是,Android構建工具是通過ResourceUsageAnalyzer來檢查哪些資源是無用的,

當檢查到無用的資源時會把該資源替換成預定義的版本。主要是針對 .png、.9.png、.xml 提供了 TINY_PNG、TINY_9PNG、TINY_XML 這 3 個 byte 陣列的預定義版本。資源壓縮工具預設是採用 安全壓縮模式 來執行,可以通過開啟 嚴格壓縮模式 來達到 更好的瘦身效果

4、語言資源優化

語言資源優化 讓構建工具移除指定語言之外的所有資源(可以刪除sdk裡面的語言資源) resConfigs "zh", "zh-rCN"

5、圖片解析度優化

根據專案實際需要,大部分圖片可以只保留一套xxhdpi圖片

6、屬性程式碼替代shape xml

(1)背景

專案中為了滿足ui需求,使用了大量android shape來生成各式各樣的背景。這些背景大多數只有圓角,描邊,填充色等資訊不一樣,但是種類繁多,無法相容,需要我們使用大量的xml檔案來生產多種多樣的背景,目前專案中多達數百個

(2)解決思路

i:自定義HllRoundBackground類來構建一個android 原生GradientDrawable來表示背景shape

ii:自定義屬性來表示常用的android shape屬性,包含圓角,填充色,描邊,根據狀態改變填充顏色,描邊顏色,字型顏色,以及漸變色屬性

iii:繼承android原生LayoutInflater.Factory2類,用它來生成View,並檢查該View上是否有自定義屬性,如果有嘗試生成背景,並設定到該View上。把該Factory注入到系統中,必要的時候,由我們代替系統的LayoutInflater建立View

(3)程式碼設計方案

image.png

(4)使用示例

<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="20元" app:hll_corners_radius = "6dp" app:hll_stroke_width = "1dp" app:hll_solid_normal_color = "@color/white" app:hll_solid_selected_color = "@color/color_0dff6600" app:hll_stroke_normal_color = "@color/gray_15_percent" app:hll_stroke_selected_color = "@color/color_ff6600"/>

7、資源混淆-AndResGuard

(1) AndResGuard方案

直接處理apk. 不依賴原始碼,不依賴編譯過程,僅僅輸入一個安裝包,得到一個混淆包

image.png

(2)AndResGuard處理流程

i:resources.arsc:它記錄了資原始檔的名稱與路徑,使用混淆後的短路徑 res/s/a,可以減少檔案的大小。

ii:metadata 簽名檔案:簽名檔案 MANIFEST.MF 與 CERT.SF 需要記錄所有檔案的路徑以及它們的雜湊值,使用短路徑可以減少這兩個檔案的大小。

iii:ZIP 檔案:ZIP 檔案格式裡面通過其索引記錄了每個檔案 Entry 的路徑、壓縮演算法、CRC、檔案大小等等資訊。短路徑的優化減少了記錄檔案路徑的字串大小

AndResGuard工作流程圖

image.png

(3)傳送門

https://github.com/shwenzhang/AndResGuard

(4)與7z極限壓縮

(5)AndResGuard混淆後的資源名

image.png

(6)AndResGuard踩坑經驗

i:資源混淆之白名單

程式碼掃描呼叫getIdentifier()方法的地方

ii:開啟7zip壓縮之後會影響圖片載入速度,會對app啟動速度有點影響

8、刪除重複資源

可以使用上面的ApkChecker工具掃描出apk中重複的資源

9、刪除未使用資源

(1)可以使用上面的ApkChecker工具掃描出apk中沒有使用的資源

(2)也可以使用Android Studio自帶的lint外掛掃描專案中沒有使用的資源

Analyze -> Run Inspection by Name -> unused Resources

其他包體積優化方案

1、資源動態載入方案

(1)原理

把一些使用頻率相對低一些的資源不打包進apk,需要的時候在下載到本地進行使用(這些資源可能包括動畫檔案、字型檔案、so庫、zip壓縮包等)

(2)資源動態載入架構圖

image.png

(3)部分類UML設計

image.png

(4)資源動態配置示例

(5)動態so載入

i:正常so載入流程

安裝app的時候,PMS會把指定架構的so庫,拷貝到 data/data/[包名]/lib 下面

啟動app的時候,會把系統的so資料夾,以及 安裝包的so資料夾位置 給 BaseDexClassLoader 中的屬性DexPathList 下面屬性的 nativeLibraryDirectories 和 systemNativeLibraryDirectories 兩個File集合

呼叫及使用 呼叫:System.loadLibrary("xxx")

ii:動態載入so方案

System.loadLibrary()和System.load()最後都會呼叫DexPathList 的 findLibrary(), 通過 DexPathList 中的 nativeLibraryDirectories 和systemNativeLibraryDirectories兩個資料夾集合,生成一個NativeLibraryElement[],然後從這裡面找對應的so,返回全路徑

hook了DexPathList 中的 nativeLibraryDirectories,在這個資料夾集合中又新增一個自定義的資料夾

流程圖如下:

image.png

2、本地圖片轉網圖

(1)原理

編譯時 (1)上傳圖片 (2)刪除圖片原始檔 (3)儲存連結資訊

執行時 (1)解析連結資訊 (2)Hook Android Drawable圖片載入流程 (3)自定義Drawable,觸發網路圖片下載,還原系統的Drawable圖片繪製流程

(2)aapt流程圖

(3)本地圖片轉網圖流程圖

(4)編譯時刪除本地圖片

(5)執行時載入圖片

3、位元組碼優化Bytex

(1)Bytex簡介

ByteX位元組跳動團隊開發的一個基於gradle transform api和ASM的位元組碼外掛平臺(或許,你可以把它當成一個有無限個插頭的插座?)

目前集成了若干個位元組碼外掛,每個外掛完全獨立,既可以脫離ByteX這個宿主而獨立存在,又可以自動整合到宿主和其它外掛一起整合為一個單獨的Transform。外掛和外掛之間,宿主和外掛之間的程式碼是完全解耦的(有點像元件化),這使得ByteX在程式碼上擁有很好的可拓展性,新外掛的開發將會變得更加簡單高效

(2)傳送門

ByteX/README_zh.md at master · bytedance/ByteX

(3)位元組碼優化功能

i:優化多餘賦值指令 (field-assign-opt-plugin)

編譯期間去除程式碼中不必要或者重複的賦值(預設值)程式碼,在虛擬機器例項化時分配的記憶體中預設會給予預設值,所以程式碼中的預設值是多餘的,如下:

private boolean aBoolean = false; private byte aByte = 0; private short aShort = 0; private char aChar = '\u0000'; private int anInt = 0; private float aFloat = 0f; private double aDouble = 0d; private long aLong = 0l;

ii:刪除某些方法呼叫 (method-call-opt-plugin)

比如我們的除錯日誌Log.d()只是在開發除錯階段使用,釋出包完全不需要此類程式碼

iii:常量內聯(const-inline-plugin)

編譯期間內聯並優化掉專案中的編譯期間常量欄位,外掛將對編譯期常量的運算(對應GETFIELD指令)進行內聯操作(對應LDC指令),然後將對應的欄位進行刪除優化。外掛會對可能的反射的程式碼進行分析,對於直接使用反射方式獲取執行時常量欄位進行忽略優化處理。

4、so包瘦身

(1)移除多餘的so架構

defaultConfig { ndk { abiFilters "armeabi" } }

i:一般應用都不需要用到 neon 指令集,我們只需留下 armeabi 目錄就可以了。因為 armeabi 目錄下的 So 可以相容別的平臺上的 So

ii:缺點:別的平臺使用時效能上就會有所損耗,失去了對特定平臺的優化

(2)移除除錯符號

使用 Android NDK 中提供的 arm-eabi-strip 工具從原生庫中移除不必要的除錯符號

5、Buck-刪除 Native Library 中無用的匯出 symbol

(1)Buck作用

分析程式碼中的 JNI 方法以及不同 Library 庫的方法呼叫,然後找出無用的 symbol 並刪除,這樣 Linker 在編譯的時候也會把 symbol 對應的無用程式碼給刪除。在 Buck 有 NativeRelinker 這個類,它就實現了這個功能,其 類似於 Native Library 的 ProGuard Shrinking 功能

(2)使用

刪除 Native Library 中無用的匯出 symbol 使用facebook的Buck庫,Buck有 NativeRelinker 這個類,可以刪除 Native Library 中無用的匯出 symbol,其 類似於 Native Library 的 ProGuard Shrinking 功能。

(3)傳送門

https://github.com/facebook/buck

外掛化

1、DL 動態載入框架 ( 2014 年底)

基於代理的方式實現外掛框架,當啟動外掛元件時,首先啟動一個代理元件,然後通過這個代理元件來構建,啟動外掛元件

支援的功能

(1)plugin無需安裝即可由宿主調起。

(2)支援用R訪問plugin資源

(3)plugin支援Activity和FragmentActivity(未來還將支援其他元件)

(4)基本無反射呼叫

(5)外掛安裝後仍可獨立執行從而便於除錯

(6)支援3種plugin對host的呼叫模式:

無呼叫(但仍然可以用反射呼叫)。

部分呼叫,host可公開部分介面供plugin呼叫。 這前兩種模式適用於plugin開發者無法獲得host程式碼的情況。

完全呼叫,plugin可以完全呼叫host內容。這種模式適用於plugin開發者能獲得host程式碼的情況。

(7)只需引入DL的一個jar包即可高效開發外掛,DL的工作過程對開發者完全透明

傳送門:

https://github.com/singwhatiwanna/dynamic-load-apk

2、DroidPlugin ( 2015 年 8 月)

360 手機助手實現的一種外掛化框架,它可以直接執行第三方的獨立 APK 檔案,完全不需要對 APK 進行修改或安裝。一種新的外掛機制,一種免安裝的執行機制,是一個沙箱

功能:

(1)外掛APK完全不需做任何修改,可以獨立安裝執行、也可以做外掛執行。要以外掛模式執行某個APK,你無需重新編譯、無需知道其原始碼。

(2)外掛的四大元件完全不需要在Host程式中註冊,支援Service、Activity、BroadcastReceiver、ContentProvider四大元件

(3)外掛之間、Host程式與外掛之間會互相認為對方已經"安裝"在系統上了。

(4)API低侵入性:極少的API。HOST程式只是需要一行程式碼即可整合Droid Plugin

(5)超強隔離:外掛之間、外掛與Host之間完全的程式碼級別的隔離:不能互相呼叫對方的程式碼。通訊只能使用Android系統級別的通訊方法。

(6)支援所有系統API

(7)資源完全隔離:外掛之間、與Host之間實現了資源完全隔離,不會出現資源竄用的情況。

(8)實現了程序管理,外掛的空程序會被及時回收,佔用記憶體低。

(9)外掛的靜態廣播會被當作動態處理,如果外掛沒有執行(即沒有外掛程序執行),其靜態廣播也永遠不會被觸發

缺點:

(1)無法在外掛中傳送具有自定義資源的Notification,例如: a. 帶自定義RemoteLayout的Notification b. 圖示通過R.drawable.XXX指定的通知(外掛系統會自動將其轉化為Bitmap)

(2)無法在外掛中註冊一些具有特殊Intent Filter的ServiceActivityBroadcastReceiverContentProvider等元件以供Android系統、已經安裝的其他APP呼叫。

(3)缺乏對Native層的Hook,對某些帶native程式碼的apk支援不好,可能無法執行。比如一部分遊戲無法當作外掛執行。

傳送門:

https://github.com/DroidPluginTeam/DroidPlugin

3、Small ( 2015 年底)

實現原理:(1)動態載入類(2)資源分段(3)動態代理註冊

傳送門:

https://github.com/wequick/Small/wiki/Android

4、VirtualAPK (2017年 6 月)

VirtualAPK 是滴滴開源的一套外掛化框架,支援幾乎所有的 Android 特性,四大元件方面

VirtualAPK架構圖

image.png

傳送門:

https://github.com/didi/VirtualAPK/blob/master/README.md

5、RePlugin (2017 年 7 月) RePlugin是一套完整的、穩定的、適合全面使用的,佔坑類外掛化方案,由360手機衛士的RePlugin Team研發,也是業內首個提出”全面外掛化“(全面特性、全面相容、全面使用)的方案

RePlugin架構圖

image.png

優點:

  • 極其靈活:主程式無需升級(無需在Manifest中預埋元件),即可支援新增的四大元件,甚至全新的外掛
  • 非常穩定:Hook點僅有一處(ClassLoader),無任何Binder Hook!如此可做到其崩潰率僅為“萬分之一”,並完美相容市面上近乎所有的Android ROM
  • 特性豐富:支援近乎所有在“單品”開發時的特性。包括靜態Receiver、Task-Affinity坑位、自定義Theme、程序坑位、AppCompat、DataBinding等
  • 易於整合:無論外掛還是主程式,只需“數行”就能完成接入
  • 管理成熟:擁有成熟穩定的“外掛管理方案”,支援外掛安裝、升級、解除安裝、版本管理,甚至包括程序通訊、協議版本、安全校驗等
  • 數億支撐:有360手機衛士龐大的數億使用者做支撐,三年多的殘酷驗證,確保App用到的方案是最穩定、最適合使用的

傳送門:

https://github.com/Qihoo360/RePlugin/blob/dev/README_CN.md

6、Shadow

騰訊自主研發的Android外掛框架,經過線上億級使用者量檢驗,號稱“零hook”

Shadow主要具有以下特點:

(1)複用獨立安裝App的原始碼:外掛App的原始碼原本就是可以正常安裝執行的。

(2)零反射無Hack實現外掛技術:從理論上就已經確定無需對任何系統做相容開發,更無任何隱藏API呼叫和Google限制非公開SDK介面訪問的策略完全不衝突。

(3)全動態外掛框架:一次性實現完美的外掛框架很難,但Shadow將這些實現全部動態化起來,使外掛框架的程式碼成為了外掛的一部分。外掛的迭代不再受宿主打包了舊版本外掛框架所限制。

(4)宿主增量極小:得益於全動態實現,真正合入宿主程式的程式碼量極小(15KB,160方法數左右)。 (5)Kotlin實現:core.loader,core.transform核心程式碼完全用Kotlin實現,程式碼簡潔易維護

傳送門

https://github.com/Tencent/Shadow

總結

以上是我們目前在Apk包體積優化方面做的一些嘗試和積累,可以根據自身情況取捨使用

通過上述優化措施,貨拉拉32位包體積從82.69M減少到了33.86M,減少了60%

image.png

由於自身業務特點,我們暫時沒有使用外掛化框架;

最後,保持好的開發習慣,砍掉不必要的功能才是保證包體積持續優化的超級大招