乐趣区

关于android:Android模块化开发实践

一、前言

随着业务的疾速倒退,当初的互联网 App 越来越大,为了进步团队开发效率,模块化开发曾经成为支流的开发模式。正好最近实现了 vivo 官网 App 业务模块化革新的工作,所以本文就对模块化开发模式进行一次全面的介绍,并总结模块化革新教训,帮忙兄弟我的项目避坑。

二、什么是模块化开发

首先咱们搞清两个概念,Android 客户端开发目前有两种模式:单工程开发模式 模块化开发模式

  • 单工程开发模式:晚期业务少、开发人员也少,一个 App 对应一个代码工程,所有的代码都集中在这一个工程的一个 module 里。
  • 模块化开发模式:简略来说,就是将一个 App 依据业务性能划分成多个独立的代码模块,整个 App 是由这些独立模块集成而成。

在讲什么是模块化开发前,咱们先定义分明两个概念:组件和模块。

  • 组件:指的是繁多的性能组件,比方登录组件、分享组件;
  • 模块:狭义上来说是指性能绝对独立、边界比拟清晰的业务、性能等,本文如果独自呈现模块这个词个别是该含意。广义上是指一个业务模块,对应产品业务,比方商城模块、社区模块。

模块和组件的实质思维是一样的,都是为了业务解耦和代码重用,组件绝对模块粒度更细。在划分的时候,模块是业务导向,划分一个个独立的业务模块,组件是性能导向,划分一个个独立的性能组件。

模块化开发模式又分为两种具体的开发模式:单工程多 module 模式 多工程模式

单工程多 module 模式

所有代码位于一个工程中,模块以 AndroidStudio 的 module 模式存在,由一个 App module 和多个模块 module 组成。如图:

多工程模式

每个模块代码位于一个工程中,整个我的项目由一个主模块工程和多个子模块工程组成。其中主模块工程只有一个 App module,用于集成子模块,进行整体调试、编包。子模块工程由一个 App module 和一个 Library module 组成,App module 中是调试、测试代码,Library module 中是业务、性能代码。如下图:

上面咱们来比照一下单工程多 module 模式和多工程模式的优缺点:

通过下面的比照,咱们能够看进去,多工程模式在代码治理、开发调试、业务并行等方面有显著劣势,非常适合像 vivo 官网这种业务线多、工程大、开发人员多的 App,所以 vivo 官网目前就采纳的此模式。本文在解说模块化开发时,个别也是指多工程模式。

单工程多 module 模式,更适宜开发人员少、业务并行水平低的我的项目。然而多工程模式也有 两个毛病:代码仓较多、开发时须要关上多个工程,针对这两个毛病,咱们也有解决方案。

代码仓较多的问题

要求咱们在拆分模块时粒度不能太细,当一个模块收缩到肯定水平时再进行拆分,在模块化带来的效率晋升与代码仓治理成本增加间保持平衡。

要关上多个工程开发的问题

咱们基于 Gradle 插件开发了代码管理工具,能够不便的切换通过代码依赖子模块或者 maven 依赖子模块,理论开发体验跟单工程多 module 模式一样,如下图;

模块化开发的流程也很简略:

  • 版本后期,每个模块由特定的开发人员负责,各子模块分别独立开发、调试;
  • 子模块开发实现后,集成到主模块工程进行整体调试;
  • 集成调试胜利后,进入测试。

三、模块化开发

3.1 咱们为什么要做模块化开发呢?

这里咱们说说繁多工程开发模式的一些痛点。

团队合作效率低

  • 我的项目晚期业务少、开发人员也少,随着业务倒退、团队扩张,因为代码都在同一个工程中,尽管各个人开发的性能不同,然而常常会批改同一处的代码,这时就须要相干开发人员沟通协调以满足各自需要,减少沟通老本;
  • 提交代码时,代码抵触也要沟通如何合并(否则可能引起问题),减少合代码老本;
  • 无奈进行并行版本开发,或者勉强进行并行开发,代价是各个代码分支差别大,合并代码艰难。

代码保护老本高

  • 繁多工程模式因为代码都在一起,代码耦合重大,业务与业务之间、业务与公共组件都存在很多耦合代码,能够说是你中有我、我中有你,任何批改都可能牵一发而动全身,随着版本的迭代,保护老本会越来越高。

开发调试效率低

  • 任何一次的批改,即便是改一个字符,都须要编译整个工程代码,随着代码越来越多,编译也越来越慢,十分影响开发效率。

3.2 如何解决问题

说完繁多工程开发模式的痛点,上面咱们看看模块化开发模式怎么来解决这些问题的。

进步团队合作效率

  • 模块化开发模式下,依据业务、性能将代码拆分成独立模块,代码位于不同的代码仓,版本并行开发时,各个业务线只在各自的模块代码仓中进行开发,互不烦扰,对本人批改的代码负责;
  • 测试人员只须要重点测试批改过的功能模块,无需全副回归测试;
  • 要求产品层面要有明确的业务划分,并行开发的版本必须是不同业务模块。

升高代码保护老本

  • 模块化开发对业务模块会划分比拟明确的边界,模块间代码是互相独立的,对一个业务模块的批改不会影响其余模块;
  • 当然,这对开发人员也提出了要求,模块代码须要做到高内聚。

进步编译速度

  • 开发阶段,只须要在本人的一个代码仓中开发、调试,无需集成残缺 App,编译代码量极少;
  • 集成调试阶段,开发的代码仓以代码形式依赖,其余不波及批改的代码仓以 aar 形式依赖,整体的编译代码量也比拟少。

当然模块化开发也不是说全都是益处,也存在一些毛病,比方:

1)业务繁多、开发人员少的 App 不要模块化开发,那样反而会带来更多的保护老本;

2)模块化开发会带来更多的反复代码;

3)拆分的模块越多,须要保护的代码仓越多,保护老本也会升高,须要在拆分粒度上把握均衡。

总结一下,模块化开发就像咱们治理书籍一样,一开始只有几本书时,堆书桌上就能够了。随着书越来越多,有几十上百本时,咱们须要一个书橱,依照类别放在不同的格子里。比照 App 迭代过程,起步时,业务少,繁多工程模式效率最高,随着业务倒退,咱们要依据业务拆分不同的模块。

所有这些目标都是为了方便管理、高效查找。

四、模块化架构设计

模块化架构设计的思路,咱们总结为 纵向和横向两个维度。纵向上依据与业务的严密水平进行分层,横向上依据业务或者性能的边界拆分模块。

下图是目前咱们 App 的整体架构。

4.1 纵向分层

先看纵向分层,依据业务耦合度从上到下顺次是业务层、组件层、根底框架层。

  • 业务层:位于架构最上层,依据业务模块划分(比方商城、社区等),与产品业务绝对应;
  • 组件层:App 的一些根底性能(比方登录、自降级)和业务专用的组件(比方分享、地址治理),提供肯定的复用能力;
  • 根底框架层:齐全与业务无关、通用的根底组件(比方网络申请、图片加载),提供齐全的复用能力。

框架层级从上往下,业务相关性越来越低,代码稳定性越来越高,代码入仓要求越来越严格(能够思考代码权限收紧,越底层的代码,入仓要求越高)。

4.2 横向分模块

  • 在每一层上依据肯定的粒度和边界,拆分独立模块。比方业务层,依据产品业务进行拆分。组件层则依据性能进行拆分。
  • 大模块能够独立一个代码仓(比方商城、社区),小模块则多个模块组成一个代码仓(比方上图中虚线中的就是多个模块位于一个仓)。
  • 模块要高内聚低耦合,尽量减少与其余模块的依赖。

面向对象设计准则强调组合优于继承,平行模块对应组合关系,上上层模块对应继承关系,组合的长处是封装性好,达到高内聚成果。所以在思考框架的层级问题上,咱们更偏差前者,也就是拆分的模块尽量平行,缩小层级。

层级多的问题在于,上层代码仓的批改会影响更多的下层代码仓,并且层级越多,并行开发、并行编译的水平越低。

模块依赖规定:

  • 只有下层代码仓能力依赖上层代码仓,不能反向依赖,否则可能会呈现循环依赖的问题;
  • 同一层的代码仓不能相互依赖,保障模块间彻底解耦。

五、模块化开发须要解决哪些问题

5.1 业务模块如何独立开发、调试?

形式一:每个工程有一个 App module 和一个 Library module,利用 App module 中的代码调试 Library module 中的业务性能代码。

形式二:利用代码管理工具集成到主工程中调试,开发中的代码仓以代码形式依赖,其余模块以 aar 形式依赖。

5.2 平行模块间如何实现页面跳转,包含 Activity 跳转、Fragment 获取?

依据模块依赖准则,平行模块间禁止相互依赖。隐式 Intent 尽管能解决该问题,然而须要通过 Manifest 集中管理,合作开发比拟麻烦,所以咱们抉择了路由框架 Arouter,Activity 跳转和 Fragment 获取都能完满反对。另外 Arouter 的拦截器性能也很弱小,比方解决跳转过程中的登录性能。

5.3 平行模块间如何互相调用办法?

Arouter 服务参考——https://github.com/alibaba/AR…。

5.4 平行模块间如何传递数据、驱动事件?

Arouter 服务、EventBus 都能够做到,视具体情况定。

六、老我的项目如何施行模块化革新

老我的项目施行模块化革新十分须要急躁和仔细,是一个循序渐进的过程。

先看一下咱们我的项目的模块化进化史,从繁多工程逐渐进化成纺锤形的多工程模块化模式。下图是进化的四个阶段,从最后的单个 App 工程到当初的 4 层多仓构造。

注:此图中每个方块示意一个代码仓,下层代码仓依赖上层代码仓。

晚期我的项目都是采纳繁多工程模式的,随着业务的倒退、人员的扩张,必然会面临将老我的项目进行模块化革新的过程。然而在模块化革新过程中,咱们会面临很多问题,比方:

  • 代码逻辑简单,不足文档、正文,不敢轻易批改,胆怯引起性能异样;
  • 代码耦合重大,你中有我我中有你,牵一动员全身,拆分重构难度大;
  • 业务版本迭代与模块化革新并行,代码抵触频繁,影响我的项目进度;

置信做模块化的人都会遇到这些问题,然而模块化革新势在必行,咱们不可能暂停业务迭代,把人力都投入到模块化中来,一来业务方不可能批准,二来投入太多人反而会带来更多代码抵触。

所以须要一个可行的革新思路,咱们总结为先 自顶向下划分,再自底向上拆分

自顶向下

  • 从整体到细节逐层划分模块,先划分业务线,业务线再划分业务模块,业务模块中再划分性能组件,最终造成一个树状图。

自底向上

  • 当咱们把模块划分明确、依赖关系梳理分明后,咱们就须要自底向上,从叶子模块开始进行拆分,当咱们把叶子模块都拆分实现后,枝干模块就能够轻松拆分,最初实现骨干局部的拆分。
  • 另外整个模块化工作须要由专人兼顾,整体规划,实现次要的革新工作,然而有简单的性能也能够提需要给各模块负责人,帮助实现革新。

上面就讲讲咱们在模块化革新路上打怪降级的一些教训。总的来说就是循序渐进,各个击破

6.1 业务模块梳理

这一步是自顶向下划分模块,也就是确定子模块代码仓。一个老我的项目必然通过多年迭代,通过很多人开发,你不肯定要对所有的代码都很相熟,然而你必须要根本理解所有的业务性能,在此基础上综合产品和技术布局进行初步的模块划分。

此时的模块划分能够粒度粗一点,比方依据业务线或者大的业务模块进行划分,然而边界要清晰。一个 App 个别会有多个业务线,每个业务线下又会有多个业务模块,这时,咱们梳理业务不须要太细,放弃 2 层即可,否则适度的拆分会大大增加施行的难度。

6.2 抽取公共组件

划分完模块,然而如果间接按此来拆分业务模块,会有很大难度,并且会有很多反复代码,因为很多公共组件是每个业务模块都要依赖的(比方网络申请、图片加载、分享、登录)。所以模块化拆分的第一步就是要抽取、下沉这些公共组件。

在这一步,咱们在抽取公共组件时会遇到两类公共组件,一类是齐全业务无关的根底框架组件(比方网络申请、图片加载),一类是业务相干的公共业务组件(比方分享、登录)。

能够将这两类公共组件分成两层,便于后续的整体框架造成。比方咱们的 lib 仓放的是根底框架组件和 core 仓放的是业务公共组件。如下图

6.3 业务模块拆分

抽取完公共组件后,咱们要筹备进行业务模块的拆分,这一步耗时最长,但也是成果最显著的,因为拆完咱们就能够多业务并行开发了。

确定要拆分的业务模块(比方下图的商城业务),先把代码仓拉进去,新性能间接在新仓开发。

那老性能该怎么拆分迁徙呢?咱们不可能一口吃成大瘦子,想一次把一个大业务模块全副拆分进去,难度太大。这时咱们就要对业务模块外部做进一步的梳理,找出所有的子功能模块(比方商城业务中的领取、选购、商详等)。

依照功能模块的独立水平,从易到难一一拆分,比方领取的订单性能比拟独立,那就先把订单性能的代码拆分到新仓。

6.4 功能模块拆分

在拆分具体性能时,咱们仍然应用 Top-Down 的逻辑来施行,首先找到入口类(比方 Activity),迁徙到新的代码仓中,此时你会发现一眼望去全是报红,就像拔草一样带出大量根须。依赖的布局、资源、辅助类等等都找不到,咱们依照从易到难的程序一个个解决,须要解决的依赖问题有以下几类:

1)简略的依赖,比方字符串、图片。

这类是最容易解决,间接把资源迁徙过去即可。

2)较简单的依赖,比方布局文件、drawable。

这类相对来说也比拟容易解决,逐级迁徙即可。比方布局依赖各种 drawable、字符串、图片,drawable 又依赖其余的 drawable 等,自顶向下一一迁徙就能解决。

3)更简单的依赖,相似 A ->B->C->D。

对于这类依赖有两种解决形式,如果依赖的性能没有业务个性或只是简略封装零碎 API,那能够思考间接 copy 一份;如果依赖的代码是多个功能模块专用的或者多个功能模块须要保持一致,能够思考将该性能代码抽取下沉到下一层代码仓。

4)一时难以解决的依赖。

能够先临时正文掉,保障能够失常运行,后续理清逻辑再决定是进行解耦还是重构。斩断依赖链十分重要,否则可能保持不上来。

6.5 代码解耦

上面介绍一下罕用的代码解耦办法:

公共代码抽取下沉

比方:根底组件(eg. 网络申请框架)、各模块须要放弃性能统一的代码(eg. 适配 OS 的动效);

简略代码复制一份

比方简略封装零碎 api(eg. 获取 packageName)、功能模块自用的自定义 view(eg. 提醒弹窗);

三个工具

Arouter 路由、Arouter 服务、EventBus,能满足各种解耦场景。

6.6 新老代码共存

老我的项目模块化是一个长期的过程,新老代码共存也是一个长期的过程。通过下面革新后,一个功能模块就能够独立进去了,因为咱们都是从老的 App 工程里拆分进去的,所以 App 工程依赖新仓后就能够失常运行。当咱们继续从老工程中拆分出独立模块,最初老工程只须要保留一些入口性能,作为集成子模块的主工程。

七、总结

本文从 模块化的概念 模块化架构设计 以及 老我的项目如何施行模块化改 造等几个方面介绍挪动利用客户端模块化实际。当然模块化工作远不止这些,还包含模块 aar 治理、继续集成、测试、模块化代码治理、版本迭代流程等,本文就不一一赘述,心愿这篇文章能给筹备做模块化开发的我的项目提供帮忙。

​作者:vivo 互联网客户端团队 -Wang Zhenyu

退出移动版