Now in Android 项目学习 - 构建

cover

Now in Android是Android团队开源的示例项目,体现Android团队推荐的Android应用开发方法。这个项目在持续更新中。我们可以从这个项目学习到最新的开发Android App的技术和官方推荐的方式。这篇博客通过回答以下问题,来总结构建和模块组织方面的内容和思考:

  • Q1: NIA项目是如何组织项目内的module的?
  • Q2: NIA项目是如何在众多module间共享构建配置的?
  • Q3: NIA项目是如何管理依赖的?

下文中NIA表示 Now in Android。

Q1: NIA项目是如何组织项目内的module的?

NIA项目采用官方推荐的架构方式,分为UI Layer、Domain Layer、Data Layer。在module组织上,主要划分成两个组:

  • core:包含Domain Layer和Data Layer的通用逻辑。
  • feature:包含UI Layer的逻辑。

project-structure

module间仅存在从上至下的单向依赖关系。项目里给出了module间的依赖关系图,比如:feature:foryou这个module会依赖:core下的其他module。

思考

NIA项目清晰地展示了项目架构和文件组织的关系,但是缺失了一个重要的考虑因素:实际的商业项目往往由多个团队并行开发并集成到一个产物中,NIA的做法对项目进行纵向分层,这没问题,但是实际开发过程往往需要横向按照业务进行拆分。NIA仅在:feature组下将功能拆分到独立的module,是不够的。

实际项目往往每个业务线、项目组维护自己的代码仓库,对于包含多个仓库的项目的维护,以及多个仓库的管理,是需要额外考虑的问题,对项目的架构和代码组织是另一个挑战。

Q2: NIA项目是如何在众多module间共享构建配置的?

构建配置主要指Gradle脚本的逻辑。NIA项目充分发挥了的Gradle的扩展性,使用插件在多个module中共享配置,并简化module的配置。比如下面是:feature:foryou模块的build.gradle.kts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins {
alias(libs.plugins.nowinandroid.android.feature)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
}

android {
namespace = "com.google.samples.apps.nowinandroid.feature.foryou"
}

dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.activity.compose)
implementation(libs.accompanist.permissions)
}

可以看到除了namespace和特殊的依赖,其他的公共配置都通过插件进行剥离和共享。这一点在大型项目中非常重要。
NIA项目的配置并没有提取一个统一的NIA插件,而是按照配置类型拆分成了多种插件,其他module按需组合搭配使用,保证灵活性。
有两点值得注意:

  1. NIA项目的插件在同一个project下,实际项目中可以拆分成独立项目,以获得更大的复用性。
  2. 在多仓库构建的大型项目中,经常需要增加新的仓库来支持业务发展,需要考虑基于共享配置提供新项目脚手架(项目模板) 用来创建新项目。

Q3: NIA项目是如何管理依赖的?

Gradle 7.0 开始支持Version Catalog特性,支持在脚本外统一定义项目依赖。libs.versions.toml将做为项目依赖的Single Source of Truth。Version Catalog 的介绍和使用可以参考:Android 开发者网站Gradle指南 。官方示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[versions]
groovy = "3.0.5"
checkstyle = "8.37"

[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }

[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]

[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }

有一个细节需要注意:Gradle会对依赖的Alias进行标准化处理:Alias will be automatically normalized: '-', '_' and '.' will be replaced with '.’,也就是最终都会变成使用.(点号)连接。NIA中有一个Anti-pattern,toml中定义的alias和build.gradle.kts中使用的不同,导致通过简单搜索无法直接找到使用依赖的位置。实际项目中,我们应当保证项目中使用一致的命名。或者,如果使用共享插件管理,可以考虑在插件中显式定义依赖。

参考

https://juejin.cn/post/7170848190139203614