参考文章
谷歌官方指南
一、概念
将大型、复杂问题拆解成一个个小的、简单问题,从而可以做到各个击破。模块化简单讲就是把多功能高耦合的代码逻辑拆散成多个功能单一职责明确的模块。模块指 Android 项目中的 module,通常会包含 Gradle 构建脚本、源代码、资源等,模块可以独立构建和测试。
1.1 好处
可扩展性 | 在高耦合的单一代码库中,牵一发而动全身。模块化项目当采用关注点分离原则,从而规避了上述问题。 |
支持并行工作 | 模块化有助于减少代码冲突,为大型团队中的开发人员提供更高效的并行工作。 |
所有权 | 一个模块可以有一个专门的负责人,负责维护代码和测试、修复错误和审查更改。 |
封装 | 独立的代码更容易阅读、理解、测试和维护。 |
减少构建时间 | 利用 Gradle 的并行构建、增量构建、构建缓存可以减少编译时间。 |
可定制的交付 | 可以使用 Play 商店动态下发( Play Feature Delivery )功能,它允许有条件地交付应用程序的某些功能或按需下载。 |
可重用性 | 每个模块都是一个独立有效的构建单元,可以复用来构建多个APP。 |
严格的访问权限 | 模块可以很好做控制代码的可访问性,模块内部私有的逻辑添加 internal 或者 private 修饰。防止代码被其他模块引用而导致的过度耦合。 |
1.2 误区
太细粒度 | 意味着项目中会有很多模块,每个模块都有其成本,可能会导致 Gradle 同步及编译时间的增加,并产生持续的维护成本。此外,与单模块相比,添加更多模块会增加项目 Gradle 设置的复杂性。这可以通过使用约定插件来缓解,将可重用和可组合的构建配置提取到类型安全的 Kotlin 代码中。 |
太粗粒度 | 意味着项目中会有很少模块,这将失去模块化的一些好处。如果您的模块臃肿且没有单一的、明确定义的职责,您应该考虑将其进一步拆分。 |
太复杂了 | 将项目模块化并不总是有意义的。这主要取决于代码库的大小和相对复杂性,如果您的项目预计不会超过某个阈值,则可扩展性和构建时间收益将不适用,保持现状也是一种不错的选择。 |
1.3 高内聚度耦合原则
如果两个模块严重依赖彼此,那么它们实际上应该作为一个系统运行。相反,如果一个模块的两个部分不经常交互,它们可能应该是单独的模块。
低耦合 | 模块应尽可能相互独立,以便对一个模块的更改对其他模块的影响为零或最小。他们不应该了解其他模块的内部工作原理。 |
高内聚 | 一个模块应该有明确的职责并保持在某些领域知识的范围内,如一个电子书应用程序,将书籍和支付的代码混合在同一个模块中可能是不合适的,因为它们是两个不同的功能领域。 |
二、结构划分
app 应用模块 | 是应用程序的入口点,通常提供导航能力。使用多渠道打包方案,单个应用程序模块可以编译为许多不同的二进制文件。如根据使用用途可以分为正式版本 App、 测试 Demo App,其中正式版本 App 根据其发布平台又可以分为 智能手机、汽车、电视、可穿戴设备等。 |
feature 特性模块 | 特性是 App 中功能相对独立的部分,通常对包含一个页面或一系列密切相关的页面,例如注册或结帐流程。如果您的应用具有底部栏导航,则很可能每个目的地都是一项功能。特性模块中一般会包含页面或路由(destinations)。因此,在模块内部需求处理 UI Layer 中相关的内容。特性模块中不必局限于单个页面或导航目的地,可以包含多个页面。 |
data 数据模块 | 封装某个领域的所有数据和业务逻辑:每个数据模块应该负责处理代表某个领域的数据。它可以处理多种相关类型的数据。 将 Repository 公开为外部 API:数据模块的公共 API 应该是 Repository,因为它们负责将数据公开给 App 的其余部分。 对外隐藏所有实现细节和 DataSource:DataSource 只能由同一模块的 Repository 访问,对外是隐藏的状态。可以通过使用 Kotlin 的 private 或者 internal 关键字来强制执行此操作。 |
common 公共模块 | 也称为核心模块或者基础模块,包含其他模块经常使用的代码。 基础 UI 模块:如果 App 中使用自定义 View 和样式(style),应该考虑将他们统一封装到一个模块中,以便可以复用。也就是大家通常所说的 UI 规范库,这可以使 UI 在不同特性模块之间保持一致。 |
三、模块间通信
页面跳转 | Navigation、各种路由框架。 |
数据交互 | 使用 Hilt,依赖管理不仅可以很好地解决对象繁琐的初始化逻辑,还可以很好的实施控制反转的编码思想。 |