引言
现代 Android 项目通常构建在 Gradle 工程之上,开发者习惯于使用 Gradle Module 来划分和组织代码。然而,随着 Module 数量的增加(几十甚至上百个),如果模块间的依赖关系不合理,会严重拖慢工程的编译速度。如何更科学地组织 Gradle Module 是 Android 开发领域的普遍需求。
整洁架构(Clean Architecture)由 Robert C. Martin 提出,Google 推荐将其用于 MVVM 的分层优化。除了业务架构,其组件设计原则同样适用于优化 Gradle 的工程架构。本文将讨论如何基于整洁架构中的设计原则来设计和治理 Gradle Module。
Module 粒度划分
参考 Clean Architecture 中对组件的定义:组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。在 Android 中,Gradle Module 是发布 JAR 或 AAR 的基本单元,因此 Module 可视为一个组件。在 Module 粒度划分上,我们套用书中关于组件划分的三个原则:复用发布等价原则(REP)、共同封闭原则(CCP)、共同复用原则(CRP)。
复用发布等价原则(REP)
软件复用的最小粒度应等同于其发布的最小粒度。
REP 告诉我们 Module 划分的一个基本原则是代码的可复用性。当一些代码有被复用的价值时,它们就应该被考虑拆分为 Module,以便独立发布。此外,REP 还要求我们注意 Module 不能拆分过度。当我们发布 AAR 时都需要为其设定发布版本号,如果不设定版本号就无法保证组件之间能够彼此兼容。这在 androidx 系列组件中尤为突出,我们经常遇到因为版本不一致造成的运行时问题,产生这种不一致的一个重要原因就是组件的拆分过度。
如果两个可以独立发布的组件,它们总是作为一个整体被复用,就会出现可复用的粒度大于可发布粒度的问题,增大了版本冲突的概率。此时可以考虑将它们合二为一,同时发布以避免版本不一致。在小团队中这个问题不突出,因为通过人为约定可以保证所有组件同时发布;但在跨团队的大型项目中,如果一个功能的升级总是要多个团队一起配合,沟通成本是难以忍受的。
共同封闭原则(CCP)
组件中的所有类对于同一种性质的变化应该是共同封闭的,即一个变化的影响应该尽量局限在单个组件内部,而避免同时影响多个组件。我们应该将那些会因为相同目的而同时修改的类放到同一个组件中,而将不会为了相同目的同时修改的那些类放到不同的组件中。
相对于 REP 关注的可复用性,CCP 强调的是可维护性。很多场景下可维护性相对于可复用性更加重要。CCP 要求我们将所有可能被一起修改的类集中在一起,两个总是被一起修改的类应该放入同一组件。这和大家熟知的 SOLID 设计原则中的 SRP(单一职责)很类似,SRP 要求总是一起修改的函数应该放在同一个类,CCP 可以看作是组件版本的 SRP。
有的人在 Android 项目中喜欢按照代码的功能属性划分 Module,比如一个 MVVM 架构的工程,目录可能如下划分:
+ UI
+ Logic
+ Repository
+ API
+ DB
但实际开发中,很少只修改 UI 或者只修改 Logic,大多是围绕某个 Feature 进行垂直修改。这种跨 Module 修改显然违反了 CCP 原则,因此以业务属性为单位来进行 Module 划分可能更加合理。例如一个短视频应用的目录结构应该是:
+ VideoPlay
+ ui
+ data
+ ...
+ VideoCreation
+ Account
+ ...
这样,我们的修改可以在单个组件中闭环完成,在 Gradle 编译中减少受影响的模块,提升编译速度。
共同复用原则(CRP)
组件中的类应该同时被复用,即组件中不要依赖不参与复用的类。
REP 要求我们将紧密相关的类放在一个组件中一同发布,而 CRP 要求我们强调的是不要把不相关的类放进来,要用大家就一起用。要注意所谓'共同'复用并不意味着所有的类都能被外部访问,有些类可能是服务于内部其他类的,但也是必不可少的。我们虽然不希望组件过度拆分,但是同时要求组件的类不能过度冗余,不应该出现别人只需要依赖它的某几个类而不需要其他类的情况。


