Android組件化

1、組件化和插件化區(qū)別:

組件化開發(fā):

(1)、組件化是將一個app分成多個Module,每個Module都是一個組件;
(2)、組件化在發(fā)布的時候,所有組件以lib的形式被主app工程依賴,并打包成1個apk,不支持動態(tài)加載。

插件化開發(fā):

(1)、插件化是將整個app拆分成很多模塊,這些模塊包括一個宿主和多個插件,每個模塊都是一個apk;
(2)插件化在發(fā)布的時候,可以將宿主apk和插件apk,分開或者聯(lián)合打包,在運行時,宿主apk可以動態(tài)加載某個插件apk。

相同點:

(1)、兩者都是為了使項目模塊化解耦;
(2)、二者中組件或者插件都可以單獨調(diào)試。

2、組件化的優(yōu)點和缺點:

2.1、組件化的有點

(1)、業(yè)務(wù)隔離;
(2)、組件單獨運行提高編譯速度
(3)、組件化之后,很容易地實現(xiàn)一些組件層面的AOP,比如打開頁面的時候進(jìn)行異步數(shù)據(jù)加載;

2.2組件化的缺點:

(1)、每個組件都需要依賴公用庫,會拖慢整體編譯速度;

3、CC組件化原理

1、組件化優(yōu)點之一是解耦,cc如何實現(xiàn)解耦
2、解耦之后的問題是組件之間如何進(jìn)行通信(app之內(nèi)的通信,和跨app通信)
3、既然后跨app通信,如何將組件單獨運行,以及在A,L之間智能切換
4、組件注冊和反注冊過程繁瑣,如何實現(xiàn)自動注冊
5、如何監(jiān)控調(diào)用超時以及取消,如何將調(diào)用和activity生命周期關(guān)聯(lián)

cc組件化中,組件之間通信采用了組件總線通信的方式,在組件管理類(ComponentMananger)中注冊了所有組件對象,當(dāng)請求某個組件服務(wù)時,ComponentMananger通過查找映射表找到組件對象并調(diào)用并返回結(jié)果,它支持同步異步調(diào)用組件和同步異步實現(xiàn)組件,并且支持跨app調(diào)用組件。

3.1、app內(nèi)部調(diào)用流程是

app內(nèi)部調(diào)用流程.png

當(dāng)組件a需要請求組件b的服務(wù)時,它就通過cc去調(diào)用cm,然后cm查找路由表,找到對應(yīng)組件,進(jìn)行調(diào)用,最后將結(jié)果返回給組件a。

3.2、跨app調(diào)用流程

跨app調(diào)用流程.png

當(dāng)app1中的組件a請求app2中的組件B的服務(wù)時,它依然是通過cc調(diào)用cm,cm路由表中不存在組件B,它會通過BroadReceiver+Localsocket和app2建立通信,然后app2中的cm去查找自身的路由表,如果存在組件B,就就行調(diào)用,然后將結(jié)果通過LocalSocke返回給組件a。

3.3、app內(nèi)部,組件之間的具體調(diào)用流程

app內(nèi)部組件調(diào)用流程.png

3.4、跨app調(diào)用流程

1.0版本跨app組件調(diào)用流程
2.0版本跨app組件調(diào)用流程

這里有一個問題是如何知道組件在哪個進(jìn)程?

前提:組件注冊時會將組件名稱及其對應(yīng)的進(jìn)程記錄在一個map中

情況1:同一個app中,跨進(jìn)程調(diào)用時,直接(調(diào)用ComponentManager.getComponentProcessName(componentName),)查詢這個map,就可以獲取組件所對應(yīng)的進(jìn)程。

情況2:跨app調(diào)用時,沒法直接(調(diào)用ComponentManager.getComponentProcessName(componentName)查詢調(diào)用方的這個map,去獲取組件對應(yīng)的進(jìn)程名。因為組件沒有在調(diào)用方的app中注冊。

跨app調(diào)用時,獲取組件對應(yīng)進(jìn)程的具體過程是:
1、app啟動時,獲取到手機中所有應(yīng)用了cc框架的app,以及它對應(yīng)的包名(這個包名即主進(jìn)程名)
2、獲取通過ContentProvider獲取這些app主進(jìn)程中的IRemoteCCService。
3、通過這個主進(jìn)程的IRemoteCCService去查詢對應(yīng)app中,組件對應(yīng)進(jìn)程的map,就可以獲取組件對應(yīng)的進(jìn)程名。

如何知道哪些app應(yīng)用了cc框架?
在cc框架中內(nèi)置一個RemoteConnectionActivity,并給其指定一個intent-filter,
在調(diào)用方app啟動的時候,使用PackageManager去查詢這個activity對應(yīng)的activityinfo,繼而通過這個ActivityInfo獲取到對應(yīng)packageName。

另外會通過廣播監(jiān)聽app的安裝,可直接獲取app對應(yīng)的包名,通過判斷是否能啟動RemoteConnectionActivity,判斷是否應(yīng)用了cc框架。如果是就繼續(xù)進(jìn)行2,3兩步流程。

3.5、cc監(jiān)控策略

在ChainProcessor開啟攔截器鏈的之前是調(diào)用CCMonitor.addMonitorFor(cc);將此次調(diào)用流程加入到監(jiān)控隊列中。

超時監(jiān)控

在CCMonitor中維護這一個ConcurrentHashMap<String, CC>,并且使用minTimeoutAt記錄ConcurrentHashMap中cc的最短超時時間,當(dāng)ConcurrentHashMap不為空時,就會開啟一個監(jiān)控線程去檢測這個map中的cc,具體的過程就是監(jiān)控線程先阻塞minTimeoutAt秒,然后去遍歷map,超時cc從map中去除并調(diào)用cc.timeout,終止調(diào)用流程(設(shè)置將finished設(shè)為ture,每個攔截器調(diào)用chain.proceed之前會檢測finished,如果為true就終止流程),然后在遍歷的過程中篩選出隊列中剩余最短的超時時間minTimeoutAt,再次循環(huán)檢測。直到map為空,監(jiān)控線程停止。(注意:當(dāng)新加入的cc超時時間比minTimeoutAt小時,主動喚醒監(jiān)控線程)。

在CCMonitor中會將所有cc加入ConcurrentHashMap<String, CC>,并開啟一個異步線程去監(jiān)控cc超時:
map中有監(jiān)控對象時,循環(huán)遍歷map,統(tǒng)計出下次需要進(jìn)行超時調(diào)用操作距現(xiàn)在的時間間隔,進(jìn)入wait狀態(tài),時間到后,被喚醒進(jìn)行超時調(diào)用,并且將超時cc從map中移除,然后再次計統(tǒng)計出下次需要進(jìn)行超時調(diào)用操作距現(xiàn)在的時間間隔,再次進(jìn)入狀態(tài),以此循環(huán)。
直到map為空,跳出循環(huán),當(dāng)有監(jiān)控對象加入時再次開啟上述循環(huán)

和Activity、Fragment的生命周期關(guān)聯(lián)

通過application.registerActivityLifecycleCallbacks(new CCMonitor.ActivityMonitor())注冊一個自定義的
ActivityLifecycleCallbacks,在其回調(diào)方法onActivityDestroyed方法中遍歷持有cc對象的ConcurrentHashMap,如果當(dāng)前Destroye的activity和cc中傳入的cancelOnDestroyActivity相同就調(diào)用cc的cancel方法,取消整個調(diào)用流程。

Fragment類似
通過FragmentManager.registerFragmentLifecycleCallbacks注冊一個自定義的FragmentLifecycleCallbacks。

4、cc-register

4.1、作用1:將module在application和library之間自動切換

4.1.1、現(xiàn)象:

所有應(yīng)用cc-register插件的module,
(1)、可直接點擊運行按鈕單獨運行;
(2)、當(dāng)moduleA依賴moduleB,給moduleA打包時, moduleB自動變成library被依賴。

4.1.2、原理:

判斷當(dāng)前執(zhí)行的task:
(1、如果當(dāng)前module是主app(在主app中使用ext.mainapp=true聲明),則應(yīng)用application插件;)
(2、否則,如果當(dāng)前module是library或者在local.properties聲明打包,則應(yīng)用library插件)
3、否則,如果當(dāng)前task是對當(dāng)前module集成打包或者當(dāng)前task不是集成打包的task,則應(yīng)用application插件,否則應(yīng)用library插件
這樣就保證了
1、 如果當(dāng)前task是集成打包的task,并且不是為該module打包,那么該module是應(yīng)用的library插件,可以被依賴;
2、如果當(dāng)前task不是集成打包的task,module默認(rèn)都是應(yīng)用的application插件,可以直接運行 )

(打開項目會執(zhí)行構(gòu)建任務(wù),不是集成打包任務(wù),默認(rèn)給所有module應(yīng)用application插件)

如何判斷當(dāng)前task是為本module做集成打包?

讀取當(dāng)前task,如果當(dāng)前任務(wù)是ASSEMBLE|(BUILD)|(INSTALL)中的一種,并且前綴是當(dāng)前module名,則是為當(dāng)前module打包。

有些集成打包task名稱前綴沒有module名稱,比如build,但是主app的build task本身是給主app打包,所以這里也需要給主app默認(rèn)應(yīng)用application插件(在主app的build.gradle的ext塊中聲明一個字段,然后讀取這字段判斷)。

然后根據(jù)module應(yīng)用的是application插件還是library插件,使用sourceSets來給module應(yīng)用不同的menifest文件。

4.1.3、如何做到module完全隔離
提供了addComponent dependencymodule 方法,只有在給當(dāng)前module集成打包的時候才去依賴dependencymodule(dependencymodule沒有在localProperties文件中聲明排除),保證了組件化完全隔離。

4.2、作用2:組件自動注冊

使用Transform API 結(jié)合ASM進(jìn)行字節(jié)碼插樁,在打包過程中,class文件轉(zhuǎn)換dex的階段,會經(jīng)歷多個Transform,我們可以自定義一個Transform得到所有的class文件,然后使用ASM對其進(jìn)行操作。
在plugin的apply注冊一個自定義的Transform類,在其transform方法中去進(jìn)行掃描和插入。

4.2.1、掃描過程:

掃描所有jar文件中的class文件和本地目錄中的class文件,
1、 根據(jù)類名比較找到要被插入代碼的類并記錄;
2、通過ASM中的classvisitor找到其實現(xiàn)了Icomponet接口的子類,并記錄。
通過構(gòu)造一個自定義的ScanClassVisitor,然后將class文件傳入,(調(diào)用ClassReader的accept方法),就會回調(diào)自定義的ClassVisitor中的visit方法,在visit方法中我們可以得到當(dāng)前類的父類和接口,然后進(jìn)行判斷即可。

4.2.2、插入過程

如果要被插入代碼的類在jar文件中:

建立一個(optJar的)臨時文件,)然后用這個文件構(gòu)建一個JarOutputStream,)遍歷Jar文件中的class文件,如果是被注入注冊代碼的類,就獲取代碼插入之后的字節(jié)碼數(shù)組,寫入(optJar)臨時文件,如果不是就直接寫入,最后將原jar文件刪除,臨時文件(optJar)重命名為原jar文件的名稱。

如果要被插入代碼的類是class文件

建立一個(optJar的)臨時文件,獲取代碼插入之后的字節(jié)碼數(shù)組,然后寫入臨時文件(optJar),最后刪除原class文件,將臨時文件(optJar)重命名為原class文件的名稱。

使用asm插入字節(jié)碼的具體過程

自定義一個classvisitor,將我們的要插入字節(jié)碼class文件傳入,在其visitMethod方法中判斷是否是要被插入的方法,如果是就返回一個自定義的MethodVisitor,在自定義的MethodVisitor 的回調(diào)方法visitInsn中就可以將我們收集的Icomponet的子類構(gòu)造一個對象調(diào)用相應(yīng)方法進(jìn)行插入。

總結(jié):
添加一個組件分為兩步:
(1)、在build.gradle對組件對應(yīng)的module進(jìn)行依賴
(2)、調(diào)用ComponetManager.registerComponet(Icomponent c)方法注冊我們的組件。
一個前提是:cc中組件的概念是自定義一個類實現(xiàn)Icomponent接口就是一個組件。
這里的注冊組件就是注冊我們自定義的Icomponent的子類。

cc為了減少后期維護成本。它使用AOP的方式幫我們自動注冊了組件。
具體的過程是在打包過程中有一個階段是class文件轉(zhuǎn)換dex的階段,所有class文件會經(jīng)歷多個Transform進(jìn)行處理,我們可以自定義一個Transform得到所有的class文件,然后掃描得到所有IComponent的子類,使用ASM操作字節(jié)碼去調(diào)用注冊方法進(jìn)行注冊。
1、怎么識別IComponent的子類
2、asm插入字節(jié)碼的具體過程

5、cc組件化框架的優(yōu)勢

cc,DDComponentForAndroid,ModularizationArchitecture,arouter
從組件化的幾個好處來看
1、代碼解耦,業(yè)務(wù)隔離:
業(yè)務(wù)隔離其實不需要組件化框架,把一個組件的代碼拷貝到一個module中,然后可以人為控制兩個組件不耦合,但是這種控制可能是不可靠的,比如,
cc和DDComponentForAndroid在代碼層面做限制,避免了各個模塊之間的耦合,原理是,
arouter和ModularizationArchitecture并沒有提供提這種代碼層面的解耦限制。

2、加快編譯速度-單獨運行
2.1、各個模塊可以單獨運行以及被依賴
讓module單獨運行的原理,就是讓它應(yīng)用application插件以及配置對應(yīng)的menifest;
組件有單獨運行,必然也有可被依賴。它要被依賴的時候又要去給它應(yīng)用library插件,以及配置library對應(yīng)的menifest,過程繁瑣。
cc和DDComponentForAndroid都做到了自動切換?,F(xiàn)象是。。。
原理是。。。
ModularizationArchitecture和arouter都沒有提供這種切換方式,由開發(fā)者自行解決。
2.2、加快編譯速度-聯(lián)合調(diào)試
module A 依賴 module B, 此時在module B
上開發(fā)一個功能,但是必須通過module A才能進(jìn)入module B看到這個功能,,調(diào)試時,一種方式是將module A
和module B集成打包,一種方式是將module A和module B分開打包,module A通過跨app的方式和module B進(jìn)行通信。由于我們只在module B開發(fā)功能,需要反復(fù)編譯的是module B。集成打包所消耗的編譯時間是(module A+module B)n,分開打包的編譯時間是moduleA+module Bn。可以看出分開打包編譯時間比集成打包編譯時間縮減了(n-1)*module A。

比如首頁和商戶,首頁單獨運行調(diào)試沒問題,但是調(diào)試商戶時,需要從首頁進(jìn)去,這時就有兩個選擇,一個是首頁商戶聯(lián)合打包,一個是首頁商戶分開打包,調(diào)試商戶時,從首頁組件跨app調(diào)用商戶組件。在編譯時間方面顯然是后一種方式比較好。

然后在看哪些組件化框架支持跨app通信呢?
cc和ModularizationArchitecture,都是通過aidl實現(xiàn)跨進(jìn)程通信,原理是,
DDComponentForAndroid無法跨app通信,
arouter使用URLScheme結(jié)合一個SchemeFilterActivity可以做到跨進(jìn)程通信(通過URLScheme啟動另外一個app中的SchemeFilterActivity,在這個activity中使用arouter進(jìn)行分發(fā)),它的缺點是1、需要中轉(zhuǎn)activity;2、不可利于數(shù)據(jù)回傳;3、如果設(shè)備上安裝了多個相同URLScheme的app,會彈出選擇框;4、無法進(jìn)行權(quán)限設(shè)置,無法進(jìn)行開關(guān)設(shè)置,任意app都可調(diào)用,存在安全性風(fēng)險

另外的亮點:
1、 cc是基于攔截器模式,容易擴展或?qū)崿F(xiàn)一些組件層面的aop,比如頁面跳轉(zhuǎn)時異步請求數(shù)據(jù)。
2、使用字節(jié)碼插樁自動注冊組件,代替手動注冊
3、支持超時,取消,并且和activity的生命周期關(guān)聯(lián)

6、自己思考的組件化方案-縮短編譯時間

不需要將組件單獨打成apk,
1、自定義一個gradle插件,功能是可以將組件打成aar包,然后上傳到maven庫
2、然后各方向負(fù)責(zé)人只需要維護一個app殼module,和對應(yīng)負(fù)責(zé)業(yè)務(wù)的module,其他的組件從maven依賴。
3、這樣我們的編譯時間就是app的編譯時間加上一個業(yè)務(wù)module的編譯時間。
4、發(fā)版上線的時候所有業(yè)務(wù)負(fù)責(zé)人發(fā)版最新的aar,然后集成到app中進(jìn)行打包。

優(yōu)點:
1、避免了跨進(jìn)程通信的麻煩
2、不需要每個組件都依賴公共庫

最后編輯于
?著作權(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)容

  • (轉(zhuǎn)載) Android組件化方案已經(jīng)開源,參見Android組件化方案開源。方案的解讀文章是一個小的系列,這是系...
    江左灬梅郎閱讀 2,820評論 2 31
  • 在目前移動互聯(lián)網(wǎng)時代,每個 APP 就是流量入口,與過去 PC Web 瀏覽器時代不同的是,APP 的體驗與迭代速...
    斜杠時光閱讀 14,134評論 4 139
  • 1. 天色將晚的時候,我扛上鋤頭往家走。 暮色越過重重高山,整個世界將要被披上一層黑色的外衣。 老一輩常說,這個時...
    洛子帥閱讀 1,445評論 42 43
  • 今天是我們導(dǎo)師班的畢業(yè)典禮,大家都好開心,臨時我串了下主持人, 我也沒準(zhǔn)備,臨時就上了, 感覺主持過程中有點不銜接...
    岳瑤閱讀 142評論 1 2
  • 周五市場繼續(xù)維持震蕩走勢,早上沖高,下午回落,尾盤繼續(xù)上攻。在這樣的震蕩走勢下,個股分化是較為明顯的。 行情分析:...
    穩(wěn)中有升閱讀 296評論 0 0

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