教你打造一个Android组件化开发框架
*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布
CC:Component Caller,一個android組件化開發(fā)框架, 已開源,github地址:https://github.com/luckybilly/CC
本文主要講解框架實現(xiàn)原理,如果只是想了解一下如何使用,可直接到github上查看README文檔。
想了解如何用CC實現(xiàn)立即開始組件化開發(fā),并漸進式地改造自己的項目,戳這里
前言
首先說明一下,本文將講述的組件化與業(yè)內(nèi)的插件化(如:Atlas, RePlugin等)不是同一個概念
【圖片來源于網(wǎng)絡(luò)】
組件化開發(fā):就是將一個app分成多個Module,每個Module都是一個組件(也可以是一個基礎(chǔ)庫供組件依賴),開發(fā)的過程中我們可以單獨調(diào)試部分組件,組件間不需要互相依賴,但可以相互調(diào)用,最終發(fā)布的時候所有組件以lib的形式被主app工程依賴并打包成1個apk。
插件化開發(fā):和組件化開發(fā)略有不用,插件化開發(fā)時將整個app拆分成很多模塊,這些模塊包括一個宿主和多個插件,每個模塊都是一個apk(組件化的每個模塊是個lib),最終打包的時候?qū)⑺拗鱝pk和插件apk(或其他格式)分開或者聯(lián)合打包。
本文將主要就以下幾個方面進行介紹:
一、為什么需要組件化?
二、CC的功能介紹
三、CC技術(shù)要點
四、CC執(zhí)行流程詳細解析
五、使用方式介紹
一、為什么需要組件化?
關(guān)于使用組件化的理由,上網(wǎng)能搜到很多,如業(yè)務(wù)隔離、單獨以app運行能提高開發(fā)及調(diào)試效率等等這里就不多重復(fù)了,我補充一條:組件化之后,我們能很容易地實現(xiàn)一些組件層面的AOP,例如:
- 輕易實現(xiàn)頁面數(shù)據(jù)(網(wǎng)絡(luò)請求、I/O、數(shù)據(jù)庫查詢等)預(yù)加載的功能
- 組件被調(diào)用時,進行頁面跳轉(zhuǎn)的同時異步執(zhí)行這些耗時邏輯
- 頁面跳轉(zhuǎn)并初始化完成后,再將這些提前加載好的數(shù)據(jù)展示出來
- 在組件功能調(diào)用時進行登錄狀態(tài)校驗
- 借助攔截器機制,可以動態(tài)給組件功能調(diào)用添加不同的中間處理邏輯
二、CC的功能介紹
支持app間跨進程的組件調(diào)用(組件開發(fā)/調(diào)試時可單獨作為app運行)
- 在獨立運行組件時非常有用,比如:一個組件的某個功能要用到用戶的登錄信息,若未登錄則調(diào)起登錄組件的登錄頁面,若已登錄則獲取當前用戶信息。此時可以直接使用主app中的登錄組件及用戶在主app中的登錄狀態(tài),該組件作為app獨立運行時無需依賴登錄組件。
盡可能的解決了使用姿勢不正確導(dǎo)致的crash:
- 組件調(diào)用處、回調(diào)處、組件實現(xiàn)處的crash全部在框架內(nèi)部catch住
- 同步返回或異步回調(diào)的CCResult對象一定不為null,避免空指針
demo效果演示
組件A打包在主app中,組件B為單獨運行的組件app,下圖演示了在主app中調(diào)用兩者的效果,并將結(jié)果以Json的格式顯示在下方。demo下載地址):
三、 CC技術(shù)要點
實現(xiàn)CC組件化開發(fā)框架主要需要解決的問題有以下幾個方面:
- 組件如何自動注冊?
- 如何兼容同步/異步方式調(diào)用組件?
- 如何兼容同步/異步方式實現(xiàn)組件?
- 如何進行跨進程組件任意功能的調(diào)用(不只是啟動Activity)?
- 組件如何更方便地在application和library之間切換?
- 如何實現(xiàn)startActivityForResult?
- 如何阻止非法的外部調(diào)用?
- 如何與Activity、Fragment的生命周期關(guān)聯(lián)起來
3.1 組件如何自動注冊?
為了減少后期維護成本,想要實現(xiàn)的效果是:當需要添加某個組件到app時,只需要在gradle中添加一下對這個module的依賴即可(通常都是maven依賴,也可以是project依賴)
最初想要使用的是annotationProcessor通過編譯時注解動態(tài)生成組件映射表代碼的方式來實現(xiàn)。但嘗試過后發(fā)現(xiàn)行不通,因為編譯時注解的特性只在源碼編譯時生效,無法掃描到aar包里的注解(project依賴、maven依賴均無效),也就是說必須每個module編譯時生成自己的代碼,然后要想辦法將這些分散在各aar種的類找出來進行集中注冊。
ARouter的解決方案是:
- 每個module都生成自己的java類,這些類的包名都是’com.alibaba.android.arouter.routes’
-
然后在運行時通過讀取每個dex文件中的這個包下的所有類通過反射來完成映射表的注冊,詳見ClassUtils.java源碼
運行時通過讀取所有dex文件遍歷每個entry查找指定包內(nèi)的所有類名,然后反射獲取類對象。這種效率看起來并不高。
ActivityRouter的解決方案是(demo中有2個組件名為’app’和’sdk’):
- 在主app module中有一個@Modules({"app", "sdk"})注解用來標記當前app內(nèi)有多少組件,根據(jù)這個注解生成一個RouterInit類
- 在RouterInit類的init方法中生成調(diào)用同一個包內(nèi)的RouterMapping_app.map
- 每個module生成的類(RouterMapping_app.java 和 RouterMapping_sdk.java)都放在com.github.mzule.activityrouter.router包內(nèi)(在不同的aar中,但包名相同)
- 在RouterMapping_sdk類的map()方法中根據(jù)掃描到的當前module內(nèi)所有路由注解,生成了調(diào)用Routers.map(…)方法來注冊路由的代碼
-
在Routers的所有api接口中最終都會觸發(fā)RouterInit.init()方法,從而實現(xiàn)所有路由的映射表注冊
這種方式用一個RouterInit類組合了所有module中的路由映射表類,運行時效率比掃描所有dex文件的方式要高,但需要額外在主工程代碼中維護一個組件名稱列表注解: @Modules({“app”, “sdk”})
還有沒有更好的辦法呢?
Transform API: 可以在編譯時(dex/proguard之前)掃描當前要打包到apk中的所有類,包括: 當前module中java文件編譯后的class、aidl文件編譯后的class、jar包中的class、aar包中的class、project依賴中的class、maven依賴中的class。
ASM: 可以讀取分析字節(jié)碼、可以修改字節(jié)碼
二者結(jié)合,可以做一個gradle插件,在編譯時自動掃描所有組件類(IComponent接口實現(xiàn)類),然后修改字節(jié)碼,生成代碼調(diào)用掃描到的所有組件類的構(gòu)造方法將其注冊到一個組件管理類(ComponentManager)中,生成組件名稱與組件對象的映射表。
此gradle插件被命名為:AutoRegister,現(xiàn)已開源,并將功能升級為編譯時自動掃描任意指定的接口實現(xiàn)類(或類的子類)并自動注冊到指定類的指定方法中。只需要在app/build.gradle中配置一下掃描的參數(shù),沒有任何代碼侵入,原理詳細介紹傳送門
3.2 如何兼容同步/異步方式調(diào)用組件?
通過實現(xiàn)java.util.concurrent.Callable接口同步返回結(jié)果來兼容同步/異步調(diào)用:
- 同步調(diào)用時,直接調(diào)用CCResult result = Callable.call()來獲取返回結(jié)果
- 異步調(diào)用時,將其放入線程池中運行,執(zhí)行完成后調(diào)用回調(diào)對象返回結(jié)果: IComponentCallback.onResult(cc, result)
- 1
3.3 如何兼容同步/異步方式實現(xiàn)組件?
調(diào)用組件的onCall方法時,可能需要異步實現(xiàn),并不能同步返回結(jié)果,但同步調(diào)用時又需要返回結(jié)果,這是一對矛盾。
此處用到了Object的wait-notify機制,當組件需要異步返回結(jié)果時,在CC框架內(nèi)部進行阻塞,等到結(jié)果返回時,通過notify中止阻塞,返回結(jié)果給調(diào)用方
注意,這里要求在實現(xiàn)一個組件時,必須確保組件一定會回調(diào)結(jié)果,即:需要確保每一種導(dǎo)致調(diào)用流程結(jié)束的邏輯分支上(包括if-else/try-catch/Activity.finish()-back鍵-返回按鈕等等)都會回調(diào)結(jié)果,否則會導(dǎo)致調(diào)用方一直阻塞等待結(jié)果,直至超時。類似于向服務(wù)器發(fā)送一個網(wǎng)絡(luò)請求后服務(wù)器必須返回請求結(jié)果一樣,否則會導(dǎo)致請求超時。
3.4 如何進行跨進程組件任意功能的調(diào)用(不只是啟動Activity)?
市面上常見的組件化框架采用的通信解決方案有:
- URLScheme(例如:ActivityRouter、ARouter等)
- 優(yōu)勢有:
- 基因中自帶支持從webview中調(diào)用
- 不用互相注冊(不用知道需要調(diào)用的app的進程名稱等信息)
- 劣勢有:
- 只能單向地給組件發(fā)送信息,適用于啟動Activity和發(fā)送指令,不適用于獲取數(shù)據(jù)(例如:獲取用戶組件的當前用戶登錄信息)
- 需要有個額外的中轉(zhuǎn)Activity來統(tǒng)一處理URLScheme
- 如果設(shè)備上安裝了多個使用相同URLScheme的app,會彈出選擇框(多個組件作為app同時安裝到設(shè)備上時會出現(xiàn)這個問題)
- 無法進行權(quán)限設(shè)置,無法進行開關(guān)設(shè)置,存在安全性風(fēng)險
- 優(yōu)勢有:
- AIDL (例如:ModularizationArchitecture)
- 優(yōu)勢有:
- 可以傳遞Parcelable類型的對象
- 效率高
- 可以設(shè)置跨app調(diào)用的開關(guān)
- 劣勢有:
- 調(diào)用組件之前需要提前知道該組件在那個進程,否則無法建立ServiceConnection
- 組件在作為獨立app和作為lib打包到主app時,進程名稱不同,維護成本高
- 優(yōu)勢有:
設(shè)計此功能時,我的出發(fā)點是:作為組件化開發(fā)框架基礎(chǔ)庫,想盡量讓跨進程調(diào)用與在進程內(nèi)部調(diào)用的功能一致,對使用此框架的開發(fā)者在切換app模式和lib模式時盡量簡單,另外需要盡量不影響產(chǎn)品安全性。因此,跨組件間通信實現(xiàn)的同時,應(yīng)該滿足以下條件:
- 每個app都能給其它app調(diào)用
- app可以設(shè)置是否對外提供跨進程組件調(diào)用的支持
- 組件調(diào)用的請求發(fā)出去之后,能自動探測當前設(shè)備上是否有支持此次調(diào)用的app
- 支持超時、取消
基于這些需求,我最終選擇了BroadcastReceiver + Service + LocalSocket來作為最終解決方案:
如果appA內(nèi)發(fā)起了一個當前app內(nèi)不存在的組件:Component1,則建立一個LocalServerSocket,同時發(fā)送廣播給設(shè)備上安裝的其它同樣使用了此框架的app,同時,若某個appB內(nèi)支持此組件,則根據(jù)廣播中帶來的信息與LocalServerSocket建立連接,并在appB內(nèi)調(diào)用組件Component1,并將結(jié)果通過LocalSocket發(fā)送給appA。- 1
BroadcastReceiver是android四大組件之一,可以設(shè)置接收權(quán)限,能避免外部惡意調(diào)用。并且可以設(shè)置開關(guān),接收到此廣播后決定是否響應(yīng)(假裝沒接收到…)。
之所以建立LocalSocket鏈接,是為了能繼續(xù)給這次組件調(diào)用請求發(fā)送超時和取消的指令。
用這種方式實現(xiàn)時,遇到了3個問題:
- 由于廣播接收器定義在基礎(chǔ)庫中,所有app內(nèi)都有,當用戶在主線程中同步調(diào)用跨app的組件時,調(diào)用方主線程被阻塞,廣播接收器也在需要主線程中運行,導(dǎo)致廣播接收器無法運行,直至timeout,組件調(diào)用失敗。
- 將廣播接收器放到子進程中運行問題得到解決
- 被調(diào)用的app未啟動或被手動結(jié)束進程,遇到廣播接收不到的問題
- 這個問題暫時未很好的解決,但考慮到組件化開發(fā)只在開發(fā)期間需要用到跨進程通信,開發(fā)者可以通過手動在系統(tǒng)設(shè)置中給對應(yīng)的app賦予自啟動權(quán)限來解決問題
- 跨進程調(diào)用時,只能傳遞基本數(shù)據(jù)類型,無法獲取Fragment等java對象
- 這個問題在app內(nèi)部調(diào)用時不存在,app內(nèi)部來回傳遞的都是Map,可以傳遞任何數(shù)據(jù)類型。但由于進程間通信是通過字符串來回發(fā)送的,暫時支持不了非基本數(shù)據(jù)類型,未來可以考慮支持Serializable
3.5 組件如何更方便地在application和library之間切換?
關(guān)于切換方式在網(wǎng)絡(luò)上有很多文章介紹,基本上都是一個思路:在module的build.gradle中設(shè)置一個變量來控制切換apply plugin: 'com.android.application'或apply plugin: 'com.android.library'以及sourceSets的切換。
為了避免在每個module的build.gradle中配置太多重復(fù)代碼,我做了個封裝,默認為library模式,提供2種方式切換為application模式:在module的build.gradle中添加ext.runAsApp = true或在工程根目錄中l(wèi)ocal.properties中添加module_name=true
使用這個封裝只需一行代碼:
// 將原來的 apply plugin: 'com.android.application'或apply plugin: 'com.android.library' //替換為下面這一行 apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'- 1
- 2
- 3
注:cc-settings.gradle源碼傳送門
3.6 如何實現(xiàn)startActivityForResult?
android的startActivityForResult的設(shè)計也是為了頁面?zhèn)髦?#xff0c;在CC組件化框架中,頁面?zhèn)髦蹈静恍枰玫絪tartActivityForResult,直接作為異步實現(xiàn)的組件來處理(在原來setResult的地方調(diào)用CC.sendCCResult(callId, ccResult),另外需要注意:按back鍵及返回按鈕的情況也要回調(diào)結(jié)果)即可。
如果是原來項目中存在大量的startActivityForResult代碼,改造成本較大,可以用下面這種方式來保留原來的onActivityResult(…)及activity中setResult相關(guān)的代碼:
-
在原來調(diào)用startActivityForResult的地方,改用CC方式調(diào)用,將當前context傳給組件
CC.obtainBuilder("demo.ComponentA").setContext(context).addParams("requestCode", requestCode).build().callAsync();- 1
- 2
- 3
- 4
- 5
-
在組件的onCall(cc)方法中用startActivityForResult的方式打開Activity
@Overridepublic boolean onCall(CC cc) {Context context = cc.getContext();Object code = cc.getParams().get("requestCode"); Intent intent = new Intent(context, ActivityA.class); if (!(context instanceof Activity)) { //調(diào)用方?jīng)]有設(shè)置context或app間組件跳轉(zhuǎn),context為application intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } if (context instanceof Activity && code != null && code instanceof Integer) { ((Activity)context).startActivityForResult(intent, (Integer)code); } else { context.startActivity(intent); } CC.sendCCResult(cc.getCallId(), CCResult.success()); return false; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
3.7 如何阻止非法的外部調(diào)用?
為了適應(yīng)不同需求,有2個安全級別可以設(shè)置:
-
權(quán)限驗證(給進程間通信的廣播設(shè)置權(quán)限,一般可設(shè)置為簽名級權(quán)限校驗),步驟如下:
- 新建一個module
- 在該module的build.gradle中添加對基礎(chǔ)庫的依賴,如: compile 'com.billy.android:cc:0.3.0'
- 在該module的src/main/AndroidManifest.xml中設(shè)置權(quán)限及權(quán)限的級別,參考component_protect_demo
- 其它每個module都額外依賴此module,或自定義一個全局的cc-settings.gradle,參考cc-settings-demo-b.gradle
-
外部調(diào)用是否響應(yīng)的開關(guān)設(shè)置(這種方式使用起來更簡單一些)
- 在Application.onCreate()中調(diào)用CC.enableRemoteCC(false)可關(guān)閉響應(yīng)外部調(diào)用
為了方便開發(fā)者接入,默認是開啟了對外部組件調(diào)用的支持,并且不需要權(quán)限驗證。app正式發(fā)布前,建議調(diào)用CC.enableRemoteCC(false)來關(guān)閉響應(yīng)外部調(diào)用本app的組件。
3.8 如何與Activity、Fragment的生命周期關(guān)聯(lián)起來
背景:在使用異步調(diào)用時,由于callback對象一般是使用匿名內(nèi)部類,會持有外部類對象的引用,容易引起內(nèi)存泄露,這種內(nèi)存泄露的情況在各種異步回調(diào)中比較常見,如Handler.post(runnable)、Retrofit的Call.enqueue(callback)等。
為了避免內(nèi)存泄露及頁面退出后取消執(zhí)行不必要的任務(wù),CC添加了生命周期關(guān)聯(lián)的功能,在onDestroy方法被調(diào)用時自動cancel頁面內(nèi)所有未完成的組件調(diào)用
-
Activity生命周期關(guān)聯(lián)
在api level 14 (android 4.0)以上可以通過注冊全局activity生命周期回調(diào)監(jiān)聽,在onActivityDestroyed方法中找出所有此activity關(guān)聯(lián)且未完成的cc對象,并自動調(diào)用取消功能:
application.registerActivityLifecycleCallbacks(lifecycleCallback);- 1
-
android.support.v4.app.Fragment生命周期關(guān)聯(lián)
support庫從25.1.0開始支持給fragment設(shè)置生命周期監(jiān)聽:
FragmentManager.registerFragmentLifecycleCallbacks(callback)- 1
可在其onFragmentDestroyed方法中取消未完成的cc調(diào)用
-
andorid.app.Fragment生命周期關(guān)聯(lián)(暫不支持)
四、 CC執(zhí)行流程詳細解析
組件間通信采用了組件總線的方式,在基礎(chǔ)庫的組件管理類(ComponentMananger)中注冊了所有組件對象,ComponentMananger通過查找映射表找到組件對象并調(diào)用。
當ComponentMananger接收到組件的調(diào)用請求時,查找當前app內(nèi)組件清單中是否含有當前需要調(diào)用的組件
- 有: 執(zhí)行App內(nèi)部CC調(diào)用的流程:
-
沒有:執(zhí)行App之間CC調(diào)用的流程
4.1 組件的同步/異步實現(xiàn)和組件的同步/異步調(diào)用原理
- 組件實現(xiàn)時,當組件調(diào)用的相關(guān)功能結(jié)束后,通過CC.sendCCResult(callId, ccResult)將調(diào)用結(jié)果發(fā)送給框架
- IComponent實現(xiàn)類(組件入口類)onCall(cc)方法的返回值代表是否異步回調(diào)結(jié)果:
- true: 將異步調(diào)用CC.sendCCResult(callId, ccResult)
- false: 將同步調(diào)用CC.sendCCResult(callId, ccResult)。意味著在onCall方法執(zhí)行完之前會調(diào)用此方法將結(jié)果發(fā)給框架
- 當IComponent.onCall(cc)返回false時,直接獲取CCResult并返回給調(diào)用方
- 當IComponent.onCall(cc)返回true時,將進入wait()阻塞,知道獲得CCResult后通過notify()中止阻塞,繼續(xù)運行,將CCResult返回給調(diào)用方
- 通過ComponentManager調(diào)用組件時,創(chuàng)建一個實現(xiàn)了java.util.concurrent.Callable接口ChainProcessor類來負責(zé)具體組件的調(diào)用
- 同步調(diào)用時,直接執(zhí)行ChainProcessor.call()來調(diào)用組件,并將CCResult直接返回給調(diào)用方
- 異步調(diào)用時,將ChainProcessor放入線程池中執(zhí)行,通過IComponentCallback.onResult(cc, ccResult)將CCResult回調(diào)給調(diào)用方
執(zhí)行過程如下圖所示:
4.2 自定義攔截器(ICCInterceptor)實現(xiàn)原理
- 所有攔截器按順序存放在調(diào)用鏈(Chain)中
- 在自定義攔截器之前有1個CC框架自身的攔截器:
- ValidateInterceptor
- 在自定義攔截器之后有2個CC框架自身的攔截器:
- LocalCCInterceptor(或RemoteCCInterceptor)
- Wait4ResultInterceptor
- Chain類負責(zé)依次執(zhí)行所有攔截器interceptor.intercept(chain)
-
攔截器intercept(chain)方法通過調(diào)用Chain.proceed()方法獲取CCResult
4.3 App內(nèi)部CC調(diào)用流程
當要調(diào)用的組件在當前app內(nèi)部時,執(zhí)行此流程,完整流程圖如下:
CC的主體功能由一個個攔截器(ICCInterceptor)來完成,攔截器形成一個調(diào)用鏈(Chain),調(diào)用鏈由ChainProcessor啟動執(zhí)行,ChainProcessor對象在ComponentManager中被創(chuàng)建。
因此,可以將ChainProcessor看做一個整體,由ComponentManager創(chuàng)建后,調(diào)用組件的onCall方法,并將組件執(zhí)行后的結(jié)果返回給調(diào)用方。
ChainProcessor內(nèi)部的Wait4ResultInterceptor
ChainProcessor的執(zhí)行過程可以被timeout和cancel兩種事件中止。
4.4 App之間CC調(diào)用流程
當要調(diào)用的組件在當前app內(nèi)找不到時,執(zhí)行此流程,完整流程圖如下:
五、使用方式介紹
CC的集成非常簡單,僅需4步即可完成集成:
添加自動注冊插件
buildscript {dependencies {classpath 'com.billy.android:autoregister:1.0.4'} }- 1
- 2
- 3
- 4
- 5
引用apply cc-settings.gradle文件代替 ‘a(chǎn)pp plugin …’
apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'- 1
實現(xiàn)IComponent接口創(chuàng)建一個組件類
public class ComponentA implements IComponent { @Override public String getName() { //組件的名稱,調(diào)用此組件的方式: // CC.obtainBuilder("demo.ComponentA").build().callAsync() return "demo.ComponentA"; } @Override public boolean onCall(CC cc) { Context context = cc.getContext(); Intent intent = new Intent(context, ActivityComponentA.class); if (!(context instanceof Activity)) { //調(diào)用方?jīng)]有設(shè)置context或app間組件跳轉(zhuǎn),context為application intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(intent); //發(fā)送組件調(diào)用的結(jié)果(返回信息) CC.sendCCResult(cc.getCallId(), CCResult.success()); return false; } }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
使用CC.obtainBuilder("component_name").build().call()調(diào)用組件
//同步調(diào)用,直接返回結(jié)果 CCResult result = CC.obtainBuilder("demo.ComponentA").build().call(); //或 異步調(diào)用,不需要回調(diào)結(jié)果 CC.obtainBuilder("demo.ComponentA").build().callAsync(); //或 異步調(diào)用,在子線程執(zhí)行回調(diào) CC.obtainBuilder("demo.ComponentA").build().callAsync(new IComponentCallback(){...}); //或 異步調(diào)用,在主線程執(zhí)行回調(diào) CC.obtainBuilder("demo.ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...});- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
更多用法請看github上的README
結(jié)語
本文比較詳細地介紹了android組件化開發(fā)框架《CC》的主要功能、技術(shù)方案及執(zhí)行流程,并給出了使用方式的簡單示例。
大家如果感興趣的話可以從GitHub上clone源碼來進行具體的分析,如果有更好的思路和方案也歡迎貢獻代碼進一步完善CC。
系列文章
CC:可關(guān)聯(lián)生命周期的android組件化開發(fā)框架
CC框架實踐(1):實現(xiàn)登錄成功再進入目標界面功能
CC框架實踐(2):Fragment和View的組件化
CC框架實踐(3):讓jsBridge更優(yōu)雅
致謝
ActivityRouter
ARouter
ModularizationArchitecture
Android架構(gòu)思考(模塊化、多進程)
開源最佳實踐:Android平臺頁面路由框架ARouter
--------------------- 本文來自 lucky_billy 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/cdecde111/article/details/78705386?utm_source=copy
總結(jié)
以上是生活随笔為你收集整理的教你打造一个Android组件化开发框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DuerOS Java开发技能(二)第三
- 下一篇: 禁止 pdf 下载、打印的方法