以前开发针对功能较多的应用,一般是通过分包的形式将各个模块进行解耦,然后将将通用的工具或者逻辑进行封装供其他模块使用,但是这样依然很难进行有效的解耦,因为其他包里面的类依然可以通过new的方式进行创建,很难进行把控,尤其针对各个功能模块可能需要单独上线的应用更是无法满足要求,不经意就会出现空指针异常。
来到现在的项目组之后接触了一个组件话开发的框架CC,一个可以实现组件动态组册,完成各个组件很好的隔离的框架,好处自然不用多说,此文章我们先大致介绍一下组件化的基本知识:
第一个问题:什么是组件化?
组件化这三个字顾名思义就是将一个项目拆成多个项目,也就是说一个项目由多个组件组成,就比如一辆汽车,你可以把它拆分成发动机、底盘、车身和电气设备等四个模块;又比如一架飞机你可以把它拆分成机身、动力装置、机翼、尾翼、起落装置、操纵系统和机载设备等7个模块,那么你在开发项目的时候是否也应该把你的项目根据业务的不同拆分成不同的模块,如果不同的模块可以单独运行的话,我们就可以叫它组件。
第二个问题:组件化开发有什么好处?
- 现在市场上的app几乎都有个人中心这个东西,那么是不是可以把个人中心的所有业务都提到单独的一个模块中,当然是可以的,我们将它放在一个单独的模块中,这个时候你会发现一些好处:
- 耦合度降低了
- 你需要修改个人中心的时候直接从这个模块中找修改的地方就好了
- 你需要测试的时候直接单独运行这个模块就好了,不需要运行整个项目(测试的效率是不是提高了很多呢)
- 由于测试的时候可以单独编译某个模块编译速度是不是提高了呢
- 如果是团队开发的话,每个人单独负责自己的模块就好了(妈妈再也不用担心我的代码被人家修改了)。
第三个问题:组件化开发的步骤(以我的demo目录为例,我的demo主要有一个主程序【app】和四个组件【component_base,libraryone,librarytwo,xpush】demo地址:
配置流程
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如下:
其中的三个接口分别是在另外三个组件里面实现的,同时也把各个组件使用的常量也在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());
}
}