Android 插件化和熱修復(fù)知識梳理

概述

在Android開發(fā)中,插件化和熱修復(fù)的話題越來越多的被大家提及,同時隨著技術(shù)的迭代,各種框架的發(fā)展更新,插件化和熱修復(fù)的框架似乎已經(jīng)日趨成熟,許多開發(fā)者也把這兩項技術(shù)運用到實際開發(fā)協(xié)作和正式的產(chǎn)品當(dāng)中。因此,我們勢必需要了解一下這兩門技術(shù)。

插件化和熱修復(fù)

首先需要明確的一點,插件化和熱修復(fù)不是同一個概念,雖然站在技術(shù)實現(xiàn)的角度來說,他們都是從系統(tǒng)加載器的角度出發(fā),無論是采用hook方式,亦或是代理方式或者是其他底層實現(xiàn),都是通過“欺騙”Android 系統(tǒng)的方式來讓宿主正常的加載和運行插件(補(bǔ)?。?/strong>中的內(nèi)容;但是二者的出發(fā)點是不同的。插件化顧名思義,更多是想把需要實現(xiàn)的模塊或功能當(dāng)做一個獨立的提取出來,減少宿主的規(guī)模,當(dāng)需要使用到相應(yīng)的功能時再去加載相應(yīng)的模塊。熱修復(fù)則往往是從修復(fù)bug的角度出發(fā),強(qiáng)調(diào)的是在不需要二次安裝應(yīng)用的前提下修復(fù)已知的bug。

為了方便敘述,做以下稱謂約定:

宿主: 就是當(dāng)前運行的APP
插件: 相對于插件化技術(shù)來說,就是要加載運行的apk類文件
補(bǔ)?。?相對于熱修復(fù)技術(shù)來說,就是要加載運行的.patch,.dex,*.apk等一系列包含dex修復(fù)內(nèi)容的文件。

以下提到內(nèi)容中的宿主和插件(補(bǔ)丁),均是上述含義,不再贅述。

Android插件化技術(shù)的典型應(yīng)用

上圖就是對Android插件化和熱修復(fù)之間關(guān)系的體現(xiàn)。據(jù)我所知,在某些開發(fā)團(tuán)隊中,會把熱修復(fù)的技術(shù),作為在APP端部署日?;顒拥墓δ軄碛谩km然,實際效果來看是沒有問題的,但長期使用還是值得商榷的。

早期很多應(yīng)用的動態(tài)換膚功能,就是參考了Android 插件化的技術(shù),最早的新浪微博夜間模式就是通過下載一個夜間模式的apk文件完成,當(dāng)時做為開發(fā)者的自己,感覺很高級。關(guān)于動態(tài)加載的應(yīng)用,其實有很多可以擴(kuò)展的思路,比如特定節(jié)日的促銷活動,逃避審核機(jī)制的動態(tài)廣告加載都是Android插件化技術(shù)可以考慮的實現(xiàn),更多內(nèi)容可以參考Android動態(tài)加載技術(shù) 簡單易懂的介紹方式

下面就從插件化技術(shù)的發(fā)展源頭,逐步敘述一下二者的發(fā)展歷程及現(xiàn)狀,了解一下時至今日,熱修復(fù)框架的發(fā)展到了各種地步,總體梳理一下熱修復(fù)的原理,對現(xiàn)有的框架有一個了解。

插件化

發(fā)展歷程及現(xiàn)狀

關(guān)于插件化技術(shù)的起源可以追溯到5年前

  • 2012年的 AndroidDynamicLoader ,他的原理是動態(tài)加載不同的Fragment實現(xiàn)UI替換,可以說是開山鼻祖了,但是這種方案可擴(kuò)展性不強(qiáng)。

  • 再到后來出現(xiàn)了23Code,他可以直接下載一個自定義控件的demo,并且運行起來。

  • 2014年一個里程碑式的年份,任玉剛(俗稱主席)發(fā)布了dynamic-load-apk,也叫做DL。在這個框架里提供了兩個很重要的思路:

    • 如何管理插件內(nèi)Activity的生命周期: 使用 DLProxyActivity 采用靜態(tài)代理的方式去調(diào)用插件中Activity的生命周期方法。
    • 如何加載插件內(nèi)的資源文件:通過反射調(diào)用AssetManager 中到的addAssetPath方法就可以將特定路徑的資源加載到系統(tǒng)內(nèi)存中使用。

    以上兩點,可以說是非常有意義的,尤其是第二點關(guān)于插件資源的記載,是后期出現(xiàn)的許多框架的參考思路。這個框架也有一些局限,不支持插件內(nèi)Service、BroadcastReceiver等需要注冊才能使用的組件,同時插件apk也需要按照其開發(fā)規(guī)范來實現(xiàn),總體來說還是有一定的成本,但無論怎樣都是一個很有價值的框架。(話說這個框架貌似已經(jīng)不再維護(hù)了,最近一次關(guān)于代碼的更新都是2年前了,o(╥﹏╥)o)。

  • 2015年 DroidPlugin
    DroidPlugin 是Andy Zhang在Android系統(tǒng)上實現(xiàn)了一種新的 插件機(jī)制 :它可以在無需安裝、修改的情況下運行APK文件,此機(jī)制對改進(jìn)大型APP的架構(gòu),實現(xiàn)多團(tuán)隊協(xié)作開發(fā)具有一定的好處。 這段話是DroidPlugin在Github README 文檔中的介紹。這款來自360的插件化框架.

  • 2015年 DynamicAPK 這個就……,貌似因為License的原因已經(jīng)完全不更新了。

  • 2017 RePlugin 這是360 開源的插件化框架,按照他自己的說法,相較于其他框架,他對系統(tǒng)的hook只有一處,那就是ClassLoader,這樣從理論來說,貌似會有更好的穩(wěn)定性。

  • 2017年 atlas這個是阿里今年剛剛開源的插件化開發(fā)框架,可以說是非常強(qiáng)大;具體原理參考詳解 Atlas 框架原理;還沒有用過。

  • Small 最后再說一下Small,個人感覺Small 所提供了一種比插件化更高層次的概念,組件化;把一個完整的APP看成是由許多可以復(fù)用模塊組件組成(這個有點像React Native的開發(fā)理念);開發(fā)起來像是搭積木的感覺。有興趣的可以去Small官網(wǎng)了解一下。

熱修復(fù)

相較于插件化,熱修復(fù)技術(shù)的使用更加的頻繁,因為這項技術(shù)切實關(guān)切到我們實際開發(fā)的產(chǎn)品,能夠更快速更便捷的修復(fù)線上bug,才能帶來更好的用戶體驗。因此下面就結(jié)合熱修復(fù)的原理了解一下熱修復(fù)的使用及發(fā)展現(xiàn)狀。

以下所有分析源自熱修復(fù)相關(guān)文章,這里只是把結(jié)論整理了出來。具體分析就不再拾人牙慧了,對實現(xiàn)細(xì)節(jié)有興趣的同學(xué)可以查看相應(yīng)的鏈接。

類加載原理

說起熱修復(fù)就不得不提類的加載機(jī)制,和常規(guī)的JVM類似,在Android中類的加載也是通過ClassLoader來完成,具體來說就是PathClassLoader 和 DexClassLoader 這兩個Android專用的類加載器,這兩個類的區(qū)別如下:

  • PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)中的apk文件(/data/app目錄),是Android默認(rèn)使用的類加載器。
  • DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,也就是我們一開始提到的補(bǔ)丁。

這兩個類都是繼承自BaseDexClassLoader,我們可以看一下BaseDexClassLoader的構(gòu)造函數(shù)。

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

這個構(gòu)造函數(shù)只做了一件事,就是通過傳遞進(jìn)來的相關(guān)參數(shù),初始化了一個DexPathList對象。DexPathList的構(gòu)造函數(shù),就是將參數(shù)中傳遞進(jìn)來的程序文件(就是補(bǔ)丁文件)封裝成Element對象,并將這些對象添加到一個Element的數(shù)組集合dexElements中去。

ClassLoaer 的加載機(jī)制是一種特別聰明的方式,雙親委托機(jī)制,在這種機(jī)制下,一個Class只會被加載一次。

對于ClassLoader加載機(jī)制及雙親委托機(jī)制的分析可以參考Android解析ClassLoader(一)Java中的ClassLoader。

這里需要明白的一點是對于一個ClassLoader(類加載器)來說,將一個具體的類(class)加載到內(nèi)存中其實是由虛擬機(jī)完成的,對于開發(fā)者來說,我們關(guān)注的重點應(yīng)該是如何去找到這個需要加載的類

假設(shè)我們現(xiàn)在要去查找一個名為name的class,那么DexClassLoader將通過以下步驟實現(xiàn):

  • 在DexClassLoader的findClass 方法中通過一個DexPathList對象findClass()方法來獲取class
  • 在DexPathList的findClass 方法中,對之前構(gòu)造好dexElements數(shù)組集合進(jìn)行遍歷,一旦找到類名與name相同的類時,就直接返回這個class,找不到則返回null。

總的來說,通過DexClassLoader查找一個類,最終就是就是在一個數(shù)組中查找特定值的操作。

綜合以上所有的觀點,我們很容易想到一種非常簡單粗暴的熱修復(fù)方案。假設(shè)現(xiàn)在代碼中的某一個類或者是某幾個類有bug,那么我們可以在修復(fù)完bug之后,可以將這些個類打包成一個補(bǔ)丁文件,然后通過這個補(bǔ)丁文件封裝出一個Element對象,并且將這個Element對象插到原有dexElements數(shù)組的最前端,這樣當(dāng)DexClassLoader去加載類時,優(yōu)先會從我們插入的這個Element中找到相應(yīng)的類,雖然那個有bug的類還存在于數(shù)組中后面的Element中,但由于雙親加載機(jī)制的特點,這個有bug的類已經(jīng)沒有機(jī)會被加載了,這樣一個bug就在沒有重新安裝應(yīng)用的情況下修復(fù)了。

有了上面的思路,其實我們就可以自己動手去實現(xiàn)一個簡單的熱修復(fù)框架了。這里推薦一篇
熱修復(fù)——深入淺出原理與實現(xiàn),文中作者深入分析了熱修復(fù)原理,并基于以上原理實現(xiàn)了一個基礎(chǔ)的熱修復(fù)框架,實現(xiàn)過程分析的非常細(xì)致深入,非常適合做為熱修復(fù)入門原理的了解。

QQ 空間超級補(bǔ)丁方案

看完上面的原理,是不是覺得熱修復(fù)很簡單,沒什么可研究的呢?其實不然,Java是一門面向?qū)ο蟮恼Z言,我們使用的類會有繼承關(guān)系,會相互依賴引用。同時Android虛擬機(jī)和常規(guī)的JVM 不同,加載的并不是.class而是dex(準(zhǔn)確的來說是經(jīng)過優(yōu)化的odex),在這樣一個過程中,勢必會有一些新的問題值得我們?nèi)リP(guān)注。這個問題就是的CLASS_ISPREVERIFIED,什么意思呢。

  • 在apk安裝的時候系統(tǒng)會將dex文件優(yōu)化成odex文件,在優(yōu)化的過程中會涉及一個預(yù)校驗的過程
  • 如果一個類的static方法,private方法,override方法以及構(gòu)造函數(shù)中引用了其他類,而且這些類都屬于同一個dex文件,此時該類就會被打上CLASS_ISPREVERIFIED
  • 如果在運行時被打上CLASS_ISPREVERIFIED的類引用了其他dex的類,就會報錯
  • 正常的分包方案會保證相關(guān)類被打入同一個dex文件
  • 想要使得patch可以被正常加載,就必須保證類不會被打上CLASS_ISPREVERIFIED標(biāo)記。而要實現(xiàn)這個目的就必須要在分完包后的class中植入對其他dex文件中類的引用

以上內(nèi)容摘自Android熱修復(fù)技術(shù)——QQ空間補(bǔ)丁方案解析(2)

要在已經(jīng)編譯完成后的類中植入對其他類的引用,就需要操作字節(jié)碼,慣用的方案是插樁。常見的工具有javaassist,asm等

QQ 空間補(bǔ)丁方案就是使用javaassist 插樁的方式解決了CLASS_ISPREVERIFIED的難題。

Tinker

QQ空間超級補(bǔ)丁,“超級補(bǔ)丁”很多情況下意味著補(bǔ)丁文件很大,而將這樣一個大文件夾加載在內(nèi)存中構(gòu)建一個Element對象,插入到數(shù)組最前端是需要耗費時間的,無疑會印象應(yīng)用啟動的速度。因此Tinker 提出了另外一種思路。

image

圖片源自https://github.com/Tencent/tinker

Tinker的思路是這樣的,通過修復(fù)好的class.dex 和原有的class.dex比較差生差量包補(bǔ)丁文件patch.dex,在手機(jī)上這個patch.dex又會和原有的class.dex 合并生成新的文件fix_class.dex,用這個新的fix_class.dex 整體替換原有的dexPathList的中的內(nèi)容,可以說是從根本上把bug給干掉了。

Tinker 提供的思路可以說是非常新奇,也非常值得我們?nèi)W(xué)習(xí)。上圖中過程看似簡單,但其實具體實現(xiàn)起來還真的不簡單。你有想過兩個.dex 是如何比較得出差異化文件patch.dex 的嗎?有興趣的同學(xué)可以看看鴻翔的這篇分析Android 熱修復(fù) Tinker 源碼分析之DexDiff / DexPatch

當(dāng)然,需要注意的是,patch.dex和原先的class.dex 合并的時候需要新的進(jìn)程去完成,同時考慮的現(xiàn)在大多數(shù)應(yīng)用的規(guī)模,multidex已經(jīng)是很常見的事情了,因此多個dex 之間的合并策略及成功率,都是在使用Tinker時需要考慮的問題。

關(guān)于Tinker 更多細(xì)節(jié)可以參考 微信Android熱補(bǔ)丁實踐演進(jìn)之路

Tinker 提供的文檔及example非常完善,對于有興趣接入的開發(fā)者可以說是非常友好了,但總體來說接入過程還是有些復(fù)雜,對整個項目的侵入還是較強(qiáng),Tinker是個人唯一使用過的熱修復(fù)的框架,總體來說還是不錯的,通過接入到實際應(yīng)用中,對gradle也有了新的認(rèn)識,對gradle有興趣的同學(xué),其實可以看看tinker的gradle接入方式

HotFix

以上提到的兩種方式,雖然策略有所不同,但總的來說都是從上層ClassLoader的角度出發(fā),由于ClassLoader的特點,如果想要新的補(bǔ)丁文件再次生效,無論你是插樁還是提前合并,都需要重新啟動應(yīng)用來加載新的DexPathList。這樣就無法在用戶神不知鬼不覺的情況下把bug修復(fù)了,HotFix在這方面就有絕對的優(yōu)勢了。

HotFix(即AndFix),是在AndFix 的基礎(chǔ)之上提供了補(bǔ)丁安全服務(wù)及版本管理等相關(guān)內(nèi)容,方便廣大的開發(fā)人員使用。

AndFix 提供了一種運行時在Native修改Filed指針的方式,實現(xiàn)方法的替換,達(dá)到即時生效無需重啟,對應(yīng)用無性能消耗的目的。

AndFix 原理

更多細(xì)可以參考https://github.com/alibaba/AndFix,Native層不怎么理解,就不強(qiáng)行裝逼了o(╯□╰)o。

由于他是Native層操作,因此如果我們在Java層中新增字段,或者是修改類的方法,他是無能為力的。同時由于Android在國內(nèi)變成了安卓,各大手機(jī)廠商定制了自己的ROM,所以很多底層實現(xiàn)的差異,導(dǎo)致AndFix的兼容性并不是很好。

Sophix

阿里推出業(yè)界首個非侵入式熱修復(fù)方案Sophix,顛覆移動端傳統(tǒng)發(fā)版更新流程!

這是我第一次了解到Sophix時看到的文章標(biāo)題原文鏈接;對于技術(shù)類的文章來說,敢于使用顛覆這兩個字,要么是標(biāo)題黨;要么就是真的很有貨。

Sophix 可以說是博采眾長,前面提到的Tinker及AndFix 都在某一方面存在缺陷。因此Sophix 便取長補(bǔ)短,采用全量替換的思路,從一種更高的層次實現(xiàn)了熱修復(fù)。這貌似也是事物發(fā)展的一貫規(guī)律,后來的新生事物總結(jié)前人的經(jīng)驗教訓(xùn),吸收好的思想,變得更好。

關(guān)于Sophix 的原理看了很多篇文章,感覺這篇干貨滿滿,Android熱修復(fù)方案介紹分析的不錯,有興趣的可以看一下。

總的來說,Sophix應(yīng)該是現(xiàn)有最成熟的熱修復(fù)方案了。

其他及總結(jié)

當(dāng)然就熱修復(fù)的實現(xiàn),各個大廠還有各自的實現(xiàn),比如餓了嗎的Amigo,美團(tuán)的Robust,實現(xiàn)及優(yōu)缺點各有差異,但總的來說就是兩大類

  • ClassLoader 加載方案
  • Native層替換方案

或者是參考Android Studio Instant Run 的思路實現(xiàn)代碼整體的增量更新。但這樣勢必會帶來性能的影響。

綜上所述,其實對于熱修復(fù)很難有一種十分完美的解決方案。在Android開發(fā)中,四大組件使用前需要在AndroidManifest中提前聲明,而如果需要使用熱修復(fù)的方式,無論是提前占坑亦或是動態(tài)修改,都會帶來很強(qiáng)的侵入性(因此,Sophix是不支持四大組件修復(fù)的,這也是其非侵入性設(shè)計理念無法避免的事情了,不知道以后會不會有新的辦法)。再者Android碎片化的問題,對熱修復(fù)方案的適配也是一個考驗。通過查看幾大以開源在Github上的熱修復(fù)方案,在issue中可以看到提到最多的問題還是兼容性。

因此,面對實際的開發(fā),選擇使用或者說選擇哪種方案,必須符合實際的應(yīng)用的場景,一句話,沒有最好的,只有合適的。


好了,插件化和熱修復(fù)知識就梳理到這里了。

相關(guān)內(nèi)容

Android動態(tài)加載技術(shù) 簡單易懂的介紹方式
Android 插件化的 過去 現(xiàn)在 未來
ZeusPlugin: 掌閱APP插件補(bǔ)丁
Android插件化:從入門到放棄
Android 全面插件化 RePlugin 流程與源碼解析
《全面插件化——RePlugin的使命》
詳解 Atlas 框架原理
熱修復(fù)——深入淺出原理與實現(xiàn)
Android解析ClassLoader(一)Java中的ClassLoader
Android熱修復(fù)技術(shù)——QQ空間補(bǔ)丁方案解析(2)
Android熱修復(fù)技術(shù)——QQ空間補(bǔ)丁方案解析(3)
微信Android熱補(bǔ)丁實踐演進(jìn)之路
Android熱修復(fù)技術(shù)總結(jié)
干貨滿滿,Android熱修復(fù)方案介紹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容