DexDiff:基于dex文件反編譯生成dex增量包

前段時間微信分享了一篇文章——微信Android熱補丁實踐演進之路, 這篇文章主要講了目前流行的Android熱修復方案,同時微信在QZone方案的基礎上優(yōu)化出一套dex全量替換的熱修復方案(Tinker)。個人認為微信的這套方案盡管規(guī)避掉了Qzone方案中插樁導致的問題,但是由于需要在運行時加載全量的dex,這可能會在運行時內(nèi)存占用上有一定的影響,目前這個僅僅是猜測,有待調(diào)研。

GitHub上已有Tinker方案仿版——Tinker_imitator,但帶有個人感情色彩且不負責任地說,這套方案基本是堆砌各項技術然后封裝成套裝,應該屬于比較糙的仿版,方案在生成差分dex的時候采用的是現(xiàn)成的bsdiff算法,這個通用的二進制差分算法很牛逼,但由于不能很好的利用dex本身的結構,因此會導致可能僅僅添加一句代碼也需要下發(fā)不小的增量包(參考chrome增量更新小胡瓜中的見解),相對于微信文章中提到的自研DexDiff算法來說,這個就相對弱一點了。

這么一大段背景介紹,我想表達的是我認為微信這篇文章對我而言最關鍵的價值在于DexDiff的概念,看完這篇文章之后,我去學習了bsdiff算法,學習完發(fā)現(xiàn)bsdiff算法用于apk的增量更新或許不錯,但是用來做dex的增量包的確是少了許多對dex進行量身定制的優(yōu)勢,于是我嘗試去思考如何根據(jù)dex的數(shù)據(jù)格式來實現(xiàn)DexDiff,本文主要分享我的一些思路,希望起到拋磚引玉的作用。

dex文件格式

dex文件是運行在Dalvik中的字節(jié)碼文件,類似于運行于JVM中的class文件,熟悉class文件格式的同學應該很容易理解,dex文件的布局可以用下圖來進行說明:

dex文件布局

如果想更細致地了解dex文件中每部分數(shù)據(jù)的具體格式以及意義,請移步至官方文檔Dalvik Executable format,建議可以使用010 Editor學習dex文件,當打開dex文件時該編輯器會自動推薦安裝解析dex文件的插件,安裝完插件便能與dex文件愉快地玩耍了。

反編譯dex文件

了解了dex文件的基本格式之后,就可以開始dex文件的反編譯之旅了,Android反編譯套裝(apktool、smail、dex2jar、jd-gui)現(xiàn)在基本是居家必備了,而今天要出場的大牌就是dex2jar(~~Orz),dex2jar采用了跟asm一樣的套路,搖身一變便成了解析和生成dex文件的神器,建議沒看過asm源碼的Android開發(fā)小伙伴,可以直接擼一把dex2jar的源碼,相信會有不少收獲,至少對了解dex文件的內(nèi)部數(shù)據(jù)格式來講,會得到量與質(zhì)的提升。源碼都亮出來了,似乎沒必要在解釋下去了,但是為了能夠圓潤地過渡,我決定還是說明下dex2jar解析完dex文件后的數(shù)據(jù)結構:

dex2jar解析完dex文件后的數(shù)據(jù)結構

dex2jar實際上是依據(jù)Java中Class的屬性來設計數(shù)據(jù)結構的,自上而下,一目了然。同時,數(shù)據(jù)結構中的各項數(shù)據(jù)都能在dex文件找到對應的數(shù)據(jù)塊:比如className對應于dex文件格式中的type_id_item,而type_id_item指向的是string_id_item,通過string_id_item中的偏移便可以定位到data section中相應的string_data_item,從而解析出類字符串;再比如annotations對應于dex中的annotation_set_item,而annotation_set_item包含多個annotation_item,每一個annotation_item都可以通過其encoded_annotation解析出對應的type_id_item以及包含的key-value對,從而可以得到修飾類、字段或方法的注解數(shù)據(jù)。如果想要更全面更細致地了解如何解析dex文件得到上述數(shù)據(jù)結構集,請擼dex2jar。

dex文件的差分與合成

前奏終于結束了,可以開始正題了,不過不要擔心,因為正二八經(jīng)的內(nèi)容會比你想象的少很多。DexDiff的目的在于對比新舊dex生成補丁數(shù)據(jù),然后利用補丁數(shù)據(jù)和舊dex合成新dex,目前DexDiff的實現(xiàn)中,我的思路是這樣的:

  1. 基于dex2jar庫反編譯新舊dex
  2. 逐個對比新舊dex中的class:
    • 若class僅存在于舊dex,保存舊dex中的class至deleteClasses
    • 若class僅存在于新dex,保存新dex中的class至replaceClasses
    • 若class存在于新舊dex,但class數(shù)據(jù)不一致,保存新dex中的class至replaceClasses
  3. 編譯得到的replaceClasses得到replace.dex
  4. 記錄deleteClasses中類的標識字符串得到delete.data
  5. 根據(jù)replace.dex、delete.data以及舊dex便可以使用dex2jar編譯得到新dex

其中對比新舊dex中兩個對應的class以確定其是否一致時,采用的方式是逐個對比class中的屬性(accessFlags,superClass,interfaces,fields,methods等),若有一項屬性不一致則定義該class為需要用新dex中數(shù)據(jù)替換舊dex中對應數(shù)據(jù)。通過這樣的方式可以實現(xiàn)class粒度上的dex差分,這樣程度的差分可以應用于生成QZone方案中的熱修復補丁包了,如果做增量更新的話,class粒度下的增量包大小也是可以接受的。實現(xiàn)DexDiff的思路就是這么簡單粗暴,但是在實現(xiàn)的過程中還是也還是一波三折,尤其是在比較method中的指令部分時遇到了不少坑,項目源碼已經(jīng)上傳至GitHub DexDiff。

優(yōu)化思路

目前版本中做差分的粒度是class,但是實際上同樣的思路可以實現(xiàn)method粒度以及instruction粒度的差分。方法級別的差分做法應該可以完全照搬class級別的差分做法;而instruction粒度的差分需要做適當?shù)恼{(diào)整和改進,因為instruction級別不能像class或method一樣可以直接整個替換,我們需要對比指令的語義,并且記錄指令的刪除或插入位置偏移,初步的想法是可以設計自己的數(shù)據(jù)格式來存儲這些指令差異,但這在我估計總體的實現(xiàn)難度跟class粒度方案比應該不在一個量級,并且應用instruction粒度方案時的整體的穩(wěn)定性、兼容性等都將是較大的挑戰(zhàn),單從技術上來講還是值得試的,如果后續(xù)完成實踐再分享。

GitHub

DexDiff:求圍觀_

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容