醫動力Android基於CC元件化框架的探索與實踐

再見理想2017發表於2018-09-16

為什麼要元件化?

醫動力App作為公司的核心產品已經有多年曆史了,隨著版本的不斷迭代,功能越來越多,程式碼量越來越大,不可避免的會產生一下問題:

  • 業務越來越複雜,維護成本高;
  • 業務耦合度高,程式碼越來越臃腫,團隊內部多人協作開發困難;
  • 編譯時間長,每修改一處程式碼後都要重新編譯打包測試,導致非常耗時;
  • 開發測試困難,每次修改都必須打包整個專案執行.

因此,為了提高專案的可維護性和開發效率,元件化成為了必然.

元件化的目標

首先看看老版本的醫動力的專案工程結構

醫動力Android基於CC元件化框架的探索與實踐

專案分為Doctor和Patient,它們之間公共的程式碼放在common中,common中會依賴一些第三方的庫.隨著專案的迭代,Doctor和Patient裡面的程式碼會越來越耦合,因為單個module中僅僅是以包名作為功能的劃分,而包之間是可以隨意呼叫的.

AndroidStudio/IDEA是多模組管理的,元件化的思路就是把不同的業務模組拆分到各個子Module中,同時這些業務Module之間不會存在直接的呼叫,這樣當我們移除專案中的某個業務Module的時候不會影響整體的專案執行,如下圖所示:

醫動力Android基於CC元件化框架的探索與實踐

可以看到有「ModuleIM」,「ModuleFollow」,「ModuleDiary」三個業務模組,它們都會引用Common模組而獲取一些基礎庫的支援,同時這些業務模組是可以單獨執行的.後續會將更多的模組獨立出來,完成徹底的元件化.

如何元件化?

Android的元件化的技術點主要在兩個方面:

  • 配置元件獨立執行的能力;
  • 元件之間的通訊

第一點是通過在module的gradle.build中切換

apply plugin: 'com.android.application'
apply plugin: 'com.android.library',
複製程式碼

並設定對應模式下載入的AndroidManifest.xml和src的路徑.

第二點由於元件之間是沒有直接依賴的關係的,要想讓它們通訊就必須把它們註冊在一個公共的地方,這裡可以通過路由,也可以通過註冊介面.

目前元件化的方案在網上有很多,由於元件化並不涉及Android系統級別的操作,因此是比較成熟穩定的.我們知道,元件化是需要花費大量時間和精力的,很難做到把專案徹底的拆分成元件,那有沒有一種方式可以讓我們不改動原有專案(改動很小)的情況下,將新功能進行元件化開發?

因此我們選擇了使用CC來進行元件化的改造

Component Caller(CC)介紹

業界首個支援漸進式元件化改造的Android元件化開源框架

引用自CC官方的兩張圖能讓我們很快的明白什麼叫做漸進式元件化?

醫動力Android基於CC元件化框架的探索與實踐

醫動力Android基於CC元件化框架的探索與實踐

在漸進式元件化的方案中,可以先不用解耦,只需要讓單獨執行的元件能夠呼叫到主App中的功能即可。思路是這樣的:

  • 新業務以元件形式開發
  • 新元件需要呼叫的主App中的業務,在對應的模組中建立一個元件類,對外暴露對應的服務,供其它元件呼叫,並不需要現在就將這個模組解耦
  • 新元件通過跨App的方式呼叫主App中的元件
  • 主App也可以通過跨App的方式呼叫到單獨執行的元件App中的元件
  • 在同一個module中可以建立多個元件類,將來解耦時將對應的元件類移動到解耦後的module中即可

關於CC的技術實現細節可以檢視其github主頁的wiki系列文章 github.com/luckybilly/…

元件化實踐

CC的整合

1.在專案的根gradle.build中加入:

buildscript {

    dependencies {
        ... ...
    
        classpath 'com.billy.android:autoregister:1.4.1'
    }
}

複製程式碼

這個autoregister外掛是用來在編譯期間動態掃描並修改class檔案,實現元件的自動註冊,具體配置後面會提到

2.在Doctor和Patient這兩個主App的gradle.build中加入

ext.mainApp = true  //設定為true,表示此module為主app module,一直以application方式編譯
apply from: '../cc-setting.gradle'
複製程式碼

cc-setting.gradle中主要進行了以下幾點操作:

  • 讀取local.properties檔案中的配置來區分元件是否以獨立app的方式編譯;
  • 新增com.billy.android:cc:1.1.0依賴;
  • 自動註冊元件的配置

3.實現IComponent介面建立元件類

我們統一在Doctor和Patient中的exports包內建立App對外提供的元件,不同業務的元件放在建立在對於的類中管理

醫動力Android基於CC元件化框架的探索與實踐

IComponent的實現也很簡單,提供一個模組名稱name和對不同action的處理

醫動力Android基於CC元件化框架的探索與實踐

主App的配置就這麼多,接下來要新建一個元件Module

4.業務module的配置

醫動力Android基於CC元件化框架的探索與實踐

這裡需要設定module獨立執行時的applicationId,同時指定resourcePrefix資源字首(防止不同模組之間資原始檔的衝突)

因為設定了module的獨立執行,就需要準備一份module在獨立執行模式下的AndroidManifest檔案,路徑在src/main/debug下

醫動力Android基於CC元件化框架的探索與實踐

5.業務module提供IComponent介面

和主App一樣,模組要提供服務給其他模組呼叫,需要提供實現IComponent的子類,因為業務module提供的服務會比較少且單一,我們將它放在包名下的ExportComponent下

醫動力Android基於CC元件化框架的探索與實踐

7.主App設定需要依賴的module

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
    addComponent 'module_follow'
    addComponent 'module_diary'
    addComponent 'module_im'
}
複製程式碼

通過addComponent方式新增依賴,其內部會根據業務module的執行模式決定是否依賴

6.設定業務module的執行模式

業務module的執行模式包括開發模式和整合模式

  • 開發模式:會以App的方式執行
  • 整合模式:打包在主App中

開啟local.properties檔案

module_follow=true
module_diary=true
module_im=false
複製程式碼

設定模組名稱等於true或者false(沒有設定則為false)

  • 當等於true則該模組會以App執行,這時候打包主App的時候是不會把該模組打包進去
  • 當等於false時則不能獨立執行,打包主App的時候會一起打包進去

執行除錯

現在你可以將元件單獨執行起來了,但是由於業務元件是不包含登入功能的,因此它是沒有使用者登入狀態的,所以我們需要通過CC的元件呼叫去主App中獲取

醫動力Android基於CC元件化框架的探索與實踐

補充一點,如果想要跨App呼叫首先需要開啟CC的設定

CC.enableRemoteCC(true);
複製程式碼

元件呼叫

CCResult result = CC.obtainBuilder("common")
                .setActionName("httpInfo")
                .addParam("type", type)
                .build()
                .call();

        String baseUrl = result.getDataItem("baseUrl");
        String token = result.getDataItem("token");
複製程式碼

呼叫的方式很簡單,只需要指定模組名稱和對應的Action,同時可以傳遞引數,以上程式碼呼叫的是common模組的action為httpInfo的元件.

同時支援同步和非同步的呼叫,前一個例子是同步呼叫,非同步呼叫需要傳入一個回撥方法.

String callId = CC.obtainBuilder("Common")
            .setActionName("httpInfo")
            .addParam("type",type)
            .build()
            .callAsync(new IComponentCallback(){...});
複製程式碼

被呼叫元件在處理完請求後,需要作出響應,看以下程式碼:

public class HttpComponent implements IComponent {

        @Override
        public String getName() {
            return "http";
        }
    
        @Override
        public boolean onCall(CC cc) {
            String action = cc.getActionName();
            if(action.equals("action1")){
    
                CC.sendCCResult(cc.getCallId(),CCResult.success());
                return false;
            }else if(action.equals("action2")){
                String ccId = cc.getCallId();
    
                return true;
            }
    }
}

複製程式碼

注意onCall()方法的返回值,action1中返回的是false,而action2返回的是true,作用是:

  • false表示立即回撥結果,這裡需要呼叫 CC.sendCCResult(cc.getCallId(),CCResult.success() 告訴呼叫發返回成功
  • true則表示延遲迴調結果,這時你可以拿著ccCallId,等到事情處理完後才呼叫CC.sendCCResult(cc.getCallId(),CCResult.success()

這有什麼作用呢? 比如你在呼叫一個登入元件去到登入介面,只有登入成功了才返回結果,這就需要延遲迴調.

網路請求元件化

專案中用到的網路請求框架是okhttp+retrofit,我們希望不同module中使用不同的retrofit的Service例項,比如在Module_follow中我們會建立FollowService.java來處理當前模組的網路請求

在整合開發模式下我們可以通過元件呼叫去獲取主App中的Retrofit物件,我們只需要在主App中定一個name=http,action=getRetrofit的IComponent;

但在開發模式下,跨程式呼叫元件是傳輸不了Retrofit物件的,因為Android的跨程式只能傳輸Parcelable物件,這裡我們可以在本module中提供一個相同名稱的IComponent,在裡面去獲取主App的baseUrl和token,並建立新的Retrofit物件,這樣就可以透明的處理獲取Retrofit物件了.

為什麼以上方式可行呢?因為CC在進行元件化呼叫的時候,會檢查當前模組是否存在要呼叫的模組,如果存在則會呼叫本地的,不存在才會去跨程式呼叫.最後我們可以把這些模擬操作抽到一個lib_mock的module裡面複用.

元件化小結

元件化後帶來的一些變化:

  • 編譯時間明顯縮短
  • 開發人員之間可以通過模組分工
  • 可以在模組中嘗試新的技術而不擔心影響全域性(Kotlin)

CC已知侷限:

開發模式不能在Android8.0及以上環境執行,開發的時候可以使用虛擬機器或者低版本的手機,整合模式不影響.

引用

CC:基於匯流排的android元件化開發框架
Android徹底元件化方案實踐

相關文章