近期公司有组件化的打算,因此对市面上的方案进行了调研,目前已经整理出一套作为项目组件化的方案,这里分享一波,当然组件化是没法一步到位的,中间肯定少不了踩坑优化,所以本篇也会持续更新。
那么我们先说说组件化是干嘛的吧,组件化就是将单模块的项目拆成多个,并且每个模块可以单独运行。
WTF!!!这么简单?
对概念就是这么简单,但当我们去做的时候就会发现几个问题
- 模块如何单独运行
- 拆成独立模块后初始化问题(组合运行和独立运行的时候怎么初始化)
- 跨模块方法调用(如何启动Activity、跨模块获取数据)
- 模块独立运行时跨模块方法调用
那么要想组件化就需要解决上面这几个问题,所以接下来就是围绕这几个问题展开讨论,不过在这之前我们先看看整体架构有个大概的认识。
组件化架构图
从组件的划分上分为四层,从上往下依次为
- App壳工程:负责管理各个业务组件和打包APK,没有具体的业务功能。
- 业务组件层:根据不同的业务构成独立的业务组件。
- 功能组件层:对上层提供基础功能服务不包含业务,如地图、拍照、日志等。
- 组件基础设施:Base类、第三方Sdk、View等一些通用代码。
这里单独说下业务组件和功能组件,一个典型的业务组件工程结构是这个样子:
以上图为例,它包含三个模块(两个Library和一个Application):
- jd :组件代码,它包含了这个组件所有业务代码并实现了jd-api的接口。
- jd_api:组件的接口模块,专门用于与其他组件通信,只包含 Model、Interface 和 Event,不存在任何业务和逻辑代码。
- jd_app 模块:用于独立运行 app,它直接依赖组件模块,只要添加一些简单的配置,即可实现组件独立运行。
你可能会问为什么要有个jd_api模块,其实和接口隔离是一个意思,jd_api模块存放着jd模块需要对外暴露的接口,jd模块去实现这些接口,当别的模块想要调用jd模块方法的时候拿到的是jd_api模块的接口对象,从而隔离jd模块,只不过这些接口是装在一个独立的library中,之所以这样也是因为业务模块粒度太大,包含的代码量较多,如果将接口放在业务模块内,既不利于隔离不同实现,还会因为获取接口实现类增加很多冗余的判断代码,所以将接口单独作为一个library模块,具体实现类的话根据具体业务场景依赖对应的业务模块。
以jd模块为例,他需要依赖jd_api并实现它的接口
dependencies {
...
implementation project(':component-jd:jd_api')
...
}
而独立运行的jd_app模块则需要依赖接口模块jd_api和业务具体实现模块jd
dependencies {
...
runtimeOnly project(':component-jd:jd')//runtimeOnly可以防止我们在写代码的时候直接引用到jd模块的类
implementation project(':component-jd:jd_api')
...
}
如果哪天对于jd的业务有新的实现,我们只需要修改runtimeOnly project(':component-jd:jd')
依赖即可,至于怎么拿到接口实现类是通过Arouter这个框架去获取的,后面会说。
对于功能模块来说,同样也需要用接口隔离,但与业务模块不同的是功能模块本身相对独立没有业务逻辑,所以不需要单独为接口创建一个library,直接把对外暴露的接口定义在功能模块内即可,外部只需通过工厂拿到具体实现类进行操作。
以支付功能模块为例:
在支付模块内有一个接口IPay进行隔离,RandomPay为接口具体实现类,业务模块要想调用支付模块的方法只需通过PayFactory拿到IPay实现类操作即可。
模块如何单独运行
模块要想单独运行只需要新建一个Application壳工程用来作为独立运行的入口,模块本身永远是library,然后壳工程依赖模块即可,那么一个模块的目录将变成如下这样:
projectRoot
+--app
+--component_module1(文件夹)
| +--module1(业务模块library)
| +--module1_api(业务组件的接口模块,专门用于与其他组件通信library)
| +--module1_app (独立运行的壳工程Application)
app模块是全量编译的application模块入口,module1是业务library模块,module1_api是业务组件的接口library模块,module1_app是用来独立启动 module1的application模块。
对于独立运行的module1_app模块只需依赖业务接口模块和业务模块
dependencies {
...
runtimeOnly project(':module1')
implementation project(':module1_api')
...
}
对于全量编译的app模块则根据所需业务依赖对应的业务接口模块和业务模块
dependencies {
...
runtimeOnly project(':module1')
implementation project(':module1_api')
runtimeOnly project(':module2')
implementation project(':module2_api')
...
}
由于有专门用于单独启动的module1_app模块的存在,业务的 library模块只需要按自己是library模块这一种情况开发即可,而为了让业务模块单独启动所需要的配置、初始化工作都可以放到module1_app模块里,并且不用担心这些代码被打包到最终Release的App中。
拆成独立模块后初始化问题
初始化的逻辑我们可以细分为两类
- 通用的初始化逻辑
- 每个模块个性化的初始化逻辑
对于通用的初始化逻辑可以写在Base模块的Application中
public class BaseApplication extends Application {
private static Application sApplication;
@Override
public void onCreate() {
super.onCreate();
sApplication = this;
initARouter(this);
}
public void initARouter(Application application) {
if (BuildConfig