Android 的插件化淺析

近一段時(shí)間由于業(yè)務(wù)上的需要 一直在做Android 插件化方面的工作,略有一些收獲,于是寫(xiě)出來(lái)和大家分享。
文章按照以下邏輯來(lái)組織:

  • 背景知識(shí)
  • 插件化可以做什么
  • 插件化的原理
  • 插件化框架應(yīng)該具備的能力

下面會(huì)分別闡述

背景知識(shí)

對(duì)于一個(gè)初創(chuàng)公司來(lái)說(shuō),可以說(shuō)插件化是無(wú)用武之地的,產(chǎn)品初期的目標(biāo)在于發(fā)展業(yè)務(wù),迭代功能,爭(zhēng)取用戶(hù),而只有產(chǎn)品和業(yè)務(wù)發(fā)展到一定規(guī)模,技術(shù)架構(gòu)遇到一定瓶頸時(shí),才會(huì)產(chǎn)生對(duì)插件化的需求。
對(duì)于Android平臺(tái)而言, 插件大致分為兩類(lèi):獨(dú)立插件非獨(dú)立插件

  • 獨(dú)立插件即為獨(dú)立的apk, 插件與app無(wú)異,插件框架更像一個(gè)沙盒容器,比如類(lèi)似Lbe的平行空間, 360手機(jī)助手 加載的均為此類(lèi)插件

  • 非獨(dú)立插件,宿主與插件間有一定的約定規(guī)范,開(kāi)發(fā)插件需要遵循制定的規(guī)則來(lái)進(jìn)行開(kāi)發(fā),具有一定的弱侵入性,這種方式主要用于產(chǎn)品內(nèi)部的業(yè)務(wù)模塊插件化解耦。

獨(dú)立插件對(duì)于技術(shù)上的要求更高,需要完整的支持四大組件的全部特性,類(lèi)似成熟的框架有360開(kāi)源的DroidPlugin, ACDD 等等, 而對(duì)于很多產(chǎn)品形態(tài)而言,更多的是需要非獨(dú)立插件式框架,解耦產(chǎn)品內(nèi)部的業(yè)務(wù)模塊,類(lèi)似的框架有 Small 本篇文章著重介紹后者。

插件化可以做什么

  • 動(dòng)態(tài)更新, 減小Apk大小,同時(shí)也可以解決MultiDex的問(wèn)題。
    將用戶(hù)可選擇使用的功能模塊做成獨(dú)立的插件動(dòng)態(tài)下發(fā),減少主APK的大小,當(dāng)然這種行為只適用于國(guó)內(nèi)市場(chǎng),google play 是不允許這種行為的。
  • 解耦主項(xiàng)目,方便不同業(yè)務(wù)的并行開(kāi)發(fā)。
    每個(gè)業(yè)務(wù)作為獨(dú)立的插件進(jìn)行開(kāi)發(fā),可以大大減輕合并不同分支帶來(lái)的工作量
  • 提升編譯效率
    從編譯整個(gè)項(xiàng)目到只需要編譯獨(dú)立的插件,尤其對(duì)于大中型項(xiàng)目來(lái)說(shuō)可以節(jié)約不少時(shí)間。 Instant-Run 雖然解決了增量編譯的問(wèn)題,但偶爾還是會(huì)遇到代碼同步后未更新的bug。

當(dāng)然,插件化也會(huì)帶來(lái)不方便的地方,比如開(kāi)發(fā)習(xí)慣上的不同,需要滿(mǎn)足一些插件框架約束條件;如果項(xiàng)目開(kāi)發(fā)到一定規(guī)模再進(jìn)行分拆業(yè)務(wù)模塊的話(huà),還會(huì)存在很大的重構(gòu)成本。

插件化原理

本質(zhì)上講插件化的原理可以概括為:動(dòng)態(tài)加載
這其中包括代碼的動(dòng)態(tài)加載資源的動(dòng)態(tài)加載,下面逐一介紹。

  • 代碼的動(dòng)態(tài)加載

    • 動(dòng)態(tài)加載dex
      ClassLoader 機(jī)制:ClassLoader是通過(guò)雙親委托模型來(lái)搜索類(lèi)的, 每個(gè)ClassLoader實(shí)例都有一個(gè)父類(lèi)加載器的引用, 當(dāng)進(jìn)程在啟動(dòng)的時(shí)候,并不會(huì)一次性加載程序所要用的所有class文件,而是根據(jù)程序的需要,通過(guò)Java的ClassLoader 來(lái)動(dòng)態(tài)加 載某個(gè)class文件到內(nèi)存當(dāng)中的,從而只有class文件被載入到了內(nèi)存之后,才能被其它c(diǎn)lass所引用。
      針對(duì)Android 平臺(tái),研究DexClassLoader的加載機(jī)制可以發(fā)現(xiàn)最終的dex文件都會(huì)存放在dexPathList 對(duì)象的Element[] 數(shù)組中,因此我們可以通過(guò)反射擴(kuò)展element數(shù)組load 多個(gè)dex. 具體參考MultiDex源碼
    • 動(dòng)態(tài)加載系統(tǒng)組件
      對(duì)于Activity Service 這類(lèi)系統(tǒng)的組件不只是一個(gè)java對(duì)象, Android還賦予了他們各自的生命周期。對(duì)于這塊的邏輯需要研究Activity、Service、Broadcast、 ContentProvider四大組件各自的啟動(dòng)過(guò)程,通過(guò)攔截關(guān)鍵的節(jié)點(diǎn)進(jìn)行組件intent的替換,各大主流的插件框架的原理大致相同,具體 hook的節(jié)點(diǎn)存在一些差異。對(duì)于這塊具體的分析可以參考weishu博客,作者在這博客對(duì)于四大組件的插件化分析比較清晰,不在此贅述。
    • 動(dòng)態(tài)加載.so 文件
      https://segmentfault.com/a/1190000004062899
  • 資源的動(dòng)態(tài)加載
    對(duì)于動(dòng)態(tài)加載插件資源這塊大體的做基本類(lèi)似,通過(guò)反射調(diào)用AssetManager中的addAssetPath 加載插件中的資源,還有另外一個(gè)問(wèn)題如何解決資源id的沖突,一個(gè)資源ID值主要包含三個(gè)部分,package段 type段 以及value段,如果不加處理很容易造成不同插件間的資源沖突, 具體原理參考 這塊的解決方案大致有兩種

    • 修改aapt的源碼,在編譯期修改package字段,保證不同插件間的packageId不同 避免沖突。
    • 在編譯后期刪除原來(lái)的R,重新生成自定義packageId的R.class。參考small的編譯工具。

插件化框架應(yīng)具備的能力

  • 穩(wěn)定 兼容性強(qiáng), 盡量減少對(duì)framework 層的hook
  • 版本管理:在插件升級(jí)后保證每個(gè)進(jìn)程加載dex的一致性。
  • 安全校驗(yàn):保證加載的插件不被篡改,可以采用非對(duì)稱(chēng)加密的方案。
  • 監(jiān)控回調(diào):當(dāng)插件加載失敗時(shí)通知宿主,不影響宿主的正常運(yùn)行。
  • 提供完整的編譯插件的工具鏈
后記

介紹了這么多,其實(shí)會(huì)發(fā)現(xiàn)目前主流的插件化框架均為國(guó)內(nèi)團(tuán)隊(duì)開(kāi)發(fā)的,而國(guó)外對(duì)于動(dòng)態(tài)更新選擇了react native 的開(kāi)發(fā)方式,為什么在插件化的技術(shù)層面國(guó)內(nèi)與國(guó)外的開(kāi)發(fā)方式存在巨大的差別,筆者贊同OasisFeng 的回答Google Play的開(kāi)發(fā)者協(xié)議不允許繞開(kāi)Google Play Store進(jìn)行代碼層面的更新,并非不想使用這個(gè)技術(shù) 。正式由于國(guó)內(nèi)app的分發(fā) 脫離了google play的生態(tài)才造成了插件化百家爭(zhēng)鳴的現(xiàn)狀, 所以說(shuō) 趨勢(shì)在哪?還請(qǐng)大家自行斟酌。

參考資料:
https://github.com/wequick/Small
https://github.com/bunnyblue/ACDD
https://github.com/DroidPluginTeam/DroidPlugin
http://weishu.me/
Android動(dòng)態(tài)加載基礎(chǔ) ClassLoader工作機(jī)制
instant-run 源碼
multi-dex 源碼

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

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

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