DRouter:簡單易用的支援多程式架構的元件化方案

Dovar_66發表於2019-02-19

歡迎star/issue,專案地址:github.com/Dovar66/DRo…

寫在開頭

元件化技術適用於需要多人協作的中大型專案,如果是一個人的專案且開發人員未實踐過元件化方案則不建議採用。

元件化的優點

  1. 業務隔離,使得各業務模組專注於自己的業務實現,而不必關心其他業務模組.
  2. 單獨除錯,每個模組可以獨立執行,方便開發除錯.
  3. 元件可複用性,針對有重疊業務的不同APP,可直接使用元件來組裝.
  4. 適合AOP.
  5. 可以更細粒度的設定組員的程式碼修改許可權.

DRouter:簡單易用的支援多程式架構的元件化方案

demo下載

DRouter目前已應用於觸電新聞

DRouter主要提供三大功能:介面路由、動作路由和事件匯流排,幫助Android開發者更容易地完成專案的元件化改造。
特別是對於多程式應用,引入DRouter能夠讓開發者在不用瞭解AIDL的情況下就可以進行跨程式通訊.
複製程式碼

框架特點

* 完美支援多程式,且不需要使用者去bindService或自定義AIDL.
* 頁面路由:支援給Activity定義url,然後通過url跳轉到Activity,支援新增攔截器.
* 支援跨程式的API呼叫(動作路由).
* 支援跨程式的事件匯流排.
* 基於AOP引導Module的初始化以及頁面、攔截器、Provider的自動註冊.
複製程式碼

如何配置

1.在專案根目錄的 build.gradle 中新增 JitPack 倉庫:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}
複製程式碼

2.在BaseModule中新增依賴:

api 'com.github.Dovar66.DRouter:router-api:1.0.8'
複製程式碼

3.在其他需要用到DRouter的元件中新增註解處理器的依賴:

annotationProcessor 'com.github.Dovar66.DRouter:router-compiler:1.0.8'

同時在這些元件的defaultConfig中配置註解引數,指定唯一的元件名:

 defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }
複製程式碼

4.多程式配置:

* 如果你的專案需要使用多程式廣域路由,那麼請讓你的Application實現 IMultiProcess 介面,廣域路由預設是關閉狀態,只有實現了該介面才會啟用。

* 在App module的build.gradle檔案中,且必須在apply plugin: 'com.android.application'之後引用編譯外掛RouterPlugin,具體如下:

    apply plugin: 'com.android.application'

    apply plugin: "com.dovar.router.plugin" //必須在apply plugin: 'com.android.application'之後,否則找不到AppExtension

    buildscript {
        repositories {
            google()
            maven {
                url "https://plugins.gradle.org/m2/"
            }
        }
        dependencies {
            classpath "gradle.plugin.RouterPlugin:plugin:2.0.0"
        }
    }
複製程式碼

如何使用

在Application.onCreate()中完成初始化

DRouter.init(app);
複製程式碼

建立元件初始化入口(非必須)

在元件中建立BaseAppInit的子類,並新增Module註解:

    @Module
    public class AInit extends BaseAppInit {

        @Override
        public void onCreate() {
            super.onCreate();
            //與Application.onCreate()的執行時機相同
            //建議在這裡完成元件內的初始化工作
        }
    }
    
註解處理器會將AInit註冊到DRouter,當DRouter初始化時,會呼叫被註冊的BaseAppInit子類的onCreate().
BaseAppInit中提供了Application例項,用於元件工程中獲取全域性的應用上下文.
複製程式碼

頁面路由

 新增Route註解,可通過interceptor設定攔截器:

 @Route(path = "/b/main", interceptor = BInterceptor.class)
 public class MainActivity extends AppCompatActivity {

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.module_b_activity_main);
     }
 }

 然後在專案中使用DRouter進行頁面跳轉:

 DRouter.navigator("/b/main").navigateTo(mContext);
複製程式碼

動作路由(API呼叫)

建立相應的AbsProvider子類並新增Provider註解,然後在類中註冊Action:

@Provider(key = "a")
public class AProvider extends AbsProvider {
    @Override
    protected void registerActions() {

        registerAction("test1", new Action() {
            @Override
            public RouterResponse invoke(@NonNull Bundle params, Object extra) {
                Toast.makeText(appContext, "彈個窗", Toast.LENGTH_SHORT).show();
                return null;
            }
        });

        registerAction("test2", new Action() {
             @Override
             public RouterResponse invoke(@NonNull Bundle params, Object extra) {
                if (extra instanceof Context) {
                   Toast.makeText((Context) extra, params.getString("content"), Toast.LENGTH_SHORT).show();
                }
                return null;
             }
        });
    }
}

接下來就可以在專案中使用:

       DRouter.router("a","test1").route();

       DRouter.router("a","test2")
                       .withString("content","也彈個窗")
                       .extra(context)
                       .route();
       //跨程式呼叫            
       DRouter.multiRouter("a","test1").route(process);
       
需要注意的是,跨程式呼叫時,目標Action將會在Binder執行緒中執行,可以通過設定runOnUiThread指定Action在ui執行緒執行.

       DRouter.multiRouter("a","test1").runOnUiThread().route(process);
複製程式碼

事件匯流排

訂閱事件
有兩種訂閱方式:

1.生命週期感知,不需要手動取消訂閱:

    DRouter.subscribe(this, ServiceKey.EVENT_A, new EventCallback() {
        @Override
        public void onEvent(Bundle e) {
            Toast.makeText(MainActivity.this, "/b/main/收到事件A", Toast.LENGTH_SHORT).show();
        }
    });

2.需要手動取消訂閱:

    Observer<Bundle> mObserver = DRouter.subscribeForever("event_a", new EventCallback() {
        @Override
        public void onEvent(Bundle e) {
            Toast.makeText(MainActivity.this, "/b/main/收到事件A", Toast.LENGTH_SHORT).show();
        }
    });
複製程式碼
釋出事件(在任意執行緒)(開啟多程式配置後,事件會分發到所有程式)
 Bundle bundle = new Bundle();
 bundle.putString("content", "事件A");
 DRouter.publish(ServiceKey.EVENT_A, bundle);
複製程式碼
退訂事件(通過subscribeForever()訂閱時,需要及時取消訂閱)
 DRouter.unsubscribe("event_a", mObserver);
複製程式碼

混淆配置

-keep class com.dovar.router.generate.** { *; }
複製程式碼

DRouter的元件化實現原理

介面路由

支援給Activity定義path,然後通過path跳轉到Activity,可設定跳轉攔截器.
複製程式碼

介面路由

動作路由

服務提供者向DRouter註冊Action實現對其他元件和程式暴露服務。注意:跨程式呼叫時傳遞的引數需要實現序列化,否則會被DRouter過濾掉.
複製程式碼

動作路由

事件匯流排

事件匯流排

元件化專案架構圖

元件化專案架構圖

關於APP殼工程

管理打包配置.
設定元件引用.
集中管理混淆規則,各個元件中不再配置混淆檔案.
複製程式碼

關於應用元件層

業務中心,包含所有業務元件.
複製程式碼

關於公共服務層

管理跨元件呼叫和公共資源,詳細可參考專案中的common_service.

為什麼要在基礎框架層和應用元件層中間多架設一個公共服務層?

* 封裝對基礎框架層功能API的呼叫,方便應對日後更換第三方庫的需求,相信很多程式設計師都經歷過更換第三方庫(特別是基礎庫)的痛苦啦,
  如果專案中沒有自己封裝而是直接引用第三方API的話,等到要換的時候就會發現需要修改的程式碼實在太多了。
* 儲存公用資源和程式碼,暴露給上層業務使用,同時避免這些資源被下沉到基礎框架層,從而減少對基礎框架層的非必要更新。
  在多人協作專案中,基礎框架必須是穩定的,所以我們希望有儘可能少的commit指向基礎框架層。
複製程式碼

關於基礎框架層

與業務無關的通用功能模組,如網路請求、圖片載入、通用的自定義控制元件等.
複製程式碼

元件化後如何配置Module單獨除錯?

設定一個可執行的殼工程,如示例中的app.然後在殼工程中配置元件依賴,要單獨除錯某個元件的時候將其他元件依賴註釋掉即可.
複製程式碼

元件化後的資原始檔處理

1. AndroidManifest.xml合併:

每個module都有一份自己的AndroidManifest清單檔案,在APP的編譯過程中最終會將所有module的清單檔案合併成一份。

我們可以在配置為Application的module下的build/intermediates/manifests路徑下找到合成後的AndroidManifest檔案,對比編譯前後的差異就能大致分析出合併規則和衝突處理規則。

需要注意的是如果在多個module中出現同名資源(如 android:label="@string/app_name"),且同名資源被合成後的AndroidManifest.xml引用,則會優先取用當前ApplicationModule的資源。

2. R檔案:

libModule中R檔案裡的id不再是靜態常量,而是靜態變數,所以不能再使用switch..case..語法操作資源id
複製程式碼

3. 其他resource:

1. 防止出現同名資源,建議每個module下的資源命名都增加唯一識別字元,如module-live中的都帶字首"mlive_",mlive_icon_close.png

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 27
    
        defaultConfig {
            minSdkVersion 15
            targetSdkVersion 27
            ...
        }
    
        resourcePrefix "module_a_" //可以利用 resourcePrefix 限定資源命名字首
    }

2. 關於資源的拆分,一些style、常見的string、共用的圖片、drawable等資源,建議存放在common_service當中。對於屬於不同模組的資源則應該存放在各自的module中。
複製程式碼

元件化後的Git部署

Note:你依然可以沿用現有專案的版本管理方案,可跳過此部分內容.
複製程式碼

一般我們專案只會對應於一個Git倉庫,於是所有開發成員都可以對專案中所有程式碼進行編輯並提交修改,但作為專案管理者,我們想讓成員只能提交自己負責的業務程式碼,避免成員在開發過程中不小心修改了其他成員的程式碼導致出現bug。而通過元件化後,我們就可以根據成員開發職責重新設定程式碼修改許可權啦。

假設:在開發過程中基礎框架層不變,甲負責module_a,乙負責module_b,丙負責module_c和打包,甲乙丙都可以修改common_service。於是我們將module_a/module_b/module_c/common_service全都獨立成子倉庫,module_a倉庫只對甲開放修改許可權,module_b倉庫只對乙開放修改許可權,module_c倉庫只對丙開放修改許可權,於是就有下圖所示的許可權分佈:

讀寫許可權

有以下三種Git部署方式可供選擇:

* 主專案下直接部署多個子倉庫
    直接在主專案下引入所有子倉庫.
    優點:不用學習新的Git命令.
    缺點:下載主專案時操作較複雜,需要先clone主專案然後再在主專案下clone所有子專案.(可以通過配置指令碼完成一鍵clone).
    
* git submodule
    關於submodule如何使用請自行百度.
    優點:使用比subtree簡單,也沒有第一種方式的Git clone問題.
    缺點:submodule的方式不能將在主專案中對子專案的修改推送到子專案倉庫.
    
* git subtree
    [如何使用](https://blog.csdn.net/Dovar_66/article/details/83185288)
    優點:可以將在主專案中對子專案的修改推送到子專案倉庫.
    缺點:Git命令使用較複雜,組員學習成本高.
複製程式碼

我目前用的第一種方式。附一個AS外掛,可能用得上:一鍵Git Pull專案下所有倉庫的當前分支

如何逐步的進行元件化改造?

完全的元件化拆分並非一兩日就能完成,而我們的專案卻總會不斷有新的需求等待開發,版本迭代工作幾乎註定了我們不可能將專案需求暫停來做元件化。
那麼,版本迭代與元件化拆分就需要同步進行,下面是我的建議:

1.開始準備:
* 新增APP殼工程,建議參考本專案中的app工程.
* 將你專案當前的application工程作為公共服務層(後面直接用common_service表示),當然,由於還未開始拆分元件,所以此時它也是最大的業務元件.
* 新增一個元件(後面用module_search表示),建議優先選擇一個自己最熟悉或相對簡單的業務模組著手,比如我自己公司專案的搜尋模組,它只有搜尋功能且與其他模組互動很少,所以我選擇由它開始。

2.建立依賴鏈:
殼工程依賴common_service.
殼工程依賴module_search.
module_search依賴common_service.(implementation依賴)

3.引入DRouter:
參考上面的DRouter使用說明.

4.分離公共服務層與基礎架構層(非必須):
如果之前你的專案沒有分離業務層和基礎架構層,那麼建議你現在將基礎架構從common_service中抽離出來.

5.逐步拆分元件:(這個過程短則幾天,長則數月,專案越大耦合越重則耗時越長,建議徐徐推進)
    拆分第一個元件:
        * 前面我們已經新增了module_search,所以現在要將搜尋模組的程式碼從common_service中抽離並放入搜尋元件,此時搜尋元件依然可以直接引用公共服務層的程式碼,但公共服務層則只能通過路由使用搜尋功能.
        * 在開發主線做新需求時,新增的資原始檔建議最好放到對應的元件工程中,之前的資原始檔可以暫時保留在公共服務層,等待後續由各個元件工程認領走,儘可能少的積壓在公共服務層。
        * 許可權、Android四大元件、特有的第三方庫引用等都應該宣告在對應的元件Module中,而不應沉入公共服務層,更不允許進入基礎框架層。
    (在第一個元件拆分成功並推入市場後,如果反饋良好,那麼就可以繼續拆分其他的元件了)
    拆分第二個元件、第三個...
元件拆分粒度取決於你的業務模組和組員職責,請按需拆分。
複製程式碼

我的其他專案:同學,你的系統Toast可能需要修復一下!

相關文章