CC:基於總線的android組件化開發框架

*本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈html

CC:Component Caller,一個android組件化開發框架, 已開源,github地址:github.com/luckybilly/… 本文主要講解框架實現原理,若是隻是想了解一下如何使用,可直接到github上查看README文檔java

前言


首先說明一下,本文將講述的組件化與業內的插件化(如:Atlas, RePlugin等)不是同一個概念android

組件化 vs 插件化
【圖片來源於網絡】

組件化開發:就是將一個app分紅多個Module,每一個Module都是一個組件(也能夠是一個基礎庫供組件依賴),開發的過程當中咱們能夠單獨調試部分組件,組件間不須要互相依賴,但能夠相互調用,最終發佈的時候全部組件以lib的形式被主app工程依賴並打包成1個apk。git

插件化開發:和組件化開發略有不用,插件化開發時將整個app拆分紅不少模塊,這些模塊包括一個宿主和多個插件,每一個模塊都是一個apk(組件化的每一個模塊是個lib),最終打包的時候將宿主apk和插件apk(或其餘格式)分開或者聯合打包。github

本文將主要就如下幾個方面進行介紹:web

1、爲何須要組件化?數據庫

2、CC的功能介紹api

3、CC技術要點安全

4、CC執行流程詳細解析服務器

5、使用方式介紹

6、老項目進行組件化改造的成本有多高?

1、爲何須要組件化?


關於使用組件化的理由,上網能搜到不少,如業務隔離、單獨以app運行能提升開發及調試效率等等這裏就很少重複了,我補充一條:組件化以後,咱們能很容易地實現一些組件層面的AOP,例如:

  • 輕易實現頁面數據(網絡請求、I/O、數據庫查詢等)預加載的功能
  • 組件被調用時,進行頁面跳轉的同時異步執行這些耗時邏輯
  • 頁面跳轉並初始化完成後,再將這些提早加載好的數據展現出來
  • 在組件功能調用時進行登陸狀態校驗
  • 藉助攔截器機制,能夠動態給組件功能調用添加不一樣的中間處理邏輯

2、CC的功能介紹


  1. 支持組件間相互調用(不僅是Activity跳轉,支持任意指令的調用/回調)
  2. 支持組件調用與Activity、Fragment的生命週期關聯
  3. 支持app間跨進程的組件調用(組件開發/調試時可單獨做爲app運行)
  • 在獨立運行組件時很是有用,好比:一個組件的某個功能要用到用戶的登陸信息,若未登陸則調起登陸組件的登陸頁面,若已登陸則獲取當前用戶信息。此時能夠直接使用主app中的登陸組件及用戶在主app中的登陸狀態,該組件做爲app獨立運行時無需依賴登陸組件,能夠始終保持獨立運行狀態進行開發。
  1. 支持app間調用的開關及權限設置(知足不一樣級別的安全需求,默認打開狀態且不須要權限)
  2. 支持同步/異步方式調用
  3. 支持同步/異步方式實現組件
  4. 調用方式不受實現方式的限制(例如:能夠異步調用另外一個組件的同步實現功能。注:不要在主線程同步調用耗時操做)
  5. 支持添加自定義攔截器(按添加的前後順序執行)
  6. 支持超時設置
  7. 支持手動取消
  8. 編譯時自動註冊組件(IComponent),無需手動維護組件註冊表(使用ASM修改字節碼的方式實現)
  9. 支持動態註冊/反註冊組件(IDynamicComponent)
  10. 支持組件間傳遞Fragment等非基礎類型的對象(組件在同一個app內時支持、跨app傳遞非基礎類型的對象暫不支持)
  11. 儘量的解決了使用姿式不正確致使的crash:
  • 組件調用處、回調處、組件實現處的crash所有在框架內部catch住
  • 同步返回或異步回調的CCResult對象必定不爲null,避免空指針,同時能夠根據CCResult的結果進行降級處理。

demo效果演示

組件A打包在主app中,組件B爲單獨運行的組件app,下圖演示了在主app中調用二者的效果,並將結果以Json的格式顯示在下方。demo下載地址):

demo

3、 CC技術要點

實現CC組件化開發框架主要須要解決的問題有如下幾個方面:

  • 組件如何自動註冊?
  • 如何兼容同步/異步方式調用組件?
  • 如何兼容同步/異步方式實現組件?
  • 如何跨app調用組件?
  • 組件如何更方便地在application和library之間切換?
  • 如何實現startActivityForResult?
  • 如何阻止非法的外部調用?
  • 如何與Activity、Fragment的生命週期關聯起來

3.1 組件如何自動註冊?

爲了減小後期維護成本,想要實現的效果是:當須要添加某個組件到app時,只須要在gradle中添加一下對這個module的依賴便可(一般都是maven依賴,也能夠是project依賴)

最初想要使用的是annotationProcessor經過編譯時註解動態生成組件映射表代碼的方式來實現。但嘗試事後發現行不通,由於編譯時註解的特性只在源碼編譯時生效,沒法掃描到aar包裏的註解(project依賴、maven依賴均無效),也就是說必須每一個module編譯時生成本身的代碼,而後要想辦法將這些分散在各aar種的類找出來進行集中註冊。

ARouter的解決方案是:

  • 每一個module都生成本身的java類,這些類的包名都是'com.alibaba.android.arouter.routes'
  • 而後在運行時經過讀取每一個dex文件中的這個包下的全部類經過反射來完成映射表的註冊,詳見ClassUtils.java源碼

運行時經過讀取全部dex文件遍歷每一個entry查找指定包內的全部類名,而後反射獲取類對象。這種效率看起來並不高。

ActivityRouter的解決方案是(demo中有2個組件名爲'app'和'sdk'):

  • 在主app module中有一個@Modules({"app", "sdk"})註解用來標記當前app內有多少組件,根據這個註解生成一個RouterInit類

  • 在RouterInit類的init方法中生成調用同一個包內的RouterMapping_app.map

  • 每一個module生成的類(RouterMapping_app.java 和 RouterMapping_sdk.java)都放在com.github.mzule.activityrouter.router包內(在不一樣的aar中,但包名相同)

  • 在RouterMapping_sdk類的map()方法中根據掃描到的當前module內全部路由註解,生成了調用Routers.map(...)方法來註冊路由的代碼

  • 在Routers的全部api接口中最終都會觸發RouterInit.init()方法,從而實現全部路由的映射表註冊

    這種方式用一個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: 能夠讀取分析字節碼、能夠修改字節碼

兩者結合,能夠作一個gradle插件,在編譯時自動掃描全部組件類(IComponent接口實現類),而後修改字節碼,生成代碼調用掃描到的全部組件類的構造方法將其註冊到一個組件管理類(ComponentManager)中,生成組件名稱與組件對象的映射表。

此gradle插件被命名爲:AutoRegister,現已開源,並將功能升級爲編譯時自動掃描任意指定的接口實現類(或類的子類)並自動註冊到指定類的指定方法中。只須要在app/build.gradle中配置一下掃描的參數,沒有任何代碼侵入,原理詳細介紹傳送門

3.2 如何兼容同步/異步方式調用組件?

經過實現java.util.concurrent.Callable接口同步返回結果來兼容同步/異步調用:

  • 同步調用時,直接調用CCResult result = Callable.call()來獲取返回結果
  • 異步調用時,將其放入線程池中運行,執行完成後調用回調對象返回結果: IComponentCallback.onResult(cc, result)
ExecutorService.submit(callable)
複製代碼

3.3 如何兼容同步/異步方式實現組件?

調用組件的onCall方法時,可能須要異步實現,並不能同步返回結果,但同步調用時又須要返回結果,這是一對矛盾。 此處用到了Object的wait-notify機制,當組件須要異步返回結果時,在CC框架內部進行阻塞,等到結果返回時,經過notify停止阻塞,返回結果給調用方

注意,這裏要求在實現一個組件時,必須確保組件必定會回調結果,即:須要確保每一種致使調用流程結束的邏輯分支上(包括if-else/try-catch/Activity.finish()-back鍵-返回按鈕等等)都會回調結果,不然會致使調用方一直阻塞等待結果,直至超時。相似於向服務器發送一個網絡請求後服務器必須返回請求結果同樣,不然會致使請求超時。

3.4 如何跨app調用組件?

爲何須要跨app進行組件調用呢?

  1. 對現有項目進行組件化改造的過程,確定不是一蹴而就,而是一個個組件逐步從主工程中抽離,這就涉及到主工程與組件間的通訊。若是不能跨app進行組件調用,開發時就須要跟主工程一塊兒打包,失去了組件化開發的一個很是大的優點:組件單獨編譯運行提升開發&測試效率。
  2. 當獨立運行的組件須要調用到其餘組件的功能時,不須要將其餘組件編譯進來一塊兒打包,能夠調用主app中的組件,能夠始終保持單module編譯運行的狀態進行開發。

目前,常見的組件化框架採用的跨app通訊解決方案有:

  • URLScheme(如:ActivityRouter阿里ARouter等)
    • 優勢:
      • 基因中自帶支持從webview中調用
      • 不用互相註冊(不用知道須要調用的app的進程名稱等信息)
    • 缺點:
      • 只能單向地給組件發送信息,適用於啓動Activity和發送指令,不適用於獲取數據(例如:獲取用戶組件的當前用戶登陸信息)
      • 須要有個額外的中轉Activity來統一處理URLScheme,而後進行轉發
      • 若是設備上安裝了多個使用相同URLScheme的app,會彈出選擇框(多個組件做爲app同時安裝到設備上時會出現這個問題)
      • 沒法進行權限設置,沒法進行開關設置,任意app均可調用,存在安全性風險
  • AIDL (如:ModularizationArchitecture)
    • 優勢:
      • 能夠傳遞Parcelable類型的對象
      • 效率高
      • 能夠設置跨app調用的開關
    • 缺點:
      • 調用組件以前須要提早知道該組件在那個進程,不然沒法創建ServiceConnection
      • 組件在做爲獨立app和做爲lib打包到主app時,進程名稱不一樣,維護成本高

設計此功能時,個人出發點是:做爲組件化開發框架基礎庫,想盡可能讓跨進程調用與在進程內部調用的功能一致,對使用此框架的開發者在切換app模式和lib模式時儘可能簡單,另外須要儘可能不影響產品安全性。所以,跨組件間通訊實現的同時,應該知足如下條件:

  • 每一個app都能給其它app調用
  • app能夠設置是否對外提供跨進程組件調用的支持
  • 組件調用的請求發出去以後,能自動探測當前設備上是否有支持這次調用的app
  • 支持超時、取消

基於這些需求,我最終選擇了BroadcastReceiver + Service + LocalSocket來做爲最終解決方案:

若是appA內發起了一個當前app內不存在的組件:Component1,則創建一個LocalServerSocket,同時發送廣播給設備上安裝的其它一樣使用了此框架的app,同時,若某個appB內支持此組件,則根據廣播中帶來的信息與LocalServerSocket創建鏈接,並在appB內調用組件Component1,並將結果經過LocalSocket發送給appA。 BroadcastReceiver是android四大組件之一,能夠設置接收權限,能避免外部惡意調用。而且能夠設置開關,接收到此廣播後決定是否響應(僞裝沒接收到...)。 之因此創建LocalSocket連接,是爲了能繼續給此次組件調用請求發送超時和取消的指令。

用這種方式實現時,遇到了3個問題:

  • 因爲廣播接收器定義在基礎庫中,全部app內都有,當用戶在主線程中同步調用跨app的組件時,調用方主線程被阻塞,廣播接收器也在須要主線程中運行,致使廣播接收器沒法運行,直至timeout,組件調用失敗。
    • 將廣播接收器放到子進程中運行問題獲得解決
  • 被調用的app未啓動或被手動結束進程,遇到廣播接收不到的問題
    • 這個問題暫時未很好的解決,但考慮到組件化開發只在開發期間須要用到跨進程通訊,開發者能夠經過手動在系統設置中給對應的app賦予自啓動權限來解決問題
  • 跨進程調用時,只能傳遞基本數據類型,沒法獲取Fragment等java對象
    • 這個問題在app內部調用時不存在,app內部來回傳遞的都是Map,能夠傳遞任何數據類型。但因爲進程間通訊是經過字符串來回發送的,暫時支持不了非基本數據類型,將來能夠考慮支持Serializable

3.5 組件如何更方便地在application和library之間切換?

關於切換方式在網絡上有不少文章介紹,基本上都是一個思路:在module的build.gradle中設置一個變量來控制切換apply plugin: 'com.android.application'apply plugin: 'com.android.library'以及sourceSets的切換。 爲了不在每一個module的build.gradle中配置太多重複代碼,我作了個封裝,默認爲library模式,提供2種方式切換爲application模式:在module的build.gradle中添加ext.runAsApp = true或在工程根目錄中local.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'
複製代碼

注:cc-settings.gradle源碼傳送門

3.6 如何實現startActivityForResult?

android的startActivityForResult的設計也是爲了頁面傳值,在CC組件化框架中,頁面傳值根本不須要用到startActivityForResult,直接做爲異步實現的組件來處理(在原來setResult的地方調用CC.sendCCResult(callId, ccResult)另外須要注意:按back鍵及返回按鈕的狀況也要回調結果)便可。

若是是原來項目中存在大量的startActivityForResult代碼,改形成本較大,能夠用下面這種方式來保留原來的onActivityResult(...)及activity中setResult相關的代碼:

  • 在原來調用startActivityForResult的地方,改用CC方式調用,將當前context傳給組件

    CC.obtainBuilder("demo.ComponentA")
    	.setContext(context)
    	.addParams("requestCode", requestCode)
    	.build()
    	.callAsync();
    複製代碼
  • 在組件的onCall(cc)方法中用startActivityForResult的方式打開Activity

    @Override
       public 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)) {
               //調用方沒有設置context或app間組件跳轉,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;
       }
    複製代碼

3.7 如何阻止非法的外部調用?

爲了適應不一樣需求,有2個安全級別能夠設置:

  • 權限驗證(給進程間通訊的廣播設置權限,通常可設置爲簽名級權限校驗),步驟以下:

    • 新建一個module
    • 在該module的build.gradle中添加對基礎庫的依賴,如: compile 'com.billy.android:cc:0.3.0'
    • 在該module的src/main/AndroidManifest.xml中設置權限及權限的級別,參考component_protect_demo
    • 其它每一個module都額外依賴此module,或自定義一個全局的cc-settings.gradle,參考cc-settings-demo-b.gradle
  • 外部調用是否響應的開關設置(這種方式使用起來更簡單一些)

    • 在Application.onCreate()中調用CC.enableRemoteCC(false)可關閉響應外部調用

爲了方便開發者接入,默認是開啓了對外部組件調用的支持,而且不須要權限驗證。app正式發佈前,建議調用CC.enableRemoteCC(false)來關閉響應外部調用本app的組件。

3.8 如何與Activity、Fragment的生命週期關聯起來

背景:在使用異步調用時,因爲callback對象通常是使用匿名內部類,會持有外部類對象的引用,容易引發內存泄露,這種內存泄露的狀況在各類異步回調中比較常見,如Handler.post(runnable)、Retrofit的Call.enqueue(callback)等。

爲了不內存泄露及頁面退出後取消執行沒必要要的任務,CC添加了生命週期關聯的功能,在onDestroy方法被調用時自動cancel頁面內全部未完成的組件調用

  • Activity生命週期關聯

    在api level 14 (android 4.0)以上能夠經過註冊全局activity生命週期回調監聽,在onActivityDestroyed方法中找出全部此activity關聯且未完成的cc對象,並自動調用取消功能:

    application.registerActivityLifecycleCallbacks(lifecycleCallback);
    複製代碼
  • android.support.v4.app.Fragment生命週期關聯

    support庫從 25.1.0 開始支持給fragment設置生命週期監聽:

    FragmentManager.registerFragmentLifecycleCallbacks(callback)
    複製代碼

    可在其onFragmentDestroyed方法中取消未完成的cc調用

  • andorid.app.Fragment生命週期關聯(暫不支持)

4、 CC執行流程詳細解析

組件間通訊採用了組件總線的方式,在基礎庫的組件管理類(ComponentMananger)中註冊了全部組件對象,ComponentMananger經過查找映射表找到組件對象並調用。

當ComponentMananger接收到組件的調用請求時,查找當前app內組件清單中是否含有當前須要調用的組件

  • 有: 執行App內部CC調用的流程:

App內部組件調用總線

  • 沒有:執行App之間CC調用的流程

    App之間組件調用總線

4.1 組件的同步/異步實現和組件的同步/異步調用原理

  • 組件實現時,當組件調用的相關功能結束後,經過CC.sendCCResult(callId, ccResult)將調用結果發送給框架
  • IComponent實現類(組件入口類)onCall(cc)方法的返回值表明是否異步回調結果:
    • true: 將異步調用CC.sendCCResult(callId, ccResult)
    • false: 將同步調用CC.sendCCResult(callId, ccResult)。意味着在onCall方法執行完以前會調用此方法將結果發給框架
  • 當IComponent.onCall(cc)返回false時,直接獲取CCResult並返回給調用方
  • 當IComponent.onCall(cc)返回true時,將進入wait()阻塞,知道得到CCResult後經過notify()停止阻塞,繼續運行,將CCResult返回給調用方
  • 經過ComponentManager調用組件時,建立一個實現了java.util.concurrent.Callable接口ChainProcessor類來負責具體組件的調用
    • 同步調用時,直接執行ChainProcessor.call()來調用組件,並將CCResult直接返回給調用方
    • 異步調用時,將ChainProcessor放入線程池中執行,經過IComponentCallback.onResult(cc, ccResult)將CCResult回調給調用方

執行過程以下圖所示:

CC兼容同步/異步調用和實現原理圖

4.2 自定義攔截器(ICCInterceptor)實現原理

  • 全部攔截器按順序存放在調用鏈(Chain)中

  • 在自定義攔截器以前有1個CC框架自身的攔截器:

    • ValidateInterceptor
  • 在自定義攔截器以後有2個CC框架自身的攔截器:

    • LocalCCInterceptor(或RemoteCCInterceptor)
    • Wait4ResultInterceptor
  • Chain類負責依次執行全部攔截器interceptor.intercept(chain)

  • 攔截器intercept(chain)方法經過調用Chain.proceed()方法獲取CCResult

    攔截器調用流程

4.3 App內部CC調用流程

當要調用的組件在當前app內部時,執行此流程,完整流程圖以下:

App內部CC調用流程圖

CC的主體功能由一個個攔截器(ICCInterceptor)來完成,攔截器造成一個調用鏈(Chain),調用鏈由ChainProcessor啓動執行,ChainProcessor對象在ComponentManager中被建立。 所以,能夠將ChainProcessor看作一個總體,由ComponentManager建立後,調用組件的onCall方法,並將組件執行後的結果返回給調用方。 ChainProcessor內部的Wait4ResultInterceptor ChainProcessor的執行過程能夠被timeout和cancel兩種事件停止。

4.4 App之間CC調用流程

當要調用的組件在當前app內找不到時,執行此流程,完整流程圖以下:

App之間CC調用流程圖

5、使用方式介紹

CC的集成很是簡單,僅需4步便可完成集成:

  1. 添加自動註冊插件

    buildscript {
        dependencies {
            classpath 'com.billy.android:autoregister:1.0.4'
        }
    }
    複製代碼
  2. 引用apply cc-settings.gradle文件代替 'app plugin ...'

    apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'
    複製代碼
  3. 實現IComponent接口建立一個組件類

    public class ComponentA implements IComponent {
        
        @Override
        public String getName() {
            //組件的名稱,調用此組件的方式:
            // 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)) {
                //調用方沒有設置context或app間組件跳轉,context爲application
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            context.startActivity(intent);
            //發送組件調用的結果(返回信息)
            CC.sendCCResult(cc.getCallId(), CCResult.success());
            
            return false;
        }
    }
    複製代碼
  4. 使用CC.obtainBuilder("component_name").build().call()調用組件

    //同步調用,直接返回結果
    CCResult result = CC.obtainBuilder("demo.ComponentA").build().call();
    //或 異步調用,不須要回調結果
    CC.obtainBuilder("demo.ComponentA").build().callAsync();
    //或 異步調用,在子線程執行回調
    CC.obtainBuilder("demo.ComponentA").build().callAsync(new IComponentCallback(){...});
    //或 異步調用,在主線程執行回調
    CC.obtainBuilder("demo.ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...});
    複製代碼

更多用法請看github上的README

PS:配合個人另外一個庫(PreLoader)一塊兒食用味道更佳:AOP實如今打開頁面以前預加載頁面所需的數據,而且這個預加載功能徹底在組件內部實現,與外部無耦合。

6、老項目進行組件化改造的成本有多高?

有些同窗很想嘗試組件化開發,但僅僅停留在瞭解的階段,緣由是擔憂在老項目上進行改造的工程量太大,不敢大改。

CC框架自己就是在老項目進行組件化改造的需求下設計出來的,考慮到了組件化過程當中的一些痛點:

  • 萬事開頭難,不要太多配置:CC將配置封裝到了一個gradle文件,須要組件化的module在其build.gradle中添加apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'便可
  • 萬事開頭難,不要改動太大:其實,在組件化改造以前,若是封裝的比較好的話,不少lib module與主工程的耦合僅僅是Activity的類名。例如咱們項目中的收銀臺,組件化以前經過startActivityForResult的方式打開收銀臺主界面並經過setResult的方式返回結果給調用方,只須要在收銀臺module中增長一個IComponent的實現類,在其onCall方法中調用cc.getContext().startActivityForResult打開收銀臺頁面,並將原來經過startActivityForResult打開收銀臺的地方改爲CC調用便可,收銀臺module原來的代碼紋絲不動。後續優化時再將結果的返回方式從setResult換成CC.sendCCResult(callId, result)就完全完成了收銀臺的組件化改造工做。
  • BaseActivity/BaseFragment/Util類/MVP封裝的各類Base等:組件化改造並非讓全部module都變成組件,還有一些能夠下沉做爲公共庫,
  • 模塊太複雜,短期內改不動: 頁面改版是個很好的機會,提早與產品經理溝通,獲知那些模塊即將改版,乘着改版對模塊進行組件化改造一箭雙鵰。咱們的詳情頁、購物車、首頁都是在這個過程當中完成組件化改造的。
  • 依賴太多,解耦不便:在改造初期,能夠先粗粒度定義組件,熟練以後再繼續優化
  • 組件化開發框架學習成本高,要團隊內每一個人都瞭解須要較長時間:CC爲全部的組件調用提供了統一的調用方式和實現方式,沒有任何註解、僅1個接口類。另外library模式切換成application的方式簡化爲在local.properties中配置一行module名稱=true便可。幾乎零門檻進行組件化開發

結語


本文比較詳細地介紹了android組件化開發框架《CC》的主要功能、技術方案及執行流程,並給出了使用方式的簡單示例。 你們若是感興趣的話能夠從GitHub上clone源碼來進行具體的分析,若是有更好的思路和方案也歡迎貢獻代碼進一步完善CC。

系列文章


CC框架實踐(1):實現登陸成功再進入目標界面功能

CC框架實踐(2):Fragment和View的組件化

CC框架實踐(3): 讓jsBridge更優雅

致謝


ActivityRouter

ARouter

ModularizationArchitecture

Android架構思考(模塊化、多進程)

開源最佳實踐:Android平臺頁面路由框架ARouter

DDComponentForAndroid

Router

交流


billy(齊翊)的微信二維碼
相關文章
相關標籤/搜索