Android 官方项目是怎么做模块化的?快来学习下
概述
模块化是将单一模块代码结构拆分为高内聚内耦合的多模块的一种编码实践。
模块化的好处
模块化有以下好处:
- 可扩展性:在高耦合的单一代码库中,牵一发而动全身。模块化项目当采用关注点分离原则。这会赋予了贡献者更多的自主权,同时也强制执行架构模式。
- 支持并行工作:模块化有助于减少代码冲突,为大型团队中的开发人员提供更高效的并行工作。
- 所有权:一个模块可以有一个专门的 owner,负责维护代码和测试、修复错误和审查更改。
- 封装:独立的代码更容易阅读、理解、测试和维护。
- 减少构建时间:利用 Gradle 的并行和增量构建可以减少构建时间。
- 动态交付:模块化是 Play 功能交付 的一项要求,它允许有条件地交付应用程序的某些功能或按需下载。
- 可重用性:模块化为代码共享和构建多个应用程序、跨不同平台、从同一基础提供了机会。
模块化的误区
模块化也可能会被滥用,需要注意以下问题:
- 太多模块:每个模块都有其成本,如 Gradle 配置的复杂性增加。这可能会导致 Gradle 同步及编译时间的增加,并产生持续的维护成本。此外,与单模块相比,添加更多模块会增加项目 Gradle 设置的复杂性。这可以通过使用约定插件来缓解,将可重用和可组合的构建配置提取到类型安全的 Kotlin 代码中。在 Now in Android 应用程序中,可以在 build-logic文件夹 中找到这些约定插件。
- 没有足够的模块:相反,如果你的模块很少、很大并且紧密耦合,最终会产生另外的大模块。这将失去模块化的一些好处。如果您的模块臃肿且没有单一的、明确定义的职责,您应该考虑将其进一步拆分。
- 太复杂了:模块化并没有灵丹妙药 -- 一种方案解决所有项目的模块化问题。事实上,模块化你的项目并不总是有意义的。这主要取决于代码库的大小和相对复杂性。如果您的项目预计不会超过某个阈值,则可扩展性和构建时间收益将不适用。
模块化策略
需要注意的是没有单一的模块化方案,可以确保其对所有项目都适用。但是,可以遵循一般准则,可以尽可能的享受其好处并规避其缺点。
这里提到的模块,是指 Android 项目中的 module,通常会包含 Gradle 构建脚本、源代码、资源等,模块可以独立构建和测试。如下:
一般来说,模块内的代码应该争取做到低耦合、高内聚。
- 低耦合:模块应尽可能相互独立,以便对一个模块的更改对其他模块的影响为零或最小。他们不应该了解其他模块的内部工作原理。
- 高内聚:一个模块应该包含一组充当系统的代码。它应该有明确的职责并保持在某些领域知识的范围内。例如,Now in Android 项目中的core-network模块负责发出网络请求、处理来自远程数据源的响应以及向其他模块提供数据。
Now in Android 项目中的模块类型
注:模块依赖图(如下)可以在模块化初期用于可视化各个模块之间的依赖关系。
Now in Android 项目中有以下几种类型的模块:
- app 模块: 包含绑定其余代码库的应用程序级和脚手架类,
app
例如和应用程序级受控导航。一个很好的例子是通过导航设置和底部导航栏设置。该模块依赖于所有模块和必需的模块。 feature-
模块: 功能特定的模块,其范围可以处理应用程序中的单一职责。这些模块可以在需要时被任何应用程序重用,包括测试或其他风格的应用程序,同时仍然保持分离和隔离。如果一个类只有一个feature
模块需要,它应该保留在该模块中。如果不是,则应将其提取到适当的core
模块中。一个feature
模块不应依赖于其他功能模块。他们只依赖于core
他们需要的模块。core-
模块:包含辅助代码和特定依赖项的公共库模块,需要在应用程序中的其他模块之间共享。这些模块可以依赖于其他核心模块,但它们不应依赖于功能模块或应用程序模块。- 其他模块 - 例如和模块
sync
、benchmark
、test
以及app-nia-catalog
用于快速显示我们的设计系统的目录应用程序。
项目中的主要模块
基于以上模块化方案,Now in Android 应用程序包含以下模块:
模块名 | 职责 | 关键类及核心示例 |
---|---|---|
app | 将应用程序正常运行所需的所有内容整合在一起。这包括 UI 脚手架和导航。 | NiaApp, MainActivity 应用级控制导航通过 NiaNavHost, NiaTopLevelNavigation |
feature-1, feature-2 ... | 与特定功能或用户相关的功能。通常包含从其他模块读取数据的 UI 组件和 ViewModel。如:feature-author 在 AuthorScreen 上显示有关作者的信息。feature-foryou 它在“For You” tab 页显示用户的新闻提要和首次运行期间的入职。 | AuthorScreen AuthorViewModel |
core-data | 保存多个特性模块中的数据。 | TopicsRepository AuthorsRepository |
core-ui | 不同功能使用的 UI 组件、可组合项和资源,例如图标。 | NiaIcons NewsResourceCardExpanded |
core-common | 模块之间共享的公共类。 | NiaDispatchers Result |
core-network | 发出网络请求并处理对应的结果。 | RetrofitNiANetworkApi |
core-testing | 测试依赖项、存储库和实用程序类。 | NiaTestRunner TestDispatcherRule |
core-datastore | 使用 DataStore 存储持久数据。 | NiaPreferences UserPreferencesSerializer |
core-database | 使用 Room 的本地数据库存储。 | NiADatabase DatabaseMigrations Dao classes |
core-model | 整个应用程序中使用的模型类。 | Author Episode NewsResource |
core-navigation | 导航依赖项和共享导航类。 | NiaNavigationDestination |
Now in Android 的模块化
Now in Android 项目中的模块化方案是在综合考虑项目的 Roadmap、即将开展的工作和新功能的情况下定义的。Now in Android 项目的目标是提供一个接近生产环境的大型 App 的模块化方案,并且要让方案看起来并没有过度模块化,希望是在两者之间找到一种平衡。
这种方法与 Android 社区进行了讨论,并根据他们的反馈进行了改进。这里并没有一个绝对的正确答案。归根结底,模块化 App 有很多方法和方法,没有唯一的灵丹妙药。这就需要在模块化之前考虑清楚目标、要解决的问题已经对后续工作的影响,这些特定的情况会决定模块化的具体方案。可以绘制出模块依赖关系图,以便帮助更好地分析和规划。
这个项目就是一个示例,并不是一个需要固守不可改变固定结构,相反而是可以根据需求就行变化的。根据 Now in Android 这是我们发现最适合我们项目的一般准则,并提供了一个示例,可以在此基础上进一步修改、扩展和构建。如果您的数据层很小,则可以将其保存在单个模块中。但是一旦存储库和数据源的数量开始增长,可能值得考虑将它们拆分为单独的模块。
最后,官方对其他方式的模块化方案也是持开发态度,有更好的方案及建议也可以反馈出来。
总结
以上内容是根据 Modularization learning journey 翻译整理而得。整体上是提供了一个示例,对一些初学者有一个可以参考学习的工程,对社区中模块化开发起到的积极的作用。说实话,这部分技术在国内并不是什么新技术了。
下面讲一个我个人对这个模块化方案的理解,以下是个人观点,请批判性看待。
首先是好的点提供了通用的 Gradle 配置,简化了各个模块的配置步骤,各种方式预计会在之后的一些项目中流行开来。
不足的点就是没有明确模块化的整体策略,是应采取按照功能还是按照特性分,类似讨论还有我们平时的类文件是按照功能来分还是特性来分,如下是按照特性区分:
# DO,建议方式
- Project
- feature1
- ui
- domain
- data
- feature2
- ui
- domain
- data
- feature3
按照功能区分的方式大致如下:
# DO NOT,不建议方式
- Project
- ui
- feature1
- feature2
- feature3
- domain
- feature1
- feature2
- feature3
- data
我个人是倾向去按照特性的方式区分,而示例中看上去是偏后者,或者是一个混合体,比如有的模块是添加 feature
前缀的,但是 core-model
模块又是在统一的一个模块中集中管理。个人建议的方式应该是将各个模块中各自使用的模型放到自己的模块中,否则项目在后续进行组件化时将会遇到频繁发版的问题。当然,这种方式在模块化的阶段并没有什么大问题。
模块化之后就是组件化,组件化之后就是壳工程,每个技术阶段对应到团队发展的阶段,有机会的话后面可以展开聊聊。
作者:madroid
链接:https://juejin.cn/post/7128069998978793509
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。