Dex格式解析及在Tinker中的應(yīng)用

Difference

Tinker:全量替換眯亦,無(wú)須插樁

傳統(tǒng)的熱修復(fù)需要插樁實(shí)現(xiàn),插樁的原因和操作:
原因:

  1. 通過(guò)將補(bǔ)丁dex文件插入到類加載器的dexElement列表最前面般码,完成熱修復(fù)
  2. 調(diào)用bug類的時(shí)候就會(huì)先搜索到補(bǔ)丁dex里的類妻率,從而fix bug
  3. bug類和它引用的類都在一個(gè)dex中,這個(gè)類就被打上了CLASS_ISPREVERIFIED標(biāo)識(shí)板祝,如果這個(gè)類調(diào)用了插在dexElement隊(duì)列前面的補(bǔ)丁dex文件中的同名方法宫静,就會(huì)報(bào)錯(cuò),所以需要阻止bug類打上該標(biāo)識(shí)
  4. 通過(guò)“插樁”的方法避免需要被fix的class打上CLASS_ISPREVERIFIED標(biāo)識(shí)

如何插樁:
使可能會(huì)產(chǎn)生bug的class引用另外一個(gè)dex中的class券时,從而避免該class被打上CLASS_ISPREVERIFIED標(biāo)識(shí)(Groovy語(yǔ)言字節(jié)碼織入)孤里。

Tinker使用的是apk全量替換的方法,使用差量包補(bǔ)丁包和原來(lái)的apk合成新的apk橘洞,使用全新apk捌袜,從而不會(huì)出現(xiàn)引用其他dex的class的情況,避免了插樁炸枣。

Part 1:怎樣生成差量補(bǔ)丁

APK Diff包括:DexDiff虏等,ResDiff,ManifestDiff和SoDiff

1. ManifestDiff

用來(lái)檢測(cè)新的Manifest是否發(fā)送過(guò)改變适肠,Tinker不支持Manifest修改霍衫。原因應(yīng)該是apk在執(zhí)行install操作的時(shí)候會(huì)向系統(tǒng)注冊(cè)Manifest信息,tinker熱修復(fù)不會(huì)經(jīng)歷這個(gè)過(guò)程侯养。

2. ResDiff/SoDiff

BsDiff算法敦跌,求新版本和舊版本的二進(jìn)制差異。原理來(lái)自這篇博士論文Naive Differences of Executable Code
基本步驟:
a.對(duì)old文件中所有子字符串形成一個(gè)字典沸毁;
b.對(duì)比old文件和new文件峰髓,產(chǎn)生diffstring和extra string傻寂;
c.將diffstring 和extra string 以及相應(yīng)的控制字用zip壓縮成一個(gè)patch包。

優(yōu)點(diǎn):通用性強(qiáng)携兵,適用于所有文件的補(bǔ)丁包生成
缺點(diǎn):沒(méi)有針對(duì)特定的格式做優(yōu)化疾掰,導(dǎo)致補(bǔ)丁包可能過(guò)大

3. DexDiff

對(duì)Dex文件進(jìn)行差量包生成,傳統(tǒng)的方法有兩種徐紧,除了上面提到的Bsdiff算法静檬,還有一種反編譯方法,通過(guò)對(duì)dex文件反編譯后的class進(jìn)行比較并级,以確定哪些class發(fā)生了變化的方式拂檩,并對(duì)發(fā)生變化的class文件進(jìn)行補(bǔ)丁操作。
微信團(tuán)隊(duì)為了使得差異包最小化嘲碧,充分利用了Dex的結(jié)構(gòu)稻励,開(kāi)發(fā)了專門應(yīng)用于Dex文件的差量包生成算法DexDiff算法,跟Bsdiff相比愈涩,喪失了通用性望抽,但是效率更高。
DexClassLoader文件結(jié)構(gòu)如下履婉,分為Header煤篙,Table,Data三部分毁腿。

Header:

dex文件頭部辑奈,記錄整個(gè)dex文件的相關(guān)屬性,如都包含哪些部分(如String已烤,F(xiàn)ield鸠窗,Method,Class等)草戈,每部分的大小和偏移量塌鸯。

Table:

存放每種類型數(shù)據(jù)的地址列表侍瑟,如在String Table中唐片,連續(xù)存放若干個(gè)String的地址,根據(jù)每個(gè)地址涨颜,可以在Data段找到改地址存儲(chǔ)的字符串费韭。

Data:

存放具體的數(shù)據(jù),由Table段不同類型的地址進(jìn)行索引庭瑰。

Dex文件結(jié)構(gòu)
Dex Header格式

以String數(shù)據(jù)為例星持,首先讀取Dex Header部分String IDs offset和String IDs Size的內(nèi)容,如0X70和0X14弹灭,代表String數(shù)據(jù)在Table段的偏移量是0X70督暂,共20個(gè)揪垄。在Table段讀取這20個(gè)數(shù)據(jù),每個(gè)數(shù)據(jù)4個(gè)字節(jié)逻翁,根據(jù)這4個(gè)字節(jié)代表的地址饥努,去DataSection找這個(gè)地址存儲(chǔ)的內(nèi)容,解析成String數(shù)據(jù)八回。

String數(shù)據(jù)解析.png

接下來(lái)計(jì)算新舊Dex的String數(shù)據(jù)的Diff數(shù)據(jù)酷愧。采用最小序列生成算法,生成由舊String列表生成新列表的操作缠诅,用刪除溶浴,添加,修改三種操作表示管引。

算法描述如下士败,摘抄自這篇帖子

首先我們需要將新舊內(nèi)容排序,這需要針對(duì)排序的數(shù)組進(jìn)行操作

新舊兩個(gè)指針褥伴,在內(nèi)容一樣的時(shí)候 old拱烁、new 指針同時(shí)加1,在 old 內(nèi)容小于 new >內(nèi)容的時(shí)候 old 指針加1噩翠,標(biāo)記當(dāng)前 old 項(xiàng)為刪除

在 old 內(nèi)容大于 new 內(nèi)容 new 指針加1戏自, 標(biāo)記當(dāng)前 new 項(xiàng)為新增

------old-----
11 foo2
12 foo5
13 hello dodola
14 hello dodola1

------new-----
11 foo3
12 foo5
13 hello dodola1
14 hello dodola3

對(duì)比的old cursor 和 new cursor 指針的改變以及操作判定,判定過(guò)程如下
old_11 new_11 cmp <0 del
old_12 new_11 cmp >0 add
old_12 new_12 cmp =0 no
old_13 new_13 cmp <0 del
old_14 new_13 cmp =0 no
break;

進(jìn)入下一步過(guò)程
可以確定的是刪除的內(nèi)容肯定是從 old 中的 index 進(jìn)行刪除的 添加的內(nèi)容肯定是從 new 中的 index 中來(lái)的伤锚,按照這個(gè)邏輯我們可以整理如下內(nèi)容擅笔。

old_11 del
new_11 add
old_13 del
new_14 add

到這一步我們需要找出替換的內(nèi)容,很明顯替換的內(nèi)容就是從old中del的并且在 >new 中 add 的并且 index 相同的i tem屯援,所以這就簡(jiǎn)單了

old_11 replace
old_13 del
new_14 add

這樣就生成了兩個(gè)Dex的String部分的變化猛们。

Part 2:怎么加載新的apk

  1. Dex加載:將合成的新的dex加入到PathClassLoader的dexElements列表中
PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", 
      makePathElements(dexPathList,    new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,    suppressedExceptions));
  1. Res加載
    訪問(wèn)應(yīng)用程序資源的函數(shù)有兩個(gè):getResources和getAssets。getResources返回Resources對(duì)象狞洋,Resources對(duì)象通過(guò)資源的ID訪問(wèn)編譯后的資源弯淘。getAssets返回AssetManager對(duì)象,AssetManager對(duì)象通過(guò)資源的文件名訪問(wèn)編譯后或未經(jīng)編譯的資源文件吉懊。實(shí)際上庐橙,Resources訪問(wèn)資源是先通過(guò)資源ID獲取文件名,然后通過(guò)AssetManager根據(jù)文件名訪問(wèn)資源文件借嗽。
    為了使這兩個(gè)方法加載新的資源文件态鳖,執(zhí)行以下操作:
    a. 新建一個(gè)AssetManager對(duì)象newAssetManager,通過(guò)反射調(diào)用其addAssetPath方法恶导,傳入新生成的apk文件路徑
    b. 新建Resources對(duì)象浆竭,通過(guò)反射設(shè)置其mAssets屬性的值為newAssetManager
    通過(guò)這種方式實(shí)現(xiàn)新的資源文件的加載。
addAssetPathMethod.invoke(newAssetManager, externalResourceFile)
assetsFiled.set(resources, newAssetManager);

參考:
https://github.com/WeMobileDev/article/blob/master/%E5%BE%AE%E4%BF%A1Android%E7%83%AD%E8%A1%A5%E4%B8%81%E5%AE%9E%E8%B7%B5%E6%BC%94%E8%BF%9B%E4%B9%8B%E8%B7%AF.md
https://www.zybuluo.com/dodola/note/554061
http://www.reibang.com/p/f7f0a712ddfe
http://blog.csdn.net/add_ada/article/details/51232889

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市邦泄,隨后出現(xiàn)的幾起案子删窒,更是在濱河造成了極大的恐慌,老刑警劉巖顺囊,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件易稠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡包蓝,警方通過(guò)查閱死者的電腦和手機(jī)驶社,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)测萎,“玉大人亡电,你說(shuō)我怎么就攤上這事」枨疲” “怎么了份乒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)腕唧。 經(jīng)常有香客問(wèn)我或辖,道長(zhǎng),這世上最難降的妖魔是什么枣接? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任颂暇,我火速辦了婚禮,結(jié)果婚禮上但惶,老公的妹妹穿的比我還像新娘耳鸯。我一直安慰自己,他們只是感情好膀曾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布县爬。 她就那樣靜靜地躺著,像睡著了一般添谊。 火紅的嫁衣襯著肌膚如雪财喳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天斩狱,我揣著相機(jī)與錄音耳高,去河邊找鬼。 笑死喊废,一個(gè)胖子當(dāng)著我的面吹牛祝高,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播污筷,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瓣蛀?” 一聲冷哼從身側(cè)響起陆蟆,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惋增,沒(méi)想到半個(gè)月后叠殷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诈皿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年林束,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稽亏。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壶冒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出截歉,到底是詐尸還是另有隱情胖腾,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布瘪松,位于F島的核電站咸作,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宵睦。R本人自食惡果不足惜记罚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望壳嚎。 院中可真熱鬧毫胜,春花似錦、人聲如沸诬辈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)焙糟。三九已至口渔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穿撮,已是汗流浹背缺脉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悦穿,地道東北人攻礼。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像栗柒,于是被迫代替她去往敵國(guó)和親礁扮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容