Android 插件化和熱修復知識梳理

原文鏈接:https://www.jianshu.com/p/704cac3eb13d

概述

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

插件化和熱修復

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

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

宿主: 就是當前運行的APP
插件: 相對於插件化技術來說,就是要加載運行的apk類文件
補丁: 相對於熱修復技術來說,就是要加載運行的.patch,.dex,*.apk等一系列包含dex修復內容的文件。

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

Android插件化技術的典型應用

上圖就是對Android插件化和熱修復之間關係的體現。據我所知,在某些開發團隊中,會把熱修復的技術,作爲在APP端部署日常活動的功能來用。雖然,實際效果來看是沒有問題的,但長期使用還是值得商榷的。

早期很多應用的動態換膚功能,就是參考了Android 插件化的技術,最早的新浪微博夜間模式就是通過下載一個夜間模式的apk文件完成,當時做爲開發者的自己,感覺很高級。關於動態加載的應用,其實有很多可以擴展的思路,比如特定節日的促銷活動,逃避審覈機制的動態廣告加載都是Android插件化技術可以考慮的實現,更多內容可以參考Android動態加載技術 簡單易懂的介紹方式

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

插件化

發展歷程及現狀

關於插件化技術的起源可以追溯到5年前

  • 2012年的 AndroidDynamicLoader ,他的原理是動態加載不同的Fragment實現UI替換,可以說是開山鼻祖了,但是這種方案可擴展性不強。

  • 再到後來出現了23Code,他可以直接下載一個自定義控件的demo,並且運行起來。

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

    • 如何管理插件內Activity的生命週期: 使用 DLProxyActivity 採用靜態代理的方式去調用插件中Activity的生命週期方法。
    • 如何加載插件內的資源文件:通過反射調用AssetManager 中到的addAssetPath方法就可以將特定路徑的資源加載到系統內存中使用。

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

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

  • 2015年 DynamicAPK 這個就……,貌似因爲License的原因已經完全不更新了。

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

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

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

熱修復

相較於插件化,熱修復技術的使用更加的頻繁,因爲這項技術切實關切到我們實際開發的產品,能夠更快速更便捷的修復線上bug,才能帶來更好的用戶體驗。因此下面就結合熱修復的原理了解一下熱修復的使用及發展現狀。

以下所有分析源自熱修複相關文章,這裏只是把結論整理了出來。具體分析就不再拾人牙慧了,對實現細節有興趣的同學可以查看相應的鏈接。

類加載原理

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

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

這兩個類都是繼承自BaseDexClassLoader,我們可以看一下BaseDexClassLoader的構造函數。

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

這個構造函數只做了一件事,就是通過傳遞進來的相關參數,初始化了一個DexPathList對象。DexPathList的構造函數,就是將參數中傳遞進來的程序文件(就是補丁文件)封裝成Element對象,並將這些對象添加到一個Element的數組集合dexElements中去

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

對於ClassLoader加載機制及雙親委託機制的分析可以參考Android解析ClassLoader(一)Java中的ClassLoader

這裏需要明白的一點是對於一個ClassLoader(類加載器)來說,將一個具體的類(class)加載到內存中其實是由虛擬機完成的,對於開發者來說,我們關注的重點應該是如何去找到這個需要加載的類

假設我們現在要去查找一個名爲name的class,那麼DexClassLoader將通過以下步驟實現:

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

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

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

有了上面的思路,其實我們就可以自己動手去實現一個簡單的熱修復框架了。這裏推薦一篇
熱修復——深入淺出原理與實現,文中作者深入分析了熱修復原理,並基於以上原理實現了一個基礎的熱修復框架,實現過程分析的非常細緻深入,非常適合做爲熱修復入門原理的瞭解。

QQ 空間超級補丁方案

看完上面的原理,是不是覺得熱修復很簡單,沒什麼可研究的呢?其實不然,Java是一門面向對象的語言,我們使用的類會有繼承關係,會相互依賴引用。同時Android虛擬機和常規的JVM 不同,加載的並不是.class而是dex(準確的來說是經過優化的odex),在這樣一個過程中,勢必會有一些新的問題值得我們去關注。這個問題就是的CLASS_ISPREVERIFIED,什麼意思呢。

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

以上內容摘自Android熱修復技術——QQ空間補丁方案解析(2)

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

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

Tinker

QQ空間超級補丁,“超級補丁”很多情況下意味着補丁文件很大,而將這樣一個大文件夾加載在內存中構建一個Element對象,插入到數組最前端是需要耗費時間的,無疑會印象應用啓動的速度。因此Tinker 提出了另外一種思路。

image

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

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

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

當然,需要注意的是,patch.dex和原先的class.dex 合併的時候需要新的進程去完成,同時考慮的現在大多數應用的規模,multidex已經是很常見的事情了,因此多個dex 之間的合併策略及成功率,都是在使用Tinker時需要考慮的問題。

關於Tinker 更多細節可以參考 微信Android熱補丁實踐演進之路

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

HotFix

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

HotFix(即AndFix),是在AndFix 的基礎之上提供了補丁安全服務及版本管理等相關內容,方便廣大的開發人員使用。

AndFix 提供了一種運行時在Native修改Filed指針的方式,實現方法的替換,達到即時生效無需重啓,對應用無性能消耗的目的。

AndFix 原理

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

由於他是Native層操作,因此如果我們在Java層中新增字段,或者是修改類的方法,他是無能爲力的。同時由於Android在國內變成了安卓,各大手機廠商定製了自己的ROM,所以很多底層實現的差異,導致AndFix的兼容性並不是很好。

Sophix

阿里推出業界首個非侵入式熱修復方案Sophix,顛覆移動端傳統發版更新流程!

這是我第一次瞭解到Sophix時看到的文章標題原文鏈接;對於技術類的文章來說,敢於使用顛覆這兩個字,要麼是標題黨;要麼就是真的很有貨。

Sophix 可以說是博採衆長,前面提到的Tinker及AndFix 都在某一方面存在缺陷。因此Sophix 便取長補短,採用全量替換的思路,從一種更高的層次實現了熱修復。這貌似也是事物發展的一貫規律,後來的新生事物總結前人的經驗教訓,吸收好的思想,變得更好。

關於Sophix 的原理看了很多篇文章,感覺這篇乾貨滿滿,Android熱修復方案介紹分析的不錯,有興趣的可以看一下。

總的來說,Sophix應該是現有最成熟的熱修復方案了。

其他及總結

當然就熱修復的實現,各個大廠還有各自的實現,比如餓了嗎的Amigo,美團的Robust,實現及優缺點各有差異,但總的來說就是兩大類

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

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

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

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


好了,插件化和熱修復知識就梳理到這裏了。

相關內容

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



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章