以前開發(fā)針對(duì)功能較多的應(yīng)用,一般是通過分包的形式將各個(gè)模塊進(jìn)行解耦,然后將將通用的工具或者邏輯進(jìn)行封裝供其他模塊使用,但是這樣依然很難進(jìn)行有效的解耦,因?yàn)槠渌锩娴念愐廊豢梢酝ㄟ^new的方式進(jìn)行創(chuàng)建,很難進(jìn)行把控,尤其針對(duì)各個(gè)功能模塊可能需要單獨(dú)上線的應(yīng)用更是無法滿足要求,不經(jīng)意就會(huì)出現(xiàn)空指針異常。
來到現(xiàn)在的項(xiàng)目組之后接觸了一個(gè)組件話開發(fā)的框架CC,一個(gè)可以實(shí)現(xiàn)組件動(dòng)態(tài)組冊(cè),完成各個(gè)組件很好的隔離的框架,好處自然不用多說,此文章我們先大致介紹一下組件化的基本知識(shí):
第一個(gè)問題:什么是組件化?
組件化這三個(gè)字顧名思義就是將一個(gè)項(xiàng)目拆成多個(gè)項(xiàng)目,也就是說一個(gè)項(xiàng)目由多個(gè)組件組成,就比如一輛汽車,你可以把它拆分成發(fā)動(dòng)機(jī)、底盤、車身和電氣設(shè)備等四個(gè)模塊;又比如一架飛機(jī)你可以把它拆分成機(jī)身、動(dòng)力裝置、機(jī)翼、尾翼、起落裝置、操縱系統(tǒng)和機(jī)載設(shè)備等7個(gè)模塊,那么你在開發(fā)項(xiàng)目的時(shí)候是否也應(yīng)該把你的項(xiàng)目根據(jù)業(yè)務(wù)的不同拆分成不同的模塊,如果不同的模塊可以單獨(dú)運(yùn)行的話,我們就可以叫它組件。
第二個(gè)問題:組件化開發(fā)有什么好處?
- 現(xiàn)在市場(chǎng)上的app幾乎都有個(gè)人中心這個(gè)東西,那么是不是可以把個(gè)人中心的所有業(yè)務(wù)都提到單獨(dú)的一個(gè)模塊中,當(dāng)然是可以的,我們將它放在一個(gè)單獨(dú)的模塊中,這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)一些好處:
- 耦合度降低了
- 你需要修改個(gè)人中心的時(shí)候直接從這個(gè)模塊中找修改的地方就好了
- 你需要測(cè)試的時(shí)候直接單獨(dú)運(yùn)行這個(gè)模塊就好了,不需要運(yùn)行整個(gè)項(xiàng)目(測(cè)試的效率是不是提高了很多呢)
- 由于測(cè)試的時(shí)候可以單獨(dú)編譯某個(gè)模塊編譯速度是不是提高了呢
- 如果是團(tuán)隊(duì)開發(fā)的話,每個(gè)人單獨(dú)負(fù)責(zé)自己的模塊就好了(媽媽再也不用擔(dān)心我的代碼被人家修改了)。
第三個(gè)問題:組件化開發(fā)的步驟(以我的demo目錄為例,我的demo主要有一個(gè)主程序【app】和四個(gè)組件【component_base,libraryone,librarytwo,xpush】demo地址:

one.png
配置流程
1. 組件化開發(fā)肯定是有一個(gè)或某兩個(gè)組件是各個(gè)組件都會(huì)引用的,一般我們會(huì)把log工具類、網(wǎng)絡(luò)請(qǐng)求封裝框架、自定義的view接口類、以及下沉的接口等封裝成base組件供其他組件可以調(diào)用,我們可以在這個(gè)所有組件都會(huì)依賴的組件的build.gradle文件中依賴cc,方式如下:
dependencies {
...
//以下是依賴CC
*** api 'com.billy.android:cc:2.1.5'***
}
2. 在項(xiàng)目的根目錄下的build.gradle依賴自動(dòng)注冊(cè)插件:
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. 在項(xiàng)目的根目錄下新建cc-settings-2.gradle文件,鍵入以下內(nèi)容:
project.apply plugin: 'cc-register'
4. 在非主項(xiàng)目的組件中替換原來的apply plugin ‘XXX’為以下:
ext.alwaysLib = true
apply from: rootProject.file('cc-settings-2.gradle')
5. 在主項(xiàng)目中替換原來的apply plugin ‘xxx’為以下:
ext.mainApp = true
project.apply plugin: 'cc-register'
6. 最后一步,也是最關(guān)鍵的一步,將各組件添加到主項(xiàng)目中(在app的build.gradle中添加),否則調(diào)用會(huì)失敗:
dependencies {
....
api project(':component_base')
addComponent 'libraryone'
addComponent 'component_base'
addComponent 'librarytwo'
addComponent 'xpush'
}
開發(fā)流程
以上是依賴CC進(jìn)行組件開發(fā)的配置流程,下面根據(jù)自己的項(xiàng)目說一下開發(fā)流程,我們以base組件和libraryone組件為例:
1. 由于在B組件可能存在調(diào)用A組件的一些實(shí)例,但是各個(gè)組件又都是項(xiàng)目獨(dú)立的,所以需要將對(duì)外開放使用的對(duì)象抽象到base組件,我的demo如下:

two.png
其中的三個(gè)接口分別是在另外三個(gè)組件里面實(shí)現(xiàn)的,同時(shí)也把各個(gè)組件使用的常量也在base組件里面定義了ComponentConst類,方便外部組件調(diào)用:
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. 各個(gè)組件需要有個(gè)類實(shí)現(xiàn)IComponent接口,以libraryone為例:
public class Component_A implements IComponent {
@Override
public String getName() {
//此出的名字是外部調(diào)用該組件時(shí)傳入的名稱,用于區(qū)分不同的組件
return ComponentConst.Component_A.NAME;
}
@Override
public boolean onCall(CC cc) {
//此處的action是外部調(diào)用該組件內(nèi)部的方法的標(biāo)記,用于區(qū)分該組件內(nèi)不同的功能或者方法,由于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里面實(shí)現(xiàn)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. 此時(shí)我們?nèi)绻朐谄渌M件或者任何地方(非libraryone組件內(nèi))獲取到ComponentAManager實(shí)例,發(fā)現(xiàn)new是不行的,getInstance也是不行的,也就是使用CC的一個(gè)好處,可以很好的隔離個(gè)組件的功能界限,那么我們?cè)趺传@取呢,用如下方法(此處我是在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());
}
}