以前開發針對功能較多的應用,一般是通過分包的形式將各個模塊進行解耦,然后將將通用的工具或者邏輯進行封裝供其他模塊使用,但是這樣依然很難進行有效的解耦,因為其他包里面的類依然可以通過new的方式進行創建,很難進行把控,尤其針對各個功能模塊可能需要單獨上線的應用更是無法滿足要求,不經意就會出現空指針異常。
來到現在的項目組之后接觸了一個組件話開發的框架CC,一個可以實現組件動態組冊,完成各個組件很好的隔離的框架,好處自然不用多說,此文章我們先大致介紹一下組件化的基本知識:
第一個問題:什么是組件化?
組件化這三個字顧名思義就是將一個項目拆成多個項目,也就是說一個項目由多個組件組成,就比如一輛汽車,你可以把它拆分成發動機、底盤、車身和電氣設備等四個模塊;又比如一架飛機你可以把它拆分成機身、動力裝置、機翼、尾翼、起落裝置、操縱系統和機載設備等7個模塊,那么你在開發項目的時候是否也應該把你的項目根據業務的不同拆分成不同的模塊,如果不同的模塊可以單獨運行的話,我們就可以叫它組件。
第二個問題:組件化開發有什么好處?
- 現在市場上的app幾乎都有個人中心這個東西,那么是不是可以把個人中心的所有業務都提到單獨的一個模塊中,當然是可以的,我們將它放在一個單獨的模塊中,這個時候你會發現一些好處:
- 耦合度降低了
- 你需要修改個人中心的時候直接從這個模塊中找修改的地方就好了
- 你需要測試的時候直接單獨運行這個模塊就好了,不需要運行整個項目(測試的效率是不是提高了很多呢)
- 由于測試的時候可以單獨編譯某個模塊編譯速度是不是提高了呢
- 如果是團隊開發的話,每個人單獨負責自己的模塊就好了(媽媽再也不用擔心我的代碼被人家修改了)。
第三個問題:組件化開發的步驟(以我的demo目錄為例,我的demo主要有一個主程序【app】和四個組件【component_base,libraryone,librarytwo,xpush】demo地址:
one.png
配置流程
1. 組件化開發肯定是有一個或某兩個組件是各個組件都會引用的,一般我們會把log工具類、網絡請求封裝框架、自定義的view接口類、以及下沉的接口等封裝成base組件供其他組件可以調用,我們可以在這個所有組件都會依賴的組件的build.gradle文件中依賴cc,方式如下:
dependencies {
...
//以下是依賴CC
*** api 'com.billy.android:cc:2.1.5'***
}
2. 在項目的根目錄下的build.gradle依賴自動注冊插件:
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.billy.android:cc-register:1.0.9'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
3. 在項目的根目錄下新建cc-settings-2.gradle文件,鍵入以下內容:
project.apply plugin: 'cc-register'
4. 在非主項目的組件中替換原來的apply plugin ‘XXX’為以下:
ext.alwaysLib = true
apply from: rootProject.file('cc-settings-2.gradle')
5. 在主項目中替換原來的apply plugin ‘xxx’為以下:
ext.mainApp = true
project.apply plugin: 'cc-register'
6. 最后一步,也是最關鍵的一步,將各組件添加到主項目中(在app的build.gradle中添加),否則調用會失敗:
dependencies {
....
api project(':component_base')
addComponent 'libraryone'
addComponent 'component_base'
addComponent 'librarytwo'
addComponent 'xpush'
}
開發流程
以上是依賴CC進行組件開發的配置流程,下面根據自己的項目說一下開發流程,我們以base組件和libraryone組件為例:
1. 由于在B組件可能存在調用A組件的一些實例,但是各個組件又都是項目獨立的,所以需要將對外開放使用的對象抽象到base組件,我的demo如下:
two.png
其中的三個接口分別是在另外三個組件里面實現的,同時也把各個組件使用的常量也在base組件里面定義了ComponentConst類,方便外部組件調用:
public class ComponentConst {
public interface Component_A{
String NAME = "Component_A";
public interface Action{
String SHOW = "Component_A_show";
String HIDE = "Component_A_hide";
}
}
public interface Component_B{
String NAME = "Component_B";
public interface Action{
String SHOW = "Component_B_show";
String HIDE = "Component_B_hide";
}
}
public interface Component_C{
String NAME = "Component_C";
public interface Action{
String ShOW = "Component_C_show";
String HIDE = "Component_CChide";
String CONTENT = "setContent";
}
}
}
2. 各個組件需要有個類實現IComponent接口,以libraryone為例:
public class Component_A implements IComponent {
@Override
public String getName() {
//此出的名字是外部調用該組件時傳入的名稱,用于區分不同的組件
return ComponentConst.Component_A.NAME;
}
@Override
public boolean onCall(CC cc) {
//此處的action是外部調用該組件內部的方法的標記,用于區分該組件內不同的功能或者方法,由于libraryone依賴了base組件,所以可以直接引用base組件里的常量
String action = cc.getActionName();
switch (action) {
case ComponentConst.Component_A.Action.SHOW:
ComponentAManager componentAManager = ComponentAManager.getInstance();
CC.sendCCResult(cc.getCallId(),CCResult.successWithNoKey(componentAManager));
break;
case ComponentConst.Component_A.Action.HIDE:
break;
}
return true;
}
}
3. 在libraryone里面實現base中定義的IComponentAManager接口:
public class ComponentAManager implements IComponentAManager {
private static final String TAG = "ComponentAManager";
private static ComponentAManager componentAManager;
private ComponentAManager(){}
public static ComponentAManager getInstance(){
if (componentAManager == null){
synchronized (ComponentAManager.class){
if (componentAManager == null){
componentAManager = new ComponentAManager();
}
}
}
return componentAManager;
}
private UserBean getUserBeanFromComponentA(){
Log.d(TAG, "getUserBeanFromComponentA: ");
UserBean userBean = new UserBean("ComponentA",18,180.7f);
return userBean;
}
@Override
public UserBean show() {
Log.d(TAG, "show: ");
return getUserBeanFromComponentA();
}
@Override
public void set(UserBean userBean) {
}
}
4. 此時我們如果想在其他組件或者任何地方(非libraryone組件內)獲取到ComponentAManager實例,發現new是不行的,getInstance也是不行的,也就是使用CC的一個好處,可以很好的隔離個組件的功能界限,那么我們怎么獲取呢,用如下方法(此處我是在app主程序中獲取的):
CCResult ccResult = CC.obtainBuilder(ComponentConst.Component_A.NAME)
.setActionName(ComponentConst.Component_A.Action.SHOW)
.build()
.call();
//是否獲取成功
if (ccResult.isSuccess()){
IComponentAManager componentAManager = ccResult.getDataItemWithNoKey();
UserBean userBean = componentAManager.show();
if (userBean != null){
tv.setText("");
tv.setText("name:"+userBean.name+"\n"
+"age:"+userBean.age+"\n"
+"height:"+userBean.getHeight());
}
}