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

前言

看完本文能夠達到什麼程度

  • 自頂向下分析
  • 學會 hook task
  • 學會自定義 task,自定義 gradle 插件
  • 掌握改造 Robust

預備知識

  • 理解 gradle 的基本開發
  • 掌握 Task,Transform 概念

閱讀前準備工做


前文提到,當我美滋滋接入 Robust 時卻報錯提示,只有 Application Module 才能插樁和打補丁,並且還要作補丁管理、加解密、數據分析……html

看來咱們須要進行一番改造了。android

從哪入手呢?git

咱們自頂向下分析整個需求實現步驟

在咱們開始寫代碼以前,咱們須要清楚如何解決問題。將問題分解成子問題,直到每一個子問題均可以很輕鬆地解決。對問題進行建模,這樣寫出來的代碼才具備可讀性和可測性。github

針對每個步驟,咱們來挖掘下對應的問題:編程

是否是清晰多了?安全

講解以前先說明,本文着重於解決思路, 代碼細節已經開源,不過多展開。只要掌握解決思路,剩下的都是面向 API 編程啦。bash

第一步:接入Robust

問題1:Robust 只支持Application Module

一個 apk 和一個 arr 有什麼差異?架構

aar和apk共同點

實際上沒什麼差異。咱們只須要加個開關,先將 SDK 配置爲 Application,再 Hook apk 打包的 Task,把 build 目錄下的資源壓縮爲 aar 不就好了!app

實現見:packPlugin.gradle hookAssembleAndCopyRes 方法測試

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

uploadArchives Task 配置

上傳確定是要藉助 uploadArchives 這個 Task,可是我翻遍了 uploadArchives Task 配置和官方文檔,發現並不支持自定義數據源入口的。(心拔涼拔涼)

怎麼辦?GG了?

別急,咱們還有大殺招 —— Hook Gradle Task。(後面你將會陸陸續續看到,Hook 簡直就是個萬金油~)

還不瞭解 Gradle 工做流程、Plugin、Task 的關係?見 一文應用 AOP 3、應用篇的基礎部分

咱們知道, Task 有一個重要的概念:inputs 和 outputs,Task 經過 inputs 拿到須要的參數,處理完畢以後就輸出 outputs,而下一個 Task 的 inputs 則是上一個 Task 的outputs,以下圖:

那咱們打印一下 uploadArchives 的依賴 Task 和對應的輸入輸出,看下有什麼線索(福爾摩斯.jpg)

bundleRelease Task 就是和打包相關的 Task 了,咱們打印下這個 Task 的輸出和輸入:

誒,能不能這樣,hook 這個Task,把咱們插樁的 jar 複製粘貼覆蓋這個輸入源的 classes.jar?!好一招移花接木,就這麼辦!

實現見:packPlugin.gradle hookBundle方法

問題3:資源 ID 衝突

接入後,測試跑過來:誒?怎麼 sdk 裏的 layout 都不顯示了。

哈?啥狀況

反編譯一看:

不一樣打包方式資源比對.png

好傢伙,用 application 方式打包的 aar 資源 id 變成常量了。

這會有什麼問題呢?

在 ADT 14以前,不管是主項目仍是庫項目,資源 ID 統一被定義爲 final 類型的靜態變量。若是代碼中使用了 ID,就至關於使用一個常量,編譯器的語法優化會將引用替換成具體的十六進制數。這樣形成的結果:主項目編譯時一旦發現資源 ID 衝突,就會爲庫項目的資源分配新 ID,這樣庫項目裏引用資源文件的代碼都須要從新編譯。

若是 lib R 類中的資源 ID 僅被 static 修飾,就保留了變量。當資源ID發送衝突時,主項目 R 類不變,修改庫項目 R 類中的變量,庫項目已經編譯過的代碼仍有效。

因此 Android 設計 application 的 R 類都是靜態常量,lib 的 R 類都是靜態變量。

用過 BufferKnife 的同窗都知道,在 libary 中使用 switch 須要用 R2.XXX,那不就會出現常量 R2 和業務方 R 資源衝突的問題嗎?J 神是怎麼解決的?請看:AST 抽象語法樹 —— 最輕量級的AOP方法

能咋整?

難道咱們要根據 0x7f… 反查 R.XX.XX 而後再一一把代碼替換掉?要不咱們強制改下插樁 aar 的資源 id 值從而避免衝突?默認值是0x7F...改爲0x02~0x7E?

這麼多個方案在我腦海中繞了一圈,一拍大腿,不用這麼複雜呀!

既然 R 文件的變量修飾符變了,那咱們改回來唄!

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

...

:sdk:generateReleaseBuildConfig //會生成 releasse下的 資源和源碼 包括BuildConfig  在build/generate/source/buildConfig/release  依賴checkReleaseManifest
:sdk:generateReleaseResValues //準備resource的 values文件  
:sdk:generateReleaseResources //準備 資源文件 
: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  //生成 混淆文件 運行混淆規則
:sdk:androidJavadocsPicked
:sdk:copyMappingTask  //複製 mapping文件  
:sdk:androidJavadocsJar //生成 Javadocs的Jar文件
:sdk:androidSourcesJar //生成 Java源碼的 Jar文件
...

複製代碼

咱們看到 generateReleaseSources Task 負責生成R文件,那咱們寫個 Gradle 腳本,hook 住前一個的 processReleaseResources Task,修改修飾符,so easy,hook 大法好!

實現見:packPlugin.gradle hookBuild方法

是否是以爲解決方案超簡單?有時候難的不是實現,而是你能不能找到切入點。

動態修改 resId 小擴展:

第二步:補丁下發

補丁下載、加載和上傳與通常的 app 熱更無異,因此咱們少些筆墨。

補丁的存儲咱們採用阿里雲 OSS 對象存儲:一種安全、低成本、高可靠的雲存儲服務,客戶端、服務端、阿里雲三者關係以下:

直接上時序圖

第三步:補丁加載策略

第四步:補丁上傳

這裏有一點須要注意:自定義 Gradle Task 只支持引入純 Java 庫,不支持Android庫。

第五步:補丁失效機制

1. 全局異常捕獲不是最優解

一說到捕獲異常,下意識第一反應:使用全局異常捕獲器呀!

誒誒誒,那你就掉進經驗坑裏了!

咱們思考下,如上圖一共三步,最重要的一步是:確認是補丁引發的異常,若是使用全局異常捕獲器,意味着:

  1. 咱們須要比對堆棧信息。可是補丁能夠獲取類,沒法獲取到修復的方法名,並且堆棧比較很是低效,誤判率也高。

  1. 補丁修復了 A + B,A 修復引發異常,整個補丁自動失效,B 修復也失效了。

能不能只讓 A 失效呢?能不能高效確認是補丁引發的異常呢?

2. 在Robust源碼中找找靈感

咱們看看 robust.xml 的配置,一個是否自動捕獲異常的配置映入眼簾。

來,在 Robust 全局搜索下 catchReflectException 關鍵字,發現插樁工廠類處理簡單粗暴,直接拼接了 try-catch。

生成的補丁代碼以下:

3. 確認改庫的方式

那咱們利用 try-catch,改造一下 Robust 的插樁吧。

  1. robust.xml 增長配置項,支持 rollbackWhenException 配置;
  2. 初始化時加載 XML 配置項;
  3. 新建回滾監聽器類,供業務方監聽;
  4. 插樁時,若開啓此配置,則插入 try-catch,且 catch 裏通知回滾監聽器。

每一個補丁方法都有惟一 id 咱們利用此 id 來記錄回滾狀態

對 Robust 的詳細改造MR,感興趣的能夠看看

開發階段終於結束了,歇會兒~

第六步:數據分析

咱們先看看咱們最關心的指標是哪些,再倒推出詳細的埋點項。

其中的接口失敗率和下載失敗率,咱們還應區別錯誤緣由:

後記

  1. SDK 熱修復方案少人作 ≠ 難作,考驗的就是拆分問題、細化問題的能力
  2. 大膽假設,細心驗證

以上的實現我已經封裝成庫 SDKHotFix,SDK 開發者只需 5 分鐘便可讓本身的 SDK 擁有熱修復的能力,歡迎 star !

最後,附上整個思惟導圖:

本篇完成耗時 15 個番茄鍾(450 分鐘)


我是 FeelsChaotic,一個寫得了代碼 p 得了圖,剪得了視頻畫得了畫的程序媛,致力於追求代碼優雅、架構設計和 T 型成長。

歡迎關注 FeelsChaotic 的簡書掘金,若是個人文章對你哪怕有一點點幫助,歡迎 ❤️!你的鼓勵是我寫做的最大動力!

最最重要的,請給出你的建議或意見,有錯誤請多多指正!

相關文章
相關標籤/搜索