如何5分鐘讓你的 SDK 擁有熱修復(fù)能力(原理篇)

前言

看完本文可以達(dá)到什么程度

  • 自頂向下分析
  • 學(xué)會(huì) hook task
  • 學(xué)會(huì)自定義 task,自定義 gradle 插件
  • 掌握改造 Robust

預(yù)備知識(shí)

  • 理解 gradle 的基本開發(fā)
  • 掌握 Task坝冕,Transform 概念

閱讀前準(zhǔn)備工作


前文提到,當(dāng)我美滋滋接入 Robust 時(shí)卻報(bào)錯(cuò)提示威兜,只有 Application Module 才能插樁和打補(bǔ)丁销斟,而且還要做補(bǔ)丁管理、加解密椒舵、數(shù)據(jù)分析……

看來我們需要進(jìn)行一番改造了蚂踊。

從哪入手呢?

我們自頂向下分析整個(gè)需求實(shí)現(xiàn)步驟

在我們開始寫代碼之前笔宿,我們需要清楚如何解決問題犁钟。將問題分解成子問題,直到每個(gè)子問題都可以很輕松地解決泼橘。對(duì)問題進(jìn)行建模特纤,這樣寫出來的代碼才具有可讀性和可測(cè)性。

針對(duì)每一個(gè)步驟侥加,我們來挖掘下對(duì)應(yīng)的問題:

是不是清晰多了捧存?

講解之前先說明,本文著重于解決思路担败, 代碼細(xì)節(jié)已經(jīng)開源昔穴,不過多展開。只要掌握解決思路提前,剩下的都是面向 API 編程啦吗货。

第一步:接入Robust

問題1:Robust 只支持Application Module

一個(gè) apk 和一個(gè) arr 有什么差別?

aar和apk共同點(diǎn)

實(shí)際上沒什么差別狈网。我們只需要加個(gè)開關(guān)宙搬,先將 SDK 配置為 Application,再 Hook apk 打包的 Task拓哺,把 build 目錄下的資源壓縮為 aar 不就行了勇垛!

實(shí)現(xiàn)見:packPlugin.gradle hookAssembleAndCopyRes 方法

問題2:怎么把插樁后的 aar 上傳到 jcenter 呢?

uploadArchives Task 配置

上傳肯定是要借助 uploadArchives 這個(gè) Task士鸥,但是我翻遍了 uploadArchives Task 配置和官方文檔闲孤,發(fā)現(xiàn)并不支持自定義數(shù)據(jù)源入口的。(心拔涼拔涼)

怎么辦烤礁?GG了讼积?

別急,我們還有大殺招 —— Hook Gradle Task脚仔。(后面你將會(huì)陸陸續(xù)續(xù)看到勤众,Hook 簡(jiǎn)直就是個(gè)萬金油~)

還不了解 Gradle 工作流程、Plugin鲤脏、Task 的關(guān)系们颜?見 一文應(yīng)用 AOP 三、應(yīng)用篇的基礎(chǔ)部分

我們知道, Task 有一個(gè)重要的概念:inputs 和 outputs掌桩,Task 通過 inputs 拿到需要的參數(shù),處理完畢之后就輸出 outputs姑食,而下一個(gè) Task 的 inputs 則是上一個(gè) Task 的outputs波岛,如下圖:

那我們打印一下 uploadArchives 的依賴 Task 和對(duì)應(yīng)的輸入輸出,看下有什么線索(福爾摩斯.jpg)

bundleRelease Task 就是和打包相關(guān)的 Task 了音半,我們打印下這個(gè) Task 的輸出和輸入:

誒则拷,能不能這樣,hook 這個(gè)Task曹鸠,把我們插樁的 jar 復(fù)制粘貼覆蓋這個(gè)輸入源的 classes.jar煌茬?!好一招移花接木彻桃,就這么辦坛善!

實(shí)現(xiàn)見:packPlugin.gradle hookBundle方法

問題3:資源 ID 沖突

接入后,測(cè)試跑過來:誒邻眷?怎么 sdk 里的 layout 都不顯示了眠屎。

哈?啥情況

反編譯一看:

不同打包方式資源比對(duì).png

好家伙肆饶,用 application 方式打包的 aar 資源 id 變成常量了改衩。

這會(huì)有什么問題呢?

在 ADT 14之前驯镊,無論是主項(xiàng)目還是庫(kù)項(xiàng)目葫督,資源 ID 統(tǒng)一被定義為 final 類型的靜態(tài)變量。如果代碼中使用了 ID板惑,就相當(dāng)于使用一個(gè)常量橄镜,編譯器的語(yǔ)法優(yōu)化會(huì)將引用替換成具體的十六進(jìn)制數(shù)。這樣造成的結(jié)果:主項(xiàng)目編譯時(shí)一旦發(fā)現(xiàn)資源 ID 沖突冯乘,就會(huì)為庫(kù)項(xiàng)目的資源分配新 ID蛉鹿,這樣庫(kù)項(xiàng)目里引用資源文件的代碼都需要重新編譯。

如果 lib R 類中的資源 ID 僅被 static 修飾往湿,就保留了變量妖异。當(dāng)資源ID發(fā)送沖突時(shí),主項(xiàng)目 R 類不變领追,修改庫(kù)項(xiàng)目 R 類中的變量他膳,庫(kù)項(xiàng)目已經(jīng)編譯過的代碼仍有效。

所以 Android 設(shè)計(jì) application 的 R 類都是靜態(tài)常量绒窑,lib 的 R 類都是靜態(tài)變量棕孙。

用過 BufferKnife 的同學(xué)都知道,在 libary 中使用 switch 需要用 R2.XXX,那不就會(huì)出現(xiàn)常量 R2 和業(yè)務(wù)方 R 資源沖突的問題嗎蟀俊?J 神是怎么解決的钦铺?請(qǐng)看:AST 抽象語(yǔ)法樹 —— 最輕量級(jí)的AOP方法

能咋整?

難道我們要根據(jù) 0x7f… 反查 R.XX.XX 然后再一一把代碼替換掉肢预?要不我們強(qiáng)制改下插樁 aar 的資源 id 值從而避免沖突矛洞?默認(rèn)值是0x7F...改成0x02~0x7E?

這么多個(gè)方案在我腦海中繞了一圈烫映,一拍大腿沼本,不用這么復(fù)雜呀!

既然 R 文件的變量修飾符變了锭沟,那我們改回來唄抽兆!

先找到 R 文件生成的Task,來族淮,我們打印一下:

...

:sdk:generateReleaseBuildConfig //會(huì)生成 releasse下的 資源和源碼 包括BuildConfig  在build/generate/source/buildConfig/release  依賴checkReleaseManifest
:sdk:generateReleaseResValues //準(zhǔn)備resource的 values文件  
:sdk:generateReleaseResources //準(zhǔn)備 資源文件 
:sdk:mergeReleaseResources  //release下的 生成Resource文件 在build/incremental/res/release下 和 merge.xml 在build/intermediates/incremental/mergeResources/release下
:sdk:processReleaseManifest //依賴prepareReleaseDependencies  生成 AndroidManifest文件 在build/incremental/manifest/full/release
:sdk:processReleaseResources //生成resources-release.ap_
:sdk:generateReleaseSources //生成R文件  在build/generate/source/r/debug下 
:sdk:processReleaseJavaRes            
:sdk:compileReleaseJavaWithJavac //使用Javac編譯Java代碼
:sdk:proguardRelease  //生成 混淆文件 運(yùn)行混淆規(guī)則
:sdk:androidJavadocsPicked
:sdk:copyMappingTask  //復(fù)制 mapping文件  
:sdk:androidJavadocsJar //生成 Javadocs的Jar文件
:sdk:androidSourcesJar //生成 Java源碼的 Jar文件
...

我們看到 generateReleaseSources Task 負(fù)責(zé)生成R文件辫红,那我們寫個(gè) Gradle 腳本,hook 住前一個(gè)的 processReleaseResources Task祝辣,修改修飾符厉熟,so easy,hook 大法好较幌!

實(shí)現(xiàn)見:packPlugin.gradle hookBuild方法

是不是覺得解決方案超簡(jiǎn)單揍瑟?有時(shí)候難的不是實(shí)現(xiàn),而是你能不能找到切入點(diǎn)乍炉。

動(dòng)態(tài)修改 resId 小擴(kuò)展:

第二步:補(bǔ)丁下發(fā)

補(bǔ)丁下載绢片、加載和上傳與一般的 app 熱更無異,所以我們少些筆墨岛琼。

補(bǔ)丁的存儲(chǔ)我們采用阿里云 OSS 對(duì)象存儲(chǔ):一種安全底循、低成本、高可靠的云存儲(chǔ)服務(wù)槐瑞,客戶端熙涤、服務(wù)端、阿里云三者關(guān)系如下:

直接上時(shí)序圖

第三步:補(bǔ)丁加載策略

第四步:補(bǔ)丁上傳

這里有一點(diǎn)需要注意:自定義 Gradle Task 只支持引入純 Java 庫(kù)困檩,不支持Android庫(kù)祠挫。

第五步:補(bǔ)丁失效機(jī)制

1. 全局異常捕獲不是最優(yōu)解

一說到捕獲異常,下意識(shí)第一反應(yīng):使用全局異常捕獲器呀悼沿!

誒誒誒等舔,那你就掉進(jìn)經(jīng)驗(yàn)坑里了!

我們思考下糟趾,如上圖一共三步慌植,最重要的一步是:確認(rèn)是補(bǔ)丁引起的異常甚牲,如果使用全局異常捕獲器,意味著:

  1. 我們需要比對(duì)堆棧信息蝶柿。但是補(bǔ)丁可以獲取類丈钙,無法獲取到修復(fù)的方法名,而且堆棧比較非常低效交汤,誤判率也高雏赦。
  1. 補(bǔ)丁修復(fù)了 A + B,A 修復(fù)引起異常蜻展,整個(gè)補(bǔ)丁自動(dòng)失效,B 修復(fù)也失效了邀摆。

能不能只讓 A 失效呢纵顾?能不能高效確認(rèn)是補(bǔ)丁引起的異常呢?

2. 在Robust源碼中找找靈感

我們看看 robust.xml 的配置栋盹,一個(gè)是否自動(dòng)捕獲異常的配置映入眼簾施逾。

來,在 Robust 全局搜索下 catchReflectException 關(guān)鍵字例获,發(fā)現(xiàn)插樁工廠類處理簡(jiǎn)單粗暴汉额,直接拼接了 try-catch。

生成的補(bǔ)丁代碼如下:

3. 確認(rèn)改庫(kù)的方式

那我們利用 try-catch榨汤,改造一下 Robust 的插樁吧蠕搜。

  1. robust.xml 增加配置項(xiàng),支持 rollbackWhenException 配置收壕;
  2. 初始化時(shí)加載 XML 配置項(xiàng)妓灌;
  3. 新建回滾監(jiān)聽器類,供業(yè)務(wù)方監(jiān)聽蜜宪;
  4. 插樁時(shí)虫埂,若開啟此配置,則插入 try-catch圃验,且 catch 里通知回滾監(jiān)聽器掉伏。

每個(gè)補(bǔ)丁方法都有唯一 id 我們利用此 id 來記錄回滾狀態(tài)

對(duì) Robust 的詳細(xì)改造MR,感興趣的可以看看

開發(fā)階段終于結(jié)束了澳窑,歇會(huì)兒~

第六步:數(shù)據(jù)分析

我們先看看我們最關(guān)心的指標(biāo)是哪些斧散,再倒推出詳細(xì)的埋點(diǎn)項(xiàng)。

其中的接口失敗率和下載失敗率摊聋,我們還應(yīng)區(qū)別錯(cuò)誤原因:

后記

  1. SDK 熱修復(fù)方案少人做 ≠ 難做颅湘,考驗(yàn)的就是拆分問題、細(xì)化問題的能力
  2. 大膽假設(shè)栗精,細(xì)心驗(yàn)證

以上的實(shí)現(xiàn)我已經(jīng)封裝成庫(kù) SDKHotFix闯参,SDK 開發(fā)者只需 5 分鐘即可讓自己的 SDK 擁有熱修復(fù)的能力瞻鹏,歡迎 star !

最后鹿寨,附上整個(gè)思維導(dǎo)圖:

本篇完成耗時(shí) 15 個(gè)番茄鐘(450 分鐘)


我是 FeelsChaotic新博,一個(gè)寫得了代碼 p 得了圖,剪得了視頻畫得了畫的程序媛脚草,致力于追求代碼優(yōu)雅赫悄、架構(gòu)設(shè)計(jì)和 T 型成長(zhǎng)。

歡迎關(guān)注 FeelsChaotic 的簡(jiǎn)書掘金馏慨,如果我的文章對(duì)你哪怕有一點(diǎn)點(diǎn)幫助埂淮,歡迎 ??!你的鼓勵(lì)是我寫作的最大動(dòng)力写隶!

最最重要的倔撞,請(qǐng)給出你的建議或意見,有錯(cuò)誤請(qǐng)多多指正慕趴!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痪蝇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子冕房,更是在濱河造成了極大的恐慌躏啰,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耙册,死亡現(xiàn)場(chǎng)離奇詭異给僵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)详拙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門想际,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溪厘,你說我怎么就攤上這事胡本。” “怎么了畸悬?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵侧甫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蹋宦,道長(zhǎng)披粟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任冷冗,我火速辦了婚禮守屉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒿辙。我一直安慰自己拇泛,他們只是感情好滨巴,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俺叭,像睡著了一般恭取。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熄守,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天蜈垮,我揣著相機(jī)與錄音,去河邊找鬼裕照。 笑死攒发,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晋南。 我是一名探鬼主播惠猿,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼搬俊!你這毒婦竟也來了紊扬?” 一聲冷哼從身側(cè)響起蜒茄,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤唉擂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后檀葛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玩祟,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年屿聋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了空扎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡润讥,死狀恐怖转锈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情楚殿,我是刑警寧澤撮慨,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站脆粥,受9級(jí)特大地震影響砌溺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜变隔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一规伐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匣缘,春花似錦猖闪、人聲如沸鲜棠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岔留。三九已至,卻和暖如春检柬,著一層夾襖步出監(jiān)牢的瞬間献联,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工何址, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留里逆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓用爪,卻偏偏與公主長(zhǎng)得像原押,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偎血,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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