秒拍的组件化演进

组件化的反思:

最早的组件化概念应该是从OSGI模型演变过来的。当时在JavaWeb领域,开发者为了将服务细分,出现了面向接口编程的思路。而由于工程的庞大再加上单纯的规范已经不能严格要求开发人员按照约定的规范进行 “接口” 编程了。组件化由此应运而生。

组件化的出现就是为了解决几个问题:

  • 功能拆分
  • 解耦
  • 组建单独编译,不影响整个工程。

Android 平台的早期组件化思路

  • 底层Framework为基础业务,所有公共的功能在此体现。
  • 上层组建单独依赖于FrameWork

项目结构:

1
2
3
4
5
6
7
8
9
10
11
                 +------------------------------+
| |
+--------+ FrameWork +---+
| | | |
| +-----------+------------------+ |
| | |
+--------v-------+ +-------v--------+ +--------v--------+
| | | | | |
| User Comp | | Chat Comp | | Other Comp |
| | | | | |
+----------------+ +----------------+ +-----------------+

此时,已经基本实现了简单的模块化操作,可是这样的工程结构有如下问题:

  • 项目间功能调用无法实现:如 Chat模块跟 User模块间的相互调用,IM登陆用到的用户字段,用户字段中要包含IM中的状态等等。
  • 资源文件重复使用问题
  • 一系列其他问题

当然以上问题也是有解决方案的,可以通过不断的将 所谓的 公有功能迁移到Framework中解决,不过,将会出现如下问题。

  • FrameWork 越来越臃肿的问题。

这并不是我们希望看到的。

早在2017年我就提出过组建即插件的理论。我们也在向着这个目标发展,不过如果继续使用上述的组建模型,将是灾难性的后果。

有幸后来得到了得到了一次新项目开发的机会。我们就此在新项目上继续推进我们的组建框架。

新的组件化推进:

关于组件化的思路,我们开始分析上述模型中的失误部分,最大的问题还是耦合性没有解开,如何“强制” 的让组建之间领依赖成为一个严峻的问题。

同时为了单独编译组建的时候不将无用代码编译进去。我们进行了新的组件化拆分。

新的组件化框架思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
+-------------------------------------------------------------------+
| |
| +------------+ +------------+ +----------+ +-----------+ |
| | Common | | Birdge | | UiLibs | | Other | |
| | | | | | | | | |
| +------------+ +------------+ +----------+ +-----------+ |
| |
| BaseModule |
+---------------+----------------------------+----------------------+
| |
| |
| |
+------------v----------------------------v-------------------+
| |
| +----------+ +----------+ +----------+ +--------+ |
| | User | | Home | | Feed | | Search | |
| +----------+ +----------+ +----------+ +--------+ |
| |
| Function Module |
+-------------------------------------------------------------+

+----------------------------+
| |
| App Wrapper |
| |
+----------------------------+

Base Module 编译类型为Android的library。

  • Common提供工程的基础支撑,原有的 common中大部分重叠的功能都放在了这里。

  • Bridge 中为工程的 【接口规范】和所有的Bean。

  • UiLibs 为工程中要到的基础UI控件和资源。

  • Other 中包含其他基础组件支持,如 push ,plugin ,player等

Function Module 编译类型随开关改变

工程的业务组件,包含一个可调试的 TestActivity 和一个 可调试的 DebugApplication。会在编译为工程的时候自动剔除掉。

其他为一些单独的业务功能。

和其他常见的组件化框架相比也没有特别之处。都是通过sourceSets 来控制的。

1
2
3
4
5
6
7
8
9
10
11
12
13
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'debug/**'
}
}
jniLibs.srcDirs = ['libs']
}
}

其中 isModule 是工程中定义的组件化开关,定义在工程根目录的 gradle.properties文件中。

两个魔法般的注解

@AutoInit 和 @BindLife

@AutoInit 是一个 远程 依赖引入的注解。它可将别的Module下的具体实现 引入到当前这个不可见的范围内。
比如 User 工程中 有一个 UserManager控制用户的登录退出操作 ,而Chat 工程中,要检测登陆,这两个工程是 不能相互依赖的
我们的方案:

Bridge 中 定义 IUserManager
1
2
3
4
5
6
7
8
9
10
public interface IUserManager {

boolean isLogin();

UserBean getCurrentUser();

void logout();

void login(UserBean userBean);
}
Chat工程中引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ChatManager {

//注意这里,在模块开发阶段,这里可定义成 debug包下的空实现类
@AutoInit(implclass= "com.yixia.chat.debug.DebugUserManager")
IUserManager mUserManager;

public boolean conn(ChatUserBean _chatuser,boolean shouldReConn){
//...

if(!mUserManager.isLogin){
return false;
}
//...
}

}
同时,Chat 工程中可在debug包中定义一个空的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DebugUserManager implements IUserManager{
private UserBean mUser = null;
public boolean isLogin(){
return mUser != null;
}

public UserBean getCurrentUser(){
return mUser;
}

public void logout(){
mUser = null;
}

public void login(UserBean userBean){
mUser = userBean;
}
}

等到联合编译期间,可将带有调试功能的 @AutoInit 注解替换掉。

以达到解耦,同步开发的目的。

@BindLife 注解

@BindLife 注解 顾名思义,是一个生命周期绑定注解,Example:

在AppWrapper中,我们定义的application如下。

1
2
3
4
5
6
7
8
9
10
11
public class AppContext extends BaseApp {

@AutoInit(implClass = "com.yixia.videoeditor.home.HomeApp")
@BindLife(isBefore = false)
public IHomeApp mHomeApp;

public void onCreate() {
super.onCreate();
}

}

在Bridge中定义的IHomeApp如下。

1
2
3
public interface IHomeApp {
void onCreate(IBaseAppContext app);
}

具体的实现类在 Home Module中。

1
2
3
4
5
6
7
8

public class HomeApp implements IHomeApp {
public static final String TAG = "HomeApp";

@Override public void onCreate(IBaseAppContext app) {
//onCreateHome home
}
}

经过我们编译期注解处理之后,AppContext 变成了如下

1
2
3
4
5
6
7
8
9
10
11
public class AppContext extends BaseApp {

@AutoInit(implClass = "com.yixia.videoeditor.home.HomeApp")
@BindLife(isBefore = false)
public IHomeApp mHomeApp = new com.yixia.videoeditor.home.HomeApp();

public void onCreate() {
super.onCreate();
mHomeApp.onCreate(this);
}
}

BindLife注解会将同名,同参数(第一个参数为this)的方法绑定。