背景在 Android 模塊化架構(gòu)中后,子Module 間相互解耦,作為獨(dú)立的模塊運(yùn)行。如果 子Module 也需要進(jìn)初始化的操作,那么該如何做呢?可能你會(huì)說(shuō),直接在 殼App Application的onCreate函數(shù)進(jìn)行初始化就可以了,但這樣會(huì)帶來(lái)一些新的問(wèn)題:
- 我們并不需要 殼App 去關(guān)注模塊內(nèi)部的業(yè)務(wù),所以每個(gè)模塊的初始化應(yīng)該由自身管理;
- 并不是所有子模塊的初始化,都需要在 Application onCreate() 時(shí)去進(jìn)行加載,這樣會(huì)極大影響應(yīng)用的啟動(dòng)速度。所以每個(gè)模塊的初始化應(yīng)該按需加載;
常見(jiàn)方案及優(yōu)缺點(diǎn)
1、ContentProvider
實(shí)現(xiàn)原理:每個(gè) 子Module 內(nèi)部自定義 ContentProvider ,在應(yīng)用 Application 的 onCreate 函數(shù)執(zhí)行前,系統(tǒng)就會(huì)自動(dòng)的順序調(diào)用 子Module 的 ContentProvider 的 onCreate 函數(shù),也就實(shí)現(xiàn)了 子Module 的自加載功能。例如 WorkManager 也是根據(jù)這個(gè)原理,其內(nèi)部聲明了 ContentProvider 來(lái)實(shí)現(xiàn)這種自加載方案。
優(yōu)點(diǎn):
- 模塊間充分解耦,代碼邊界獨(dú)立
弊端:
- ContentProvider 的啟動(dòng)是有性能損耗的,在子模塊較多(業(yè)務(wù)邏輯較多的 App,可能會(huì)有幾十個(gè) 子Module )的情況下,有一定的性能損耗;
- 需要繼承 ContentProvider 類(lèi)并在 AndroidManifes 中聲明,代碼不夠簡(jiǎn)潔和優(yōu)雅;
- 無(wú)法控制初始化的時(shí)機(jī),在應(yīng)用啟動(dòng)時(shí)就會(huì)初始化所有的子模塊,而初始化本身可能就是一件比較重的業(yè)務(wù)邏輯;
2、ARouter
實(shí)現(xiàn)原理:通過(guò)路由的方式,實(shí)現(xiàn)子Module的初始化。
定義通用IPreloadProvider接口
- interface IPreloadProvider: IProvider {
- fun preload()
- }
在主module中進(jìn)行初始化調(diào)用
- fun ARouter.preload(context: Context, path: String) {
- val preloadProvider = this.build(path).navigation(context.applicationContext) as IPreloadProvider
- preloadProvider.preload()
- }
優(yōu)點(diǎn):
- 在多數(shù)場(chǎng)景下,我們都會(huì)使用 ARouter 來(lái)進(jìn)行模塊化設(shè)計(jì),而ARouter天然支持這種路由調(diào)用的方法,簡(jiǎn)單好用;
- 代碼邊界獨(dú)立,殼App 與 子Modoule 間不需要代碼上的直接依賴(lài);
弊端:
- 殼App需要關(guān)心 子Module 的業(yè)務(wù),設(shè)計(jì)上有一定的耦合;
- 初始化的代碼塊容易膨脹,且各個(gè) 子Module 的初始化代碼均在一起,后續(xù)難以閱讀和維護(hù);
3、App Startup
優(yōu)點(diǎn):
- App Startup 是 為了解決因 App 啟動(dòng)時(shí)運(yùn)行多個(gè) ContentProvider 會(huì)增加 App 的啟動(dòng)時(shí)間的問(wèn)題,使用了一個(gè) InitializationProvider 管理多個(gè)依賴(lài)項(xiàng),消除了每個(gè)庫(kù)單獨(dú)使用 ContentProvider 成本,減少初始化時(shí)間;
- App Startup 可以自動(dòng)初始化 AndroidManifest.xml 文件中 InitializationProvider 下面的 聲明要初始化的模塊,并允許自定義模塊初始化順序;
- App Startup 提供了一種延遲初始化模塊的方法,減少 App 初始化時(shí)間;
弊端:
- 使用時(shí)需要修改 AndroidManifest 文件,使用不夠簡(jiǎn)潔;
- 子Module 功能比較獨(dú)立,且很多情況不需要即時(shí)加載(不需要在應(yīng)用啟動(dòng)就進(jìn)行初始化的動(dòng)作),如果使用延遲加載的話,又需要使用代碼主動(dòng)執(zhí)行初始化操作,那么勢(shì)就必存在對(duì)應(yīng)的引用。而我們的 子Module 依賴(lài)其實(shí)是建議使用 runtimeonly ,減少代碼上的直接依賴(lài);
- 無(wú)法解決需要時(shí)加載這種需求;
- 加載時(shí)序是在 Application onCreate 之前,依賴(lài)的通用庫(kù)此時(shí)可能并未被加載成功;
4、方案總結(jié)
所以,我們需要一個(gè)模塊自加載方案,能夠解決上面的問(wèn)題,且能滿(mǎn)足各種場(chǎng)景的需求:
- 代碼簡(jiǎn)潔,使用方便,邏輯清晰;
- 子Module 的預(yù)加載或者預(yù)啟動(dòng),由 子Module 自身來(lái)管理,避免與其他模塊的邏輯耦合;
- 加載時(shí)機(jī)可以控制,能做到即時(shí)加載或者懶加載,避免產(chǎn)生性能損耗;
- 不產(chǎn)生額外的性能損耗(可接受范圍內(nèi));
模塊化自加載方案(ALoader)
方案: 每個(gè) 子Module 都有自己的 ModuleApplication(虛擬Applicaiton),并定義啟動(dòng)模式,在應(yīng)用 Application 啟動(dòng)時(shí),會(huì)根據(jù)每個(gè) ModuleApplication 的啟動(dòng)模式來(lái)?yè)駲C(jī)調(diào)用其 onCreate() 函數(shù),達(dá)到模塊自加載的目的。
使用非常方便,僅需要在每個(gè) 子Module 內(nèi)創(chuàng)建 ModuleApplicaiton 類(lèi),繼承自IModuleApplication,使用注解:ModuleApplication 定義 initMode 即可。
- @ModuleApplication(initMode = InitMode.MAIN)
- class LoginModuleApplication : IModuleApplication() {
- override fun onCreate(context: Context) {
- Log.v("ALoader", "Login Module onCreate")
- }
- }
InitMode:
HUNGRY:餓漢式加載
主要用于加載時(shí)機(jī)較為嚴(yán)格的場(chǎng)景,即應(yīng)用啟動(dòng)后,需要立刻加載該 子Module 的相關(guān)配置。
IDLE:空閑時(shí)加載
主要用于加載時(shí)機(jī)不那么嚴(yán)格的場(chǎng)景。即應(yīng)用啟動(dòng)后,使用 idleHandler 進(jìn)行初始化,達(dá)到空閑時(shí)加載的目的。
LAZY:懶漢式加載
主要用于按需加載的場(chǎng)景。即應(yīng)用啟動(dòng)后,不進(jìn)行該 子Module 的初始化操作,待該 子Module 的 Activity 、 Service 或者相關(guān)邏輯執(zhí)行時(shí),才進(jìn)行 該子Module 的初始化工作,保證功能的正常運(yùn)行。
自加載方案設(shè)計(jì)思路
借鑒了ARouter的實(shí)現(xiàn)原理,模塊自加載方案的核心思想是:通過(guò)Apt技術(shù),生成 InitMode(初始化時(shí)機(jī)) 和 被注解(@ModuleApplication)的組件類(lèi) 的映射關(guān)系的類(lèi)(ALoaderCore),利用這個(gè)保存了映射關(guān)系的類(lèi),ALoader根據(jù) InitMode 尋找到目標(biāo)類(lèi),執(zhí)行其onCreate()函數(shù)。該框架的核心是利用 apt 生成的映射關(guān)系。
1、如何提高ALoader的初始化速度
利用緩存和 Gradle 插件,來(lái)提升映射類(lèi)的查找速度,實(shí)現(xiàn)高性能的初始化。
2、如何實(shí)現(xiàn)ALoader懶加載的效果
對(duì)于 子Module 懶加載的實(shí)現(xiàn),最重要的是如何識(shí)別到該 子Module 開(kāi)始啟動(dòng),并在此之前進(jìn)行初始化的操作。針對(duì)于多數(shù)的業(yè)務(wù)場(chǎng)景來(lái)講,子Module 的啟動(dòng)通常是伴隨著 四大組件(Activity、Service、ContentProvider、BroadcastReceiver)的啟動(dòng),而我們可以通過(guò) Hook 技術(shù)來(lái)實(shí)現(xiàn)對(duì)四大組件啟動(dòng)的感知,通過(guò)包名的匹配來(lái)識(shí)別到是哪個(gè) 子Module 開(kāi)始啟動(dòng)。以 Activity 和 Service 為例:
- fun hookActivityThreadHHandler() {
- val aClass = Class.forName("android.app.ActivityThread")
- val sCurrentActivityThread: Field = aClass.getDeclaredField("sCurrentActivityThread")
- sCurrentActivityThread.isAccessible = true
- val activityThread = sCurrentActivityThread.get(aClass)
- val mHField: Field = aClass.getDeclaredField("mH")
- mHField.isAccessible = true
- val mh = mHField.get(activityThread) as Handler
- val handlerClass = Class.forName("android.os.Handler")
- val mCallbackField = handlerClass.getDeclaredField("mCallback")
- mCallbackField.isAccessible = true
- mCallbackField.set(mh, ProxyHandlerCallback())
- }
- class ProxyHandlerCallback : Handler.Callback {
- override fun handleMessage(msg: Message): Boolean {
- when (msg.what) {
- LAUNCH_ACTIVITY -> {
- // TODO:
- }
- EXECUTE_TRANSACTION -> {
- // TODO:
- }
- CREATE_SERVICE -> {
- // TODO:
- }
- }
- return false
- }
- }
方案不足之處和優(yōu)化點(diǎn)
該方案的最終目的是:通過(guò)最簡(jiǎn)單的方式,實(shí)現(xiàn)模塊的按需自加載方案。
但是依然還有可以?xún)?yōu)化的地方:
- App Startup 可以自定義模塊初始化順序,在ALoader方案中,是否也需要集成?
- 通過(guò) Hook 技術(shù)來(lái)判斷子模塊的啟動(dòng),可能存在兼容性問(wèn)題,是否還有更優(yōu)雅的方式?
- 是否有其他更復(fù)雜的業(yè)務(wù)場(chǎng)景,還沒(méi)考慮到呢?
當(dāng)然,對(duì)于一般的業(yè)務(wù)場(chǎng)景,ALoader的實(shí)現(xiàn)已經(jīng)足夠滿(mǎn)足需要。如果有其他更好的方案,我們也可以一起探討。
原文地址:https://juejin.cn/post/7012825010586779678