本篇文章的主要目的:
- 帮助正在对项目进行组件化改造或者想建立组件化项目架构的小伙伴,更好的认识组件化本质。
- 目前组件化的框架众多,说的天花乱坠的,其本质来说其实都差不多,阅读本文以后,读者甚至可以摒弃这些开源框架,根据自己的项目特点,轻松构建自己的组件化框架。
- 帮助想学习和了解组件化框架,并尝试动手写自己的开源框架的小伙伴们
什么是组件化?
在平时的开发过程中,随着项目需求的增加,app支持功能越来越多,如果没有组件化的思想,代码会越来越臃肿。导致的问题也会越来越明显,比如一个很小的需求变化,可能会导致代码的“牵一发动全身”问题,甚至会出现很多隐藏的bug。还会导致维护成本越来越高,团队协作低效,问题边界不清晰。
于是,很多团队开始有了代码解耦的想法,但是面对如此复杂的项目,又不敢轻易变更其中的代码结构,如何顺利的解耦就成了很多团队难以入手的问题。甚至由于业务的不断迭代,导致代码解耦的问题遥遥无期。
当然,作为一位合格的App架构师,遇到再难的问题也会迎难而上。于是便开始对APP整体项目结构进行分析,制定解耦方案。通常情况下,解耦的最佳思路就是根据业务边界对代码结构进行划分。比如说某APP里面包含了IM、直播、内容展示等等业务场景,于是从架构的角度来说,整个APP的架构应该是如下图所示:
这种架构思路上很清晰,对应到我们Android代码结构,就是根据这些业务边界,拆分成不同的module,module之间没有直接的引用和依赖,代码完全解耦。作为团队开发成员也有很清晰的业务边界,代码维护成本大大降低,开发效率也会明显提高,应该是一个很不错的方案。
所谓的组件化其实就是根据业务边界对代码进行解耦,不同的业务对应不同的module,这些module相互独立,可以独自作为一个app进行开发并独立运行,合并时可以打包到一个整体的app中,实现完整的app功能。
如何优雅的进行组件化?
那么问题来了,以上的架构的确是非常不错的选择,但是实际的业务中,很难有个清晰的边界,并且业务与业务直接总会有衔接的地方。如果使用以上的架构,那么这些不同的module之间又该如何进行调用呢?
在我们Android系统中,进程是一个独立程序,每个进程都具有自己的虚拟机 (VM),应用代码是在与其他应用隔离的环境中运行,进程直接的通信主要是基于Binder机制。为什么要提Binder,首先Binder是Android系统的中非常重要的实现机制,而我们组件化代码耦合的问题也可以借鉴其实现原理。接下来我简单的介绍一下Binder机制,先总体看一下Binder架构图:
可以看出Binder是一个典型的CS架构,进程间的通信基于ServiceManager这个大管家,Client进程从ServiceManager中获取Server进程的一个远程代理,进行通信。为了让大家更直接的理解,我从代码层面上来简单描述一下这个过程。比如我们启动一个Activity时,需要ActivityManagerService(AMS)这个服务来进行管理,而AMS运行在SystemServer中,那么如何获取这AMS呢,我们从源码来分析(以下源码android-28中):
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
//通过ServiceManager.getService获取到AMS的代理IActivityManager
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
app中通过ServiceManager.getService获取到远程进程中服务的代理,只需要指定服务的name就可以。
Binder机制就不展开讲解,如果想了解更多的同学,可以加入到ARetrofit的QQ群中@我进行交流。接下来我们再回到“如何优雅的进行组件化”这个问题上。
在了解Binder机制后,对于我们组件化过程中解耦代码如何通信,其实也可以采用类似的机制。当然我们的module之间并没有跨进程,不会有跨进程通信的问题。
我们可以将图1中的每一个module想象成一个服务,那么module之间的通信其实就类似于服务之间的通信。而服务之间的通信其实也不需要直接进行类的引用,只需要拿到另一个module的服务代理,通过这个代理进行通信。这里又出了一个新的问题,如何实现提供代理服务的代理管家呢,其实这也类似于Binder里面的ServiceManager,用以下这张架构图来说明:
上图中IM module注册自己的IM服务到ServiceManager中,直播 module想要和IM module只需要获取IM的服务代理就可以进行操作。注册的过程,对应到实际的代码中,其实就是中ServiceManager这个管家的Module中声明自己的服务接口,并在自己的module中实现这一接口。其他module只需要在运行时拿到接口实现类的实例对象就可以完成这一过程了,这一部分其实可以参考ARetrofit README中 四 高阶用法
中登录服务接口的声明与注册过程就可以了解。
当然,这篇文章不仅仅让大家了解别人已经开源好的框架,其实类似的框架很多,如ARetrofit 、ARouter、CC等开源,无关star量,这都是开源早晚和推广的问题,其实本质上都是基于以上的原理,区别就是上层的封装的问题,最终呈现的API是否简洁,是否符合自己的要求,能否直观的进行代码维护。
这里我将带着大家动手一起实现自己的ServiceManager管家。
第一步,我们在ServiceManager中注册不同module的服务接口,如下:
public interface ILoginManager {
void login();
User getUser();
}
第二步,在Login Module中实现该接口,如下:
@Inject //需要自动注入服务的声明
public class LoginManagerService implement ILoginManager {
@Override
void login(Activity activity) {
Intent intent = new Intent(activity, LoginActivity.class);
activity.startActivity(intent);
}
@Override
User getUser(CallBack callback) {
//...网络或者 或者 本地数据库 等回去异步返回或者同步返回结果
}
}
第三步, 在直播 Module中跨Module获取ILoginManager服务实例对象,这里需要通过Android Studio自动注入框架AInject,完成跨Module自动注入流程,可参考AInject用法,具体实现如下:
public class ServiceManager implements InjectContract {
private static class ServiceManager {
private static final ServiceManager instance = new ServiceManager();
}
static ServiceManager getInstance() {
return InstanceHolder.instance;
}
/**
* @Fixme 这里建议使用实现LRU算法的列表存储
*/
List<Object> services = new ArrayList();
/**
*
* "@Inject" 注解标示的class 最终都会注入到该"@IMethod"注解标示过的方法中
* 注:"@IMethod"注解标示过的方法将由编译器自动注入实现代码,注入最终的代码如下如:
*
* @IMethod
* public void iMethodName() {
* injectClass("injectClassName1")
* injectClass("injectClassName2")
* injectClass("injectClassName3")
* injectClass("injectClassName4")
* }
*
* 用户可以在该方法中通过反射完成自己的业务需求
* @param className class name
*/
@IMethod
public void startInject() {
}
@Override
public void injectClass(String serviceClassName) {
services.clear()
services.add(className);
}
/**
* 获取登录服务,可在任意Module直接获取该服务的实例化对象
*/
public static ILoginManager getILoginManager() {
if (getInstance().service.size() == 0) {
getInstance().startInject();
}
for (String className: getInstance().services) {
try {
Class<?> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();
if (obj instanceof ILoginManager) {
return obj;
} else {
obj = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
其实就是这么简单,一个组件化的框架就完成了。
看到这里的小伙伴们,大概已经理解了如何进行解耦Module直接的通信工作了吧。想想平时有没有遇到其他关于高耦合的代码需要解耦的,都可以参考这种思路哦。
组件化并不需要一蹴而就的
前面教大家如何进行组件化,已经如何实现组件化,其实还忽略了一个非常重要的问题,就是如何对现有的项目进行组件化。现有的项目一般都已经累计了很多代码量,如果一次性根据业务进行拆解处理,解耦合显然是不合实际的。那么到底该怎么做呢?
其实有了以上自定义的组件化框架(当然推荐建议使用作者新开源的ARetrofit框架,API使用非常简洁),其实组件化并不是一个版本就需要完成的。组件化的第一步当然还是根据业务边界来架构自己的APP框架,不同的Module中声明好自己的服务。而组件化的工作可以拆分到不同的迭代版本中,对于新增的业务明确到对应的业务Module中开发,对应老的业务代码如果耦合度比较高的,不建议直接修改逻辑,可以先将这部分代码耦合的地方抽象到服务接口中,通过服务调用来实现调用过程。并在未来的版本中逐步进行剥离解耦最终实现真正的代码隔离,以服务的形式完成业务间的交集部分。
组件化最佳实践
小结
前面讲了很多,我们再回到主题,相信大家对于“如何实现自己的Android组件化改造”应该有了很清晰的步骤和流程来吧。此外仅代表个人的一些拙见,如果意见或者建议欢迎进ARetrofit QQ群赐教。