支付寶 App 構建優化解析:Android 包大小極致壓縮

前言

本章節咱們將圍繞《支付寶 App 構建優化解析》另啓新系列,細分拆解客戶端在「代碼管理」、「證書管理」、「版本管理」、「構建打包」等維度的具體實現方案展開討論,帶領你們進一步瞭解支付寶在 App 構建模塊下的持續優化。java

本節將主要記錄經過對支付寶 Android 包大小進行壓縮,來改善運行效率和質量。android

背景

包大小的重要性已經不須要多說,包大小直接影響用戶的下載,留存,還有部分廠商預裝強制要求必須小於必定的值。可是隨着業務的迭代開發,應用會愈來愈大,安裝包會不停的膨脹,因此包大小縮減是一個長期的治理過程。web

方案

支付寶也一直在優化包大小的方向上努力,咱們引入了不少方案。 好比:proguard 代碼混淆,圖片從 png 到 tinypng 到 webp,引入 7zip 壓縮方案等。 本方案是有別於上面這些常規的方案,是經過直接刪 dex 中的無用信息,達到支付寶包大小瞬間減少 2.1M 的目的,而且不影響整個的運行邏輯和性能,甚至還能下降一點運行內存。數組

方案介紹

  • 引言

在講詳細方案前得稍微說說整個 Java 系的調試邏輯。 JVM 運行時加載的是 .class 文件,Android 爲了使包大小更緊湊,而且運行更高效發明了 dalvik 和 art 虛擬機,兩種虛擬機運行的都是 .dex 文件(固然 art 虛擬機還能夠同時運行 oat 文件,不在本文章討論範圍)。 因此 dex 文件裏面信息的內容和 class 文件包含的信息是徹底一致的,不一樣的是 dex 文件對 class 中的信息作了去重,一個 dex 包含了不少的 class 文件,而且在結構上有比較大的差別,class 是流式的結構,dex 是分區結構,各個區塊間經過 offset 索引。後面就只提 dex 的結構,再也不提 class 的結構。dex 的結構能夠用下面這張圖表示:架構

dex 文件的結構其實很是清晰,分幾個大塊,header 區,索引區,data 區,map 區。本優化方案優化刪除的就是 data 區中的 debugItems 區域。app

  • debugItem 幹嘛用?

首先得知道 debugItem 裏面存了什麼? 裏面主要包含兩種信息:框架

  1. 函數的參數變量和全部的局部變量
  2. 全部的指令集行號和源文件行號的對應關係 有什麼用呢: 第一點其實很明顯,既然叫 debugItem,那麼確定就是 debug 的時候用的嘍,咱們平時在用 IDE 進行斷點和單步調試的時候都會用到這個區域。 第二點做用那就是上報 crash 或者主動獲取調用堆棧的時候用的,由於虛擬機真正執行的時候是執行的指令集,上報堆棧會上報 crash 的對應源文件行號,此時正是經過這個 debugItem 來獲取對應的行號,能夠用下面的截圖比較直觀的瞭解:

image.png | left | 732x336

上圖是一個比較常見的 crash 信息,紅框中的行號即是經過查找這個 debugItem 來獲取的。模塊化

  • debugItem 有多大?

在支付寶的場景下,debug 包有 4-5M,release 包有 3.5M 左右,佔 dex 文件大小的比例在 5.5% 左右,和 google 官方的數據是一致的。若是能把這部分直接去掉,是否是很誘人!函數

  • debugItem 能直接去掉嗎?

顯然不能,若是去掉了,那全部上報的 crash 信息就會沒有行號,全部的行號都會變成 -1,會被噴的找不到北。 其實在 proguard 的時候就是有配置能夠去掉或保留這個行號信息,-keep SourceFile, LineNumberTable 就是這個做用,爲了方便定位問題,基本全部的開發都保留了這個配置。 因此,方案的核心思路就是去掉 debugItem,同時又能讓 crash 上報的時候能拿到正確的行號。至於 IDE 調試,這個比較好解決,咱們只要處理 release 包就好了,debug 包不處理。post

方案一

核心思路也比較簡單,就是行號查找離線化,讓原本存放在 App 中的行號對應關係提早抽離出來存放在服務端,crash 上報的時候經過提早抽離的行號表進行行號反解,解決 crash 信息上報無行號,沒法定位的問題。 思路雖然簡單,實現的時候仍是有點複雜,推進上線也比較曲折,方案通過幾回調整,大概的方案能夠用下面一張圖來抽象:

image | left

如上圖,核心點有四個:

  1. 修改 proguard,利用 proguard 來刪除 debugItem (去掉 -keep lineNumberTable),在刪除行號表以前 dump 出一個臨時的 dex。
  2. 修改 dexdump,把臨時的 dex 中的行號表關係 dump 成一個 dexpcmapping 文件(指令集行號和源文件行號映射關係),並存至服務端。
  3. hook app runtime 的 crash handler,把 crash 時的指令集行號上報到反解平臺。
  4. 反解平臺經過上報指令集行號和提早準備好 dexpcmapping 文件反解出正確的行號。

上面這套方案大概花了兩個多星期,擼出了整個 demo,其它幾個改造點都不是很難,難點仍是在指令集行號的上報。 咱們知道全部的 crash 最終都是會有一個 throwable 對象,裏面保存了整個堆棧信息,通過反覆的閱讀源碼和嘗試,發現我要的指令集行號其實也在這個對象裏面。能夠用下面一幅簡單的圖示意:

在打印 crash 堆棧信息前,每一個 throwable 都會調用art虛擬機提供的一個 jni 方法,返回一個內部的對象叫 stackTrace 保存在 Throwable 對象中,這個 stackTrace 對象裏面保存的即是整個方法的調用棧,固然也包括指令集行號,後續獲取實際的堆棧信息時會再調用一個 art 的 jni 方法,把這個 stackTrace 方法丟過去,底層經過這個 stackTrace 對象中的指令集行號反解出正式的源文件行號。 好了,其實很簡單,反射獲取下這個 Throwable 中的 stackTrace 對象,拿到指令集行號,而後,上報。 這裏要注意的一個點,比較噁心,每一個虛擬機的實現都不同,首先內部對象的名字,有些叫 stackTrace,有些叫 backstrace,而後這個內部對象的類型也很是有,有些是 int 數組,有些是 long 數組,有些是對象數組,可是都會有這個指令集行號,須要針對不一樣的虛擬機版本使用不一樣的方法去解析這個對象,大概要兼容4種虛擬機,4.x, 5.x, 6.x, 7.x,7.x 虛擬機以後的就統一了。

方案二

上面這套方案其實挺完美的,沒有什麼兼容性問題,刪除是直接利用 proguard,獲取指令集行號直接在 java 層獲取,不須要各類 hook,若是隻須要處理 crash 的上報,方案一足夠了,可是在支付寶有不少場景是遠遠不夠的。 好比:

  • 性能,CPU,內存異常時調用棧。
  • native crash 時的 Java 調用棧。

上面這些 case 都會涉及到堆棧信息,方案一中經過反射調用 throwable 中的 stackTrace 內部對象根本搞不定,須要換種方法。 最開始的思路是嘗試 hook art 虛擬機,天天翻源碼,看看能夠 hook 的點,最後仍是放棄了,一個是擔憂兼容性問題,另外一個是 hook 的點太多,比較慌。 最後換了一種思路,嘗試直接修改 dex 文件,保留一小塊 debugItem,讓系統查找行號的時候指令集行號和源文件行號保持一致,這樣就什麼都不用作,任何監控上報的行號都直接變成了指令集行號,只需修改 dex 文件。能夠用下面的示意圖表示:

image | left

如上圖:原本每個方法都會有一個 debugInfoItem,每個 debuginfoItem 裏面都有一個指令集行號和源文件行號的映射關係,我作的修改其實很是簡單,就是把多餘的 debugInfoItem 所有刪掉了,只留了一個 debugInfoItem,全部的方法都指向同一個 debugInfoItem,而且這個 debugInfoItem 中的指令集行號和源文件行號保持一致,這樣無論用什麼方式來查行號,拿到的都是指令集行號。

其中也踩過不少坑,其實光留一個 debugInfoItem 是不夠的,要兼容全部虛擬機的查找方式,須要對 debugInfoItem 進行分區,而且 debugInfoItem 表不能太大,遇到過一個坑就是 androidO 上進行 dex2oat 優化的時候,會頻繁的遍歷這個 debugInfoItem,致使 AOT 編譯比較慢,最後都經過 debugInfoItem 分區解決了。

這個方案比較完全,不用改 proguard,也不用 hook native。不過若是隻須要處理 crash 的行號問題,那仍是首推方案一,這個方案改動有點大,前期也是天天研究 dex 的文件結構,摳每個細節,有比較大的把握時纔敢改。

小結

目前該方案已經在支付寶正式上線,前面通過好幾輪的外灰驗證,仍是比較穩定的。支付寶總體包大小減小了 2.1M 左右,真實的 dex 大小減小 3.5M 左右。

經過本節內容,咱們初步瞭解了支付寶在 Android 客戶端如何經過包大小壓縮以提高 App 運行效率和質量。因爲篇幅限制,不少技術要點咱們沒法一一展開。而相應的技術內核,咱們一樣應用在了 mPaaS 並對外輸出,歡迎你們上手體驗:

tech.antfin.com/docs/2/4954…

關於 Android 端包大小壓縮的設計思路和具體實踐,一樣期待大家的反饋,歡迎一塊兒探討交流。

往期閱讀

《開篇 | 模塊化與解耦式開發在螞蟻金服 mPaaS 深度實踐探討》

《支付寶移動端動態化方案實踐》

《支付寶客戶端架構解析:iOS 容器化框架初探》

《支付寶客戶端架構解析:Android 容器化框架初探》

《支付寶客戶端架構解析:Android 客戶端啓動速度優化之「垃圾回收」》

《支付寶 App 構建優化解析:經過安裝包重排布優化 Android 端啓動性能》

關注咱們公衆號,得到第一手 mPaaS 技術實踐乾貨

QRCode
相關文章
相關標籤/搜索