動態(tài)補丁修復(fù)(iOS矾兜,Android)

原文鏈接:公眾號:QQ空間終端開發(fā)團隊

TPatch動態(tài)補丁系統(tǒng)(iOS)

安卓App熱補丁動態(tài)修復(fù)技術(shù)介紹


TPatch是一套使用JavaScript給iOS打熱補丁的系統(tǒng),能非常有效的解決線上App的Crash和各種問題超全。

1.從何而來?

對于每一個開發(fā),從寫Hello World開始邓馒,到使用各種語言嘶朱,可能都會遇到各種BUG。有的BUG能快速解決光酣,比如Web側(cè)的疏遏,發(fā)個JS或者Html即可。但是在終端開發(fā)中救军,比如iOS财异,發(fā)現(xiàn)的線上問題往往沒那么快能解決,換包可能需要Apple短則幾天長則一周以上的審核唱遭,成本很高戳寸。有沒有辦法能快速解決iOS App的線上問題?TPatch是其中一種比較好的解決方案拷泽。

2.TPatch特點

支持多線程:

使用JS打補丁的天然優(yōu)勢在于JavaScriptCore是線程安全的疫鹊,雖然鎖的粒度有點大,并且有些方法的鎖有問題(這些在TPatch都已解決)司致。

支持Block:

JS中的function和OC的Block有很多相似之處拆吆。有補丁中定義的function,傳遞到OC脂矫,我們會轉(zhuǎn)成Block枣耀,并且Block可以在OC和JS之間傳遞,這點Lua補丁是很難做到庭再。

異步機制:

由于JavsSciptCore是線程安全的捞奕,同時也帶來另外一個問題牺堰,假如工作線程和主線程都打了補丁,工作線程的補丁耗時非常嚴重颅围,這時候如果主線程補丁開始運行萌焰,就會被阻塞。TPatch引入了異步機制谷浅,能讓進入JSCore的補丁快速返回,異步執(zhí)行奶卓,減少補丁之間的影響一疯。

支持在線Reset回滾:

在補丁發(fā)布后,有可能通過監(jiān)控發(fā)現(xiàn)補丁有問題夺姑,這時候用戶側(cè)的運行邏輯已經(jīng)被“污染”墩邀。TPatch支持,在補丁后臺設(shè)置該補丁過期后盏浙,用戶側(cè)App會刪掉本地有問題的補丁包眉睹,并且在線Reset,而不是等App重啟后再恢復(fù)废膘,下次重啟可能得好幾天竹海。

調(diào)試方便:

利用JavaScriptCore的天然優(yōu)勢,其內(nèi)部提供了Debug接口丐黄。我們可以像調(diào)試App里面的網(wǎng)頁一樣斋配,使用Mac下的Safari遠程調(diào)試補丁,斷點灌闺、堆棧艰争、異常等一目了然。

精準投放:

TPatch支持按用戶桂对、iOS版本甩卓、業(yè)務(wù)App版本和Mask標記投放。Mask是一個可擴展的bit標記蕉斜,業(yè)務(wù)可以自定義逾柿,比如取一位越獄標記,或者網(wǎng)絡(luò)標記蛛勉,補丁就可以根據(jù)是否越獄和網(wǎng)絡(luò)標記下發(fā)鹿寻。

3.核心原理

TPatch包括補丁包后臺系統(tǒng)和終端組件,其核心原理是補丁后臺根據(jù)補丁配置诽凌,下發(fā)一段補丁JS給終端毡熏,終端執(zhí)行這段補丁,利用OC Runtime覆蓋有問題的方法或者執(zhí)行一段邏輯侣诵,修正運行時的邏輯痢法,從而達到修復(fù)BUG的目的狱窘。

4.打補丁流程

1.在補丁后臺下發(fā)補丁腳本后,首先會經(jīng)過iOS7及以上系統(tǒng)自帶的JavaScriptCore.framework把JS補丁執(zhí)行起來财搁,通過調(diào)用TPatch.js里面的Bridge接口蘸炸,調(diào)用到OC里面打補丁的方法,打上補丁尖奔。

2.當業(yè)務(wù)代碼執(zhí)行這段已經(jīng)打了補丁的功能時搭儒,不會是原來的OC代碼,而是一段JS代碼提茁。JS可以通過JS引起和OC引擎支持Block淹禾、異步執(zhí)行等,并且支持在線Reset回滾茴扁。

5.和其他方案對比

waxPatch:

是使用Lua+Wax打補丁的方案铃岔,App需要集成Lua解釋器和Wax框架(接近1M)。不過waxPatch對Block不太完善峭火,多線程補丁也可能有問題毁习,Wax也已經(jīng)兩年沒人維護。相比之下TPatch更加輕量卖丸,對安裝包影響僅200K纺且,功能也更加強大。

JSPatch:

同樣使用JS來打補丁坯苹,和TPatch終端組件核心原理是相似的隆檀。不過JSPatch在實際的海量產(chǎn)品中運用還有不少問題沒解決,比如Block傳遞粹湃、多線程Crash等問題恐仑,TPatch解決了這些問題,更加穩(wěn)定为鳄,并且支持異步機制裳仆、動態(tài)回滾等優(yōu)化特性。


1.背景

當一個App發(fā)布之后孤钦,突然發(fā)現(xiàn)了一個嚴重bug需要進行緊急修復(fù)歧斟,這時候公司各方就會忙得焦頭爛額:重新打包App、測試偏形、向各個應(yīng)用市場和渠道換包静袖、提示用戶升級、用戶下載俊扭、覆蓋安裝队橙。有時候僅僅是為了修改了一行代碼,也要付出巨大的成本進行換包和重新發(fā)布。

這時候就提出一個問題:有沒有辦法以補丁的方式動態(tài)修復(fù)緊急Bug捐康,不再需要重新發(fā)布App仇矾,不再需要用戶重新下載,覆蓋安裝解总?

雖然Android系統(tǒng)并沒有提供這個技術(shù)贮匕,但是很幸運的告訴大家,答案是:可以花枫,我們QQ空間提出了熱補丁動態(tài)修復(fù)技術(shù)來解決以上這些問題刻盐。

2.實際案例

空間Android獨立版5.2發(fā)布后,收到用戶反饋劳翰,結(jié)合版無法跳轉(zhuǎn)到獨立版的訪客界面隙疚,每天都較大的反饋。在以前只能緊急換包磕道,重新發(fā)布。成本非常高行冰,也影響用戶的口碑溺蕉。最終決定使用熱補丁動態(tài)修復(fù)技術(shù),向用戶下發(fā)Patch悼做,在用戶無感知的情況下疯特,修復(fù)了外網(wǎng)問題,取得非常好的效果肛走。

3.解決方案

該方案基于的是android dex分包方案的漓雅,關(guān)于dex分包方案,網(wǎng)上有幾篇解釋了朽色,所以這里就不再贅述邻吞,具體可以看這里https://m.oschina.net/blog/308583。

簡單的概括一下葫男,就是把多個dex文件塞入到app的classloader之中抱冷,但是android dex拆包方案中的類是沒有重復(fù)的,如果classes.dex和classes1.dex中有重復(fù)的類梢褐,當用到這個重復(fù)的類的時候旺遮,系統(tǒng)會選擇哪個類進行加載呢?

讓我們來看看類加載的代碼:

一個ClassLoader可以包含多個dex文件盈咳,每個dex文件是一個Element耿眉,多個dex文件排列成一個有序的數(shù)組dexElements,當找類的時候鱼响,會按順序遍歷dex文件鸣剪,然后從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續(xù)查找西傀。

理論上斤寇,如果在不同的dex中有相同的類存在,那么會優(yōu)先選擇排在前面的dex文件的類拥褂,如下圖:

在此基礎(chǔ)上娘锁,我們構(gòu)想了熱補丁的方案,把有問題的類打包到一個dex(patch.dex)中去饺鹃,然后把這個dex插入到Elements的最前面莫秆,如下圖:

好,該方案基于第二個拆分dex的方案悔详,方案實現(xiàn)如果懂拆分dex的原理的話镊屎,大家應(yīng)該很快就會實現(xiàn)該方案,如果沒有拆分dex的項目的話茄螃,可以參考一下谷歌的multidex方案實現(xiàn)缝驳。然后在插入數(shù)組的時候,把補丁包插入到最前面去归苍。

好用狱,看似問題很簡單,輕松的搞定了拼弃,讓我們來試驗一下夏伊,修改某個類,然后打包成dex吻氧,插入到classloader溺忧,當加載類的時候出現(xiàn)了(本例中是QzoneActivityManager要被替換):

為什么會出現(xiàn)以上問題呢?

從log的意思上來講盯孙,ModuleManager引用了QzoneActivityManager鲁森,但是發(fā)現(xiàn)這這兩個類所在的dex不在一起,其中:

1. ModuleManager在classes.dex中

2. QzoneActivityManager在patch.dex中

結(jié)果發(fā)生了錯誤振惰。

這里有個問題,拆分dex的很多類都不是在同一個dex內(nèi)的,怎么沒有問題?

讓我們搜索一下拋出錯誤的代碼所在刀森,嘿咻嘿咻,找到了一下代碼:

從代碼上來看报账,如果兩個相關(guān)聯(lián)的類在不同的dex中就會報錯研底,但是拆分dex沒有報錯這是為什么,原來這個校驗的前提是:

如果引用者(也就是ModuleManager)這個類被打上了CLASS_ISPREVERIFIED標志透罢,那么就會進行dex的校驗榜晦。那么這個標志是什么時候被打上去的?讓我們在繼續(xù)搜索一下代碼羽圃,嘿咻嘿咻~~乾胶,在DexPrepare.cpp找到了一下代碼:

這段代碼是dex轉(zhuǎn)化成odex(dexopt)的代碼中的一段,我們知道當一個apk在安裝的時候,apk中的classes.dex會被虛擬機(dexopt)優(yōu)化成odex文件识窿,然后才會拿去執(zhí)行斩郎。

虛擬機在啟動的時候,會有許多的啟動參數(shù)喻频,其中一項就是verify選項缩宜,當verify選項被打開的時候,上面doVerify變量為true甥温,那么就會執(zhí)行dvmVerifyClass進行類的校驗锻煌,如果dvmVerifyClass校驗類成功,那么這個類會被打上CLASS_ISPREVERIFIED的標志姻蚓,那么具體的校驗過程是什么樣子的呢宋梧?

此代碼在DexVerify.cpp中,如下:

1. 驗證clazz->directMethods方法狰挡,directMethods包含了以下方法:

1. static方法

2. private方法

3. 構(gòu)造函數(shù)

2.clazz->virtualMethods

1. 虛函數(shù)=override方法?

概括一下就是如果以上方法中直接引用到的類(第一層級關(guān)系捂龄,不會進行遞歸搜索)和clazz都在同一個dex中的話,那么這個類就會被打上CLASS_ISPREVERIFIED:

所以為了實現(xiàn)補丁方案加叁,所以必須從這些方法中入手跺讯,防止類被打上CLASS_ISPREVERIFIED標志。

最終空間的方案是往所有類的構(gòu)造函數(shù)里面插入了一段代碼殉农,代碼如下:

if (ClassVerifier.PREVENT_VERIFY) {

System.out.println(AntilazyLoad.class);

}

其中AntilazyLoad類會被打包成單獨的hack.dex,這樣當安裝apk的時候局荚,classes.dex內(nèi)的類都會引用一個在不相同dex中的AntilazyLoad類超凳,這樣就防止了類被打上CLASS_ISPREVERIFIED的標志了,只要沒被打上這個標志的類都可以進行打補丁操作耀态。

然后在應(yīng)用啟動的時候加載進來.AntilazyLoad類所在的dex包必須被先加載進來,不然AntilazyLoad類會被標記為不存在轮傍,即使后續(xù)加載了hack.dex包,那么他也是不存在的首装,這樣屏幕就會出現(xiàn)茫茫多的類AntilazyLoad找不到的log创夜。

所以Application作為應(yīng)用的入口不能插入這段代碼。(因為載入hack.dex的代碼是在Application中onCreate中執(zhí)行的仙逻,如果在Application的構(gòu)造函數(shù)里面插入了這段代碼驰吓,那么就是在hack.dex加載之前就使用該類,該類一次找不到系奉,會被永遠的打上找不到的標志)

其中:

之所以選擇構(gòu)造函數(shù)是因為他不增加方法數(shù)檬贰,一個類即使沒有顯式的構(gòu)造函數(shù),也會有一個隱式的默認構(gòu)造函數(shù)缺亮。

空間使用的是在字節(jié)碼插入代碼,而不是源代碼插入翁涤,使用的是javaassist庫來進行字節(jié)碼插入的。

隱患:

虛擬機在安裝期間為類打上CLASS_ISPREVERIFIED標志是為了提高性能的,我們強制防止類被打上標志是否會影響性能葵礼?這里我們會做一下更加詳細的性能測試.但是在大項目中拆分dex的問題已經(jīng)比較嚴重号阿,很多類都沒有被打上這個標志。

如何打包補丁包:

1. 空間在正式版本發(fā)布的時候鸳粉,會生成一份緩存文件扔涧,里面記錄了所有class文件的md5,還有一份mapping混淆文件赁严。

2. 在后續(xù)的版本中使用-applymapping選項扰柠,應(yīng)用正式版本的mapping文件,然后計算編譯完成后的class文件的md5和正式版本進行比較疼约,把不相同的class文件打包成補丁包卤档。

備注:該方案現(xiàn)在也應(yīng)用到我們的編譯過程當中,編譯不需要重新打包dex,只需要把修改過的類的class文件打包成patch dex,然后放到sdcard下,那么就會讓改變的代碼生效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末程剥,一起剝皮案震驚了整個濱河市劝枣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌织鲸,老刑警劉巖舔腾,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搂擦,居然都是意外死亡稳诚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門瀑踢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扳还,“玉大人,你說我怎么就攤上這事橱夭“本啵” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵棘劣,是天一觀的道長俏让。 經(jīng)常有香客問我,道長茬暇,這世上最難降的妖魔是什么首昔? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮糙俗,結(jié)果婚禮上沙廉,老公的妹妹穿的比我還像新娘。我一直安慰自己臼节,他們只是感情好撬陵,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布珊皿。 她就那樣靜靜地躺著,像睡著了一般巨税。 火紅的嫁衣襯著肌膚如雪蟋定。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天草添,我揣著相機與錄音驶兜,去河邊找鬼。 笑死远寸,一個胖子當著我的面吹牛抄淑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驰后,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肆资,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灶芝?” 一聲冷哼從身側(cè)響起郑原,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夜涕,沒想到半個月后犯犁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡女器,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年酸役,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驾胆。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡涣澡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俏拱,到底是詐尸還是另有隱情,我是刑警寧澤吼句,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布锅必,位于F島的核電站,受9級特大地震影響惕艳,放射性物質(zhì)發(fā)生泄漏搞隐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一远搪、第九天 我趴在偏房一處隱蔽的房頂上張望劣纲。 院中可真熱鬧,春花似錦谁鳍、人聲如沸癞季。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绷柒。三九已至志于,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間废睦,已是汗流浹背伺绽。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗜湃,地道東北人奈应。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像购披,于是被迫代替她去往敵國和親杖挣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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