主流插件式框架探究

前言
目前移動端產(chǎn)品功能越來越復(fù)雜,模塊不斷增加,APK體積也不斷增長。由于Android Dalvik最初設(shè)計的問題,單個.dex文件方法數(shù)最多是65536個。因此,APK難免會遇到64K方法數(shù)限制的問題。
Google官方提供了MultiDex解決方案。但是該解決方案,有比較大的局限性。

應(yīng)用進程啟動時MultiDex需要在主線程去做DEXOPT操作,中間涉及到文件讀寫、文件驗證、數(shù)據(jù)復(fù)制、反射調(diào)用等,非常耗時,應(yīng)用啟動將變得很慢,甚至出現(xiàn)ANR。

由于Dalvik LinearAlloc的BUG,Android 4.0(API level 14)之前的系統(tǒng),可能導(dǎo)致應(yīng)用啟動失敗。應(yīng)用上線前,需要針對低版本系統(tǒng)做嚴(yán)格測試。另外Android 5.0(API level 21)之前的系統(tǒng),Dalvik加載類的堆內(nèi)存的大小有限制,可能導(dǎo)致使用了MultiDex的應(yīng)用在分配內(nèi)存時,會出現(xiàn)崩潰。

引入MultiDex時,會存在多個dex文件,應(yīng)用啟動時,先加載了主dex文件,其它的dex文件在Application的attachBaseContext()方法中加載。Android構(gòu)建工具幫我們分析處理了一部分依賴,將應(yīng)用啟動需要的類都放入了主dex文件中。但是項目中我們自己引入的第三方庫或者底層的native代碼,可能還有一些類的依賴構(gòu)建工具無法處理,會導(dǎo)致應(yīng)用啟動時找不到相關(guān)類而崩潰。

綜上所述,MultiDex方案并不完善,將直接應(yīng)用啟動變慢和崩潰的問題。為了解決這些問題,我們引入插件的概念。

技術(shù)關(guān)鍵點
插件框架的基本形式就是將一個apk中的不同功能模塊進行拆分,打包到不同的dex或者apk文件中,而主工程是一個提供了加載模塊dex和apk的框架。插件框架需要著重解決如下幾個問題:

代碼的動態(tài)加載。Android系統(tǒng)提供了DexClassLoader,可以用來動態(tài)加載apk、dex和jar文件。加載順序決定了起作用的類。這一點可做用于熱修復(fù)功能。

四大組件的動態(tài)注冊和生命周期管理。通過插樁的方式做動態(tài)代理??梢栽诙鄠€層級做Hack,不同的層級Hack時需要做的適配工作難度也不同。一般都需要反射替換Instrumentation,并對一些關(guān)鍵方法使用動態(tài)代理,以達到“欺上瞞下”的目的。

開源框架簡介
目前,已經(jīng)有多種插件框架的實現(xiàn)方案,不同的方案都存在各自的優(yōu)缺點。以下簡單介紹幾個主要的插件框架的基本原理和優(yōu)缺點。

DroidPlugin
DroidPlugin是360手機助手實現(xiàn)的一種插件框架,可以直接加載運行第三方的獨立apk,不需要對apk進行修改或安裝,插件 apk可以獨立運行。其特性主要有以下幾點:

通過Stub插樁的方式,預(yù)先靜態(tài)注冊多個不同屬性的Activity、Service、Receiver和Provider,動態(tài)代理。一個插件一個ClassLoader,插件之間代碼完全隔離,互不影響。
資源動態(tài)加載。一個插件一個AssetManager,插件之間資源完全隔離,互不影響。
Hack了幾乎所有的通過Context獲取到的系統(tǒng)服務(wù)(AM、PM等),Hack了和應(yīng)用進程密切相關(guān)的Instrucmentation、ApplicationThread、ActivityThread等相關(guān)類??蚣苤泻艽笠徊糠执a都是與Hack有關(guān)。
不同的插件APK運行于不同的進程,互不干擾。插件之間支持aidl進程間通信。提供進程管理機制。
存在局限的地方:

由于進程隔離的原因,宿主和插件分別使用了不同的ClassLoader和AssetManager,插件和宿主、插件和插件之間資源和代碼不能共享。
插件apk中所有的Intent Filter無法工作。要啟動插件中的四大組件,必須要指定包名和類名。
RemoteView支持不是太好,不支持自定義資源的Notification。
帶有native庫的插件支持不是太好,可能存在異常崩潰。
Hack了很多系統(tǒng)類,兼容性方面可能會隱患較多。
綜合以上信息,DroidPlugin比較適合主程序只提供入口,而插件apk對宿主不存在依賴,插件之間無太多通信要求的場景。

Small
Small的核心是輕巧,支持跨平臺(支持Android、iOS、Web)。其特性主要有以下幾點:

輕度Hack,代碼相對很少,邏輯簡單。插件可以是apk文件、so庫、lib庫。
提供了Gradle插件輔助構(gòu)建項目,修改各個插件資源id,避免資源id重復(fù)問題。
通過Stub插樁的方式,預(yù)先靜態(tài)注冊多個Activity,動態(tài)代理。
資源在編譯時使用自定義gradle插件的方式區(qū)分開。多個插件共享進程,共享代碼,共享資源。同一個ClassLoader加載所有插件。
使用uri的方式啟動插件、傳遞參數(shù),比較靈活。
基于Apache協(xié)議開源。
插件配置可以存放在服務(wù)端,demo中提供了簡單的版本管理、在線更新機制。
其主要問題也比較明顯:

目前不支持懶加載模式。所有插件都是在應(yīng)用進程啟動時一次性加載的,內(nèi)存占用較大。如果要支持按需加載,需要自己做改進。
除了Activity,其它三大組件不支持插件化。作者沒有支持其它三大組件的意向。插件中所有的Intent Filter無法工作。
插件更新后不是實時生效,而是在類被重新加載的時候生效。生效時間和類加載時機一致。
Small核心類代碼凌亂,部分代碼嚴(yán)重影響程序穩(wěn)定性,還附帶了部分業(yè)務(wù)邏輯,擴展性差。
綜合以上信息,Small比較適合插件依賴于主程序,并且插件和主程序之間存在通信的場景,并且程序不宜規(guī)模太大。

DynamicAPK
DynamicAPK是攜程的開源項目,但是目前作者已經(jīng)不再維護。其主要特點如下:

同一個ClassLoader加載所有的插件代碼,通過反射調(diào)整加載順序,進而實現(xiàn)了熱修復(fù)功能。一次性加載所有插件,未實現(xiàn)按需加載。
所有的插件資源都加載到了一個AssetManager中,并通過反射動態(tài)替換了Context獲取的Resource。
Activity等組件需要在宿主程序中預(yù)先注冊。
直接改造aapt的源碼,通過動態(tài)配置的方式確定插件資源id的PP字段,避免id重復(fù)。
ACDD
ACDD的原名是OpenAtlas,目前作者也已經(jīng)不再維護。攜程的DynamicAPK有相當(dāng)一部分代碼借鑒了此項目。其主要特點如下:

自定義ClassLoader,擴展了雙親委派機制,用于加載插件代碼。不同插件使用不同的ClassLoader,實現(xiàn)了代碼的隔離,也實現(xiàn)了通用代碼的共享。使用額外的配置文件管理插件和插件之間的依賴關(guān)系,實現(xiàn)了懶加載機制。
所有的插件資源都加載到了一個AssetManager中,并通過反射動態(tài)替換了Context獲取的Resource。實現(xiàn)了懶加載機制。
Activity等組件需要在宿主程序中預(yù)先注冊。
代碼和相關(guān)文檔中未提及資源id重復(fù)的問題是如何修正的。其內(nèi)部的build tools則對所有插件和配置文件做了一個簡單md5校驗,未發(fā)現(xiàn)資源id相關(guān)的代碼。應(yīng)該是使用aapt打包時,指定了package id。
DynamicLoadApk
大概算是最早開源的插件化框架了,github上已經(jīng)不怎么活躍了。其主要特點如下:

支持插件對主程序無調(diào)用、接口調(diào)用、完全調(diào)用3種集成方式。
支持Activity、FragmentActivity、Service組件,并且組件不需要在主程序的AndroidManifest中聲明。
將Activity的生命周期抽象出接口,在代理類里實現(xiàn)對Activity生命周期的管理,并在代理類里保存原Activity的引用(that應(yīng)用)。
DexClassLoader加載插件代碼,AssetManager管理插件資源。插件之間資源和代碼可以做到相互隔離。
自定義Intent和API實現(xiàn)對插件Activity的啟動。
主要問題如下:

強侵入性,組件內(nèi)很多API需要使用that調(diào)用,并需要繼承特定的類,啟動組件需要調(diào)用私有API。
插件的集成方式一旦確定將不能更改。不同的集成方式,編譯配置也不相同。
插件之間資源和代碼無法共享。啟動插件中的組件必須使用包名+類名的方式。
VirtualApp
VirtualApp是一個App虛擬化引擎(簡稱VA)。VA在App內(nèi)部創(chuàng)建一個虛擬空間,App可以在虛擬空間內(nèi)任意安裝、卸載、啟動apk。而這一切都與外部隔離,如同一個黑盒。目前已經(jīng)有較多的應(yīng)用使用了VA,并支持多種方式的應(yīng)用加固。其主要特性如下:

支持四大組件,插件中的靜態(tài)注冊的Receiver會轉(zhuǎn)為動態(tài)注冊。
插件和插件之間高度隔離,運行于獨立的進程。
插件之間支持aidl通信,支持Intent隱式調(diào)用。
IO重定向,插件相關(guān)的文件存放于主程序私有文件目錄下。
通過動態(tài)代理的方式,比DroidPlugin Hack了更多的系統(tǒng)類和方法,模擬了一個更完善的小型系統(tǒng)。
項目比較活躍,問題的解答和bug修復(fù)相對較快。
主要缺點如下:

Hack了很多系統(tǒng)類和方法,雖然比DroidPlugin穩(wěn)定,但還是存在很多兼容性方面的問題。
使用GPL3.0的授權(quán),有開源感染的風(fēng)險。
插件隔離的原因,不支持代碼和資源的共享。
Atlas
Atlas是阿里在17年3月份才開源的,目前還處于快速迭代開發(fā)期,文檔和demo都還不完善,部分細(xì)節(jié)的功能還有不少的BUG。

Atlas主要的特性有以下幾點:

單進程運行,更像一個組件框架,包含host、local bundle和remote bundle。
所有bundle的代碼和資源都是動態(tài)加載,并且支持按需加載。
自定義兩個ClassLoader,一個實現(xiàn)類加載時的路由邏輯,另一個則主要負(fù)責(zé)加載bundle中的類和其依賴的bundle中的類。
自定義ResourceManager,每個bundle里的資源都加入了同一個AssetManager。
支持差分升級和遠(yuǎn)程bundle調(diào)用。
提供了打包插件,編譯期自動合并Manifest文件,自動分配bundle的資源package id,自動生成差分包,并提供單模塊調(diào)試功能。
主要缺點如下:

項目剛開源,文檔很少,主要功能不穩(wěn)定。
差分升級和遠(yuǎn)程bundle調(diào)用,需要強有力的后臺和測試支撐。
差分升級還不支持動態(tài)添加4大組件的功能。


作者:47045039
來源:CSDN
原文:https://blog.csdn.net/mountains2001/article/details/61196497
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

?著作權(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)容