在平时的开发过程中,随着项目需求的增加,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群赐教。