前段時(shí)間微信分享了一篇文章——微信Android熱補(bǔ)丁實(shí)踐演進(jìn)之路, 這篇文章主要講了目前流行的Android熱修復(fù)方案,同時(shí)微信在QZone方案的基礎(chǔ)上優(yōu)化出一套dex全量替換的熱修復(fù)方案(Tinker)就轧。個(gè)人認(rèn)為微信的這套方案盡管規(guī)避掉了Qzone方案中插樁導(dǎo)致的問題陷寝,但是由于需要在運(yùn)行時(shí)加載全量的dex仰坦,這可能會在運(yùn)行時(shí)內(nèi)存占用上有一定的影響扣孟,目前這個(gè)僅僅是猜測官帘,有待調(diào)研瞬雹。
GitHub上已有Tinker方案仿版——Tinker_imitator,但帶有個(gè)人感情色彩且不負(fù)責(zé)任地說刽虹,這套方案基本是堆砌各項(xiàng)技術(shù)然后封裝成套裝酗捌,應(yīng)該屬于比較糙的仿版,方案在生成差分dex的時(shí)候采用的是現(xiàn)成的bsdiff算法涌哲,這個(gè)通用的二進(jìn)制差分算法很牛逼胖缤,但由于不能很好的利用dex本身的結(jié)構(gòu),因此會導(dǎo)致可能僅僅添加一句代碼也需要下發(fā)不小的增量包(參考chrome增量更新小胡瓜中的見解)阀圾,相對于微信文章中提到的自研DexDiff算法來說哪廓,這個(gè)就相對弱一點(diǎn)了。
這么一大段背景介紹初烘,我想表達(dá)的是我認(rèn)為微信這篇文章對我而言最關(guān)鍵的價(jià)值在于DexDiff的概念涡真,看完這篇文章之后,我去學(xué)習(xí)了bsdiff算法肾筐,學(xué)習(xí)完發(fā)現(xiàn)bsdiff算法用于apk的增量更新或許不錯(cuò)哆料,但是用來做dex的增量包的確是少了許多對dex進(jìn)行量身定制的優(yōu)勢,于是我嘗試去思考如何根據(jù)dex的數(shù)據(jù)格式來實(shí)現(xiàn)DexDiff吗铐,本文主要分享我的一些思路东亦,希望起到拋磚引玉的作用。
dex文件格式
dex文件是運(yùn)行在Dalvik中的字節(jié)碼文件唬渗,類似于運(yùn)行于JVM中的class文件典阵,熟悉class文件格式的同學(xué)應(yīng)該很容易理解,dex文件的布局可以用下圖來進(jìn)行說明:
如果想更細(xì)致地了解dex文件中每部分?jǐn)?shù)據(jù)的具體格式以及意義镊逝,請移步至官方文檔Dalvik Executable format壮啊,建議可以使用010 Editor學(xué)習(xí)dex文件,當(dāng)打開dex文件時(shí)該編輯器會自動推薦安裝解析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ù)結(jié)構(gòu):
dex2jar實(shí)際上是依據(jù)Java中Class的屬性來設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)的瘸恼,自上而下,一目了然册养。同時(shí)东帅,數(shù)據(jù)結(jié)構(gòu)中的各項(xiàng)數(shù)據(jù)都能在dex文件找到對應(yīng)的數(shù)據(jù)塊:比如className對應(yīng)于dex文件格式中的type_id_item,而type_id_item指向的是string_id_item球拦,通過string_id_item中的偏移便可以定位到data section中相應(yīng)的string_data_item靠闭,從而解析出類字符串;再比如annotations對應(yīng)于dex中的annotation_set_item坎炼,而annotation_set_item包含多個(gè)annotation_item愧膀,每一個(gè)annotation_item都可以通過其encoded_annotation解析出對應(yīng)的type_id_item以及包含的key-value對,從而可以得到修飾類点弯、字段或方法的注解數(shù)據(jù)扇调。如果想要更全面更細(xì)致地了解如何解析dex文件得到上述數(shù)據(jù)結(jié)構(gòu)集,請擼dex2jar抢肛。
dex文件的差分與合成
前奏終于結(jié)束了狼钮,可以開始正題了,不過不要擔(dān)心捡絮,因?yàn)檎私?jīng)的內(nèi)容會比你想象的少很多熬芜。DexDiff的目的在于對比新舊dex生成補(bǔ)丁數(shù)據(jù),然后利用補(bǔ)丁數(shù)據(jù)和舊dex合成新dex福稳,目前DexDiff的實(shí)現(xiàn)中涎拉,我的思路是這樣的:
- 基于dex2jar庫反編譯新舊dex
- 逐個(gè)對比新舊dex中的class:
- 若class僅存在于舊dex,保存舊dex中的class至deleteClasses
- 若class僅存在于新dex,保存新dex中的class至replaceClasses
- 若class存在于新舊dex鼓拧,但class數(shù)據(jù)不一致半火,保存新dex中的class至replaceClasses
- 編譯得到的replaceClasses得到replace.dex
- 記錄deleteClasses中類的標(biāo)識字符串得到delete.data
- 根據(jù)replace.dex、delete.data以及舊dex便可以使用dex2jar編譯得到新dex
其中對比新舊dex中兩個(gè)對應(yīng)的class以確定其是否一致時(shí)季俩,采用的方式是逐個(gè)對比class中的屬性(accessFlags钮糖,superClass,interfaces酌住,fields店归,methods等),若有一項(xiàng)屬性不一致則定義該class為需要用新dex中數(shù)據(jù)替換舊dex中對應(yīng)數(shù)據(jù)酪我。通過這樣的方式可以實(shí)現(xiàn)class粒度上的dex差分消痛,這樣程度的差分可以應(yīng)用于生成QZone方案中的熱修復(fù)補(bǔ)丁包了,如果做增量更新的話都哭,class粒度下的增量包大小也是可以接受的秩伞。實(shí)現(xiàn)DexDiff的思路就是這么簡單粗暴,但是在實(shí)現(xiàn)的過程中還是也還是一波三折质涛,尤其是在比較method中的指令部分時(shí)遇到了不少坑稠歉,項(xiàng)目源碼已經(jīng)上傳至GitHub DexDiff。
優(yōu)化思路
目前版本中做差分的粒度是class汇陆,但是實(shí)際上同樣的思路可以實(shí)現(xiàn)method粒度以及instruction粒度的差分怒炸。方法級別的差分做法應(yīng)該可以完全照搬class級別的差分做法;而instruction粒度的差分需要做適當(dāng)?shù)恼{(diào)整和改進(jìn)毡代,因?yàn)閕nstruction級別不能像class或method一樣可以直接整個(gè)替換阅羹,我們需要對比指令的語義,并且記錄指令的刪除或插入位置偏移教寂,初步的想法是可以設(shè)計(jì)自己的數(shù)據(jù)格式來存儲這些指令差異捏鱼,但這在我估計(jì)總體的實(shí)現(xiàn)難度跟class粒度方案比應(yīng)該不在一個(gè)量級,并且應(yīng)用instruction粒度方案時(shí)的整體的穩(wěn)定性酪耕、兼容性等都將是較大的挑戰(zhàn)导梆,單從技術(shù)上來講還是值得試的,如果后續(xù)完成實(shí)踐再分享迂烁。
GitHub
DexDiff:求圍觀_