Android 组件化分析

引言

从个人经历来说,从事APP开发这么多年来,所接触的APP因业务变得也越来越复杂,版本不断的迭代,代码越来越臃肿,它的体积变得越来越大。这个时候我们就需要对项目进行重构即模块的拆分,官方的说法就是组件化,Google的NB之一就在这,你想不到的,他想到了,并有提前为大家准备了解决方案,所以,推进业务组件的初衷也是一个美好的愿景。

组件化

定义

关于组件的定义,在软件开发领域,组件(Component)是对数据和方法的简单封装,功能单一,高内聚,并且是业务能划分的最小粒度。那么同样,组件化就是将一个APP分成多个Module,如下图,每个Module都是一个组件(也可以是一个基础库供组件依赖),开发的过程中我们可以单独调试部分组件,组件间不需要互相依赖,但可以相互调用,最终发布的时候所有组件以lib的形式被主APP工程依赖并打包成一个apk。
Android组件化

似乎组件化是一个有机的整体,需要所有器官都健在才可以存在。而实际上组件化的目标之一就是降低整体(APP)与器官(组件)的依赖关系,缺少任何一个器官APP都是可以存在并正常运行的。头和胳膊可以单独存在,每个器官(组件)可以在补足一些基本功能之后都是可以独立存活的。

组件化和插件化的最大区别(应该也是唯一区别)就是组件化在运行时不具备动态添加和修改组件的功能,但是插件化是可以的。

组件化好处

简单来说就是提高工作效率,解放生产力,好处如下:

  • 代码简洁,冗余量少,维护方便,易扩展新功能
  • 提高编译速度,从而提高并行开发效率
  • 避免模块之间的交叉依赖,做到低耦合、高内聚
  • 引用的第三方库代码统一管理,避免版本统一,减少引入冗余库
  • 定制项目可按需加载,组件之间可以灵活组建,快速生成不同类型的定制产品
  • 制定相应的组件开发规范,可促成代码风格规范,写法统一
  • 系统级的控制力度细化到组件级的控制力度,复杂系统构建变成组件构建
  • 每个组件有自己独立的版本,可以独立编译、测试、打包和部署

组件化和模块化的区别

模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容,模块我们相对熟悉,比如登录功能可以是一个模块,搜索功能可以是一个模块等等。而组件化就是更关注可复用性,更注重关注点分离,如果从集合角度来看的话,可以说往往一个模块包含了一个或多个组件,或者说模块是一个容器,由组件组装而成。简单来说,组件化相比模块化粒度更小,两者的本质思想都是一致的,都是把大往小的方向拆分,都是为了复用和解耦,只不过模块化更加侧重于业务功能的划分,偏向于复用,组件化更加侧重于单一功能的内聚,偏向于解耦。

组件化指导思想

组件化的指导思想是:分而治之,并行开发,一切皆组件:

  • 组件拆分:将一个project划分成业务组件、基础组件、路由组件。其中业务组件是相互隔离的,可以单独调试,基础组件提供业务组件所公用的功能,路由组件为业务组件之间通信提供支持。
  • 组件隔离:业务组件之间的隔离,可以单独调试。
  • 核心法则:编译期隔离,运行期按需依赖。

如何实现组件化

要实现组件化,无论采用什么样的技术方式,需要考虑以下七个方面问题:

  • 代码解耦:如何将一个庞大的工程分成有机的整体
  • 组件单独运行:因为每个组件都是高度内聚的,是一个完整的整体,如何让其单独运行和调试
  • 组件间通信:由于每个组件具体实现细节都互相不了解,但每个组件都需要给其他调用方提供服务,那么主项目与组件、组件与组件之间如何通信就变成关键
  • UI跳转:UI跳转指的是特殊的数据传递,跟组件间通信区别有什么不同
  • 组件生命周期:这里的生命周期指的是组件在应用中存在的时间,组件是否可以做到按需、动态使用、因此就会涉及到组件加载、卸载等管理问题
  • 集成调试:在开发阶段如何做到按需编译组件?一次调试中可能有一两个组件参与集成,这样编译时间就会大大降低,提高开发效率
  • 代码隔离:组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用,也就是如何从根本上杜绝耦合的产生

代码解耦问题

对已存在的项目进行模块拆分,模块分为两种类型,一种是功能组件模块,封装一些公共的方法服务等,作为依赖库对外提供,一种是业务组件模块,专门处理业务逻辑等功能,这些业务组件模块最终负责组装APP。

组件单独运行问题

通过 Gradle 脚本配置方式,进行不同环境切换。比如只需要把 Apply plugin: 'com.android.library' 切换成Apply plugin: 'com.android.application' 就可以,同时还需要在 AndroidManifest 清单文件上进行设置,因为一个单独调试需要有一个入口的 Activity。比如设置一个变量 isAlone,标记当前是否需要单独调试,isAlone 的取值,使用不同的 gradle 插件和 AndroidManifest 清单文件,甚至可以添加 Application 等 Java 文件,以便可以做一下初始化的操作。

if (isAlone.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
//在根项目gradle.properties里面的  isAlone = true
ext {
    isAlone = false;//false:作为Lib组件存在,true:作为application存在
}

组件间通信问题

通过接口+实现的结构进行组件间的通信。每个组件声明自己提供的服务Service API,这些 Service都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向Router请求这个Service的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。在组件化架构设计图中 Common 组件就包含了路由服务组件,里面包括了每个组件的路由入口和跳转。

UI跳转问题

可以说 UI 跳转也是组件间通信的一种,但是属于比较特殊的数据传递。不过一般 UI 跳转基本都会单独处理,一般通过短链的方式来跳转到具体的 Activity。每个组件可以注册自己所能处理的短链的 Scheme 和 Host,并定义传输数据的格式,然后注册到统一的 UIRouter 中,UIRouter 通过 Scheme 和 Host 的匹配关系负责分发路由。但目前比较主流的做法是通过在每个 Activity 上添加注解,然后通过 APT 形成具体的逻辑代码。目前方式是引用阿里的 ARouter 框架,通过注解方式进行页面跳转。

组件生命周期问题

在架构中的核心管理组件会定义一个组件生命周期接口,通过在每个组件设置一个配置文件,这个配置文件是通过使用注解方式在编译时自动生成,配置文件中指明具体实现组件生命周期接口的实现类,来完成组件一些需要初始化操作并且做到自动注册,暂时没有提供手动注册的方式。

集成调试问题

每个组件单独调试通过并不意味着集成在一起没有问题,因此在开发后期我们需要把几个组件机集成到一个 APP 里面去验证。由于经过前面几个步骤保证了组件之间的隔离,所以可以任意选择几个组件参与集成,这种按需索取的加载机制可以保证在集成调试中有很大的灵活性,并且可以加大的加快编译速度。需要注意的一点是,每个组件开发完成之后,需要把 isModule 设置为 true并同步,这样主项目就可以通过参数配置统一进行编译。

代码隔离问题

如果还是 compile project(xxx:xxx.aar) 来引入组件,就完全可以直接使用到其中的实现类,那么主项目和组件之间的耦合就没有消除,那之前针对接口编程就变得毫无意义。希望只在 assembleDebug 或者 assembleRelease 的时候把 AAR 引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。目前做法是主项目只依赖 Common 的依赖库,业务组件通过路由服务依赖库按需进行查找,用反射方式进行组件加载,然后在主工程中调用组件服务,组件与组件之间调用则是通过接口+实现进行通信,后续规划通过自定义Gradle 插件,通过字节码自动插入组件的依赖进行编译打包,实现自动筛选 assembleDebug 或 assembleRelease 这两个编译命任务,只有属于包含这两个任务的命令才引入具体实现类,其他的则不引入。

组件化架构目标:告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为 AAR 包集成到“ APP 壳工程”中,组成一个完整功能的 APP。

组件化的拆分步骤和动态需求

拆分原则

组件化的拆分是个庞大的工程,特别是从几十万行代码的大工程拆分出去,所要考虑的事情千头万绪。为此我觉得可以分成三步:

  • 从产品需求到开发阶段再到运营阶段都有清晰边界的功能开始拆分,比如读书模块、直播模块等,这些开始分批先拆分出去
  • 在拆分中,造成组件依赖主项目的依赖的模块继续拆出去,比如账户体系等
  • 最终主项目就是一个Host,包含很小的功能模块(比如启动图)以及组件之间的拼接逻辑

动态需求

理想的代码组织形式是插件化的方式,具备了完备的运行时动态化。在向插件化迁徙的过程中,可以通过下面的集中方式来实现编译速度的提升和动态更新。在快速编译上,采用组件级别的增量编译。在抽离组件之前可以使用代码级别的增量编译工具如freeline(但databinding支持较差)、fastdex等。动态更新方面,暂时不支持新增组件等大的功能改进。可以临时采用方法级别的热修复或者功能级别的Tinker等工具,Tinker的接入成本较高。

总结

这里主要分析为何要实现业务组件化、组件化的方向、组件化的技术方案简单探讨、组件化带来的好处。因项目中涉及组件化的内容也有限,后续再补充。


参考资料:

  • 《Android组件化架构》
  • 《Android 组件化方案探索与思考》