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)用流程是

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

當(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)用流程

3.4、跨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、不需要每個組件都依賴公共庫