本篇文章的主要目的:
帮助正在对项目进行组件化改造或者想建立组件化项目架构的小伙伴, 更好的认识组件化本质.
目前组件化的框架众多, 说的天花乱坠的, 其本质来说其实都差不多, 阅读本文以后, 读者甚至可以摒弃这些开源框架, 根据自己的项目特点, 轻松构建自己的组件化框架.
帮助想学习和了解组件化框架, 并尝试动手写自己的开源框架的小伙伴们
什么是组件化?
在平时的开发过程中, 随着项目需求的增加, 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 机制就不展开讲解, 如果想了解更多的同学, 可以看我之前的文章. 接下来我们再回到 "如何优雅的进行组件化" 这个问题上.
在了解 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 中开发, 对应老的业务代码如果耦合度比较高的, 不建议直接修改逻辑, 可以先将这部分代码耦合的地方抽象到服务接口中, 通过服务调用来实现调用过程. 并在未来的版本中逐步进行剥离解耦最终实现真正的代码隔离, 以服务的形式完成业务间的交集部分.
组件化最佳实践
ARetrofit 原理
小结
前面讲了很多, 我们再回到主题, 相信大家对于 "如何实现自己的 Android 组件化改造" 应该有了很清晰的步骤和流程来吧.
自己是从事了七年开发的 Android 工程师, 不少人私下问我, 2019 年 Android 进阶该怎么学, 方法有没有?
没错, 年初我花了一个多月的时间整理出来的学习资料, 希望能帮助那些想进阶提升 Android 开发, 却又不知道怎么进阶学习的朋友.[包括高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发 (ReactNative+Weex),Flutter 等架构技术资料] , 希望能帮助到您面试前的复习且找到一个好的工作, 也节省大家在网上搜索资料的时间来学习.
来源: http://www.jianshu.com/p/4dcd1064f131