前言
- 什么是模块化
项目由多个模块构成,模块间解耦、可重用,模块可通过aar依赖。每个模块都可以独立运行。 - 为什么要模块化
当项目越来越复杂,体积也变得臃肿,为了降低复杂性和耦合度、提高模块重用性、单模块运行提高编译打包效率等等,模块化是很好的解决方案。
改造目标
-
项目拆分为多个子工程,大概有三类:基础组件、业务模块、聚合业务模块的app模块.结构图如下:
其中app模块是application工程,其他模块都是library工程,library工程可以打成aar上传maven,使用maven依赖。
- 业务模块之间通过路由、ioc服务实现通信,减少相互依赖,降低耦合性。
- 业务模块可以独立运行,开发期间只需运行自己的模块,减少编译打包时间。
遇到的问题
拆分不彻底或重复资源
我加入项目的时候,很多业务模块已经从app模块拆分出来,但主要是代码移动,其对应的so,资源文件,manifest声明却在app模块或base模块,而且app启动时的各个组件初始化也分布在很多地方。在尝试独立运行业务模块时常常会出现缺少组件或找不到类编译时或运行时报错。另外,由于子工程是由原单一工程拆分而出的,有些资源文件或类产生了重复,增大了apk体积。-
模块间依赖关系复杂
上面的架构图是理想状况,但实际项目大概是这样的:
可以看到,很多业务模块依赖别的业务模块,而且依赖层级很深。这样就违背了模块化改造的初衷,耦合性并没有降低多少,各个业务模块也不能独立开发,修改代码牵一发而动全身。而且由于编译时要处理各个模块的依赖关系导致编译时间的大幅提高。
底层模块耦合业务
由于一些历史原因,base模块下的几个模块中有业务相关代码,可能经常需要修改。这就导致了其不能打成aar放在maven上,只能以源码形式进行依赖。base模块膨胀
各个业务模块之间往往需要通信,而且也会有很多共同依赖的资源文件。所以不能避免的需要将一些bean类和资源文件放置在base模块中。长此以往,base模块会变得越来越大。
解决方案
- 模块拆分
我觉得可以遵守以下几点原则: - 要把每个模块看成独立的app:每个模块的所有资源(.java、resources、manifest声明、lib库、so文件)都必须拆分到自己的模块.可以通过能否独立运行来校验是否有遗漏.
- 最小作用域:对于java类和资源文件,尽量做到最小作用域,能放到上层业务模块内就不要放到下层公共依赖工程中.
- 命名规范:资源文件最好加上模块名prefix(可以在gradle文件中设置resourcePrefix来警告不符规范的资源),另外可以通过脚本来找重名文件,去除重复.
- 模块间通信
要解决模块间的依赖,最重要的就是模块间通信,通过模块通信方式来减少直接依赖,我们项目现在主要采用了以下的通信方法: - activity路由
关于这个网上已经有了很多文章,我们项目的实现方法也类似.通过一段字符串(如/modulea/DemoActivity
)来标志某个activity,然后通过路由框架来获取activity的class,实现跳转. - ioc服务
除了activity跳转肯定还有大量其他需求,我们项目使用了ioc服务来完成.(此服务与android原生service概念类似,但并非android原生的service).简单的说就是在下层公共模块中定义一个功能接口(或抽象类)serviceA,然后在上层模块moduleA中创建了serviceA的实现serviceAImpl,上层模块moduleB(不依赖模块moduleA)通过ioc框架获取serviceA后就可得到真正的实现类.
每个模块都可以把自己需要被其他模块调用到的功能以服务的方式提供出来,而其他模块只要有功能接口就可以使用服务.
这个ioc框架的实现原理也比较简单,就是通过注解标注出功能接口实现类,然后利用注解处理器在编译时生成记录接口与实现对应关系的辅助类,在app启动时加载这些辅助类.这样ioc框架就能通过功能接口找到实现类了.和阿里的arouter类似 - 基础组件业务无关
从后期优化、维护方便等角度来说,网络、图片加载、持久化等基础组件都应该是业务无关的。所以对于这些组件中耦合业务的情况应该尽早重构修改。最好是将这些基础组件独立仓库,独立版本号,通过maven依赖来使用。如第一张图所示,可以在上层业务模块与基础组件中间的base模块中引入基础组件,并进行一些业务相关的配置。 - 合理划分模块
对于base模块膨胀的问题,其实关键还在于业务模块间划分不明确,导致很多view、业务组件需要重用。模块间通信只能解决一部分问题,最主要的还是要明确模块间要有明确的边界、合理划分模块,尽量让每个模块间独立起来,减少通信需求。
本文只是我们项目和我的一些想法、做法,有什么意见还望大家指正。