如何在多模块项目中统一管理依赖 – Android

/  引言  /

在大型的软件项目中,特别是 Android 项目中,往往包含多个模块(modules)。每个模块可能负责不同的功能、组件或层次,而且这些模块之间可能存在相互依赖。因此,合理、统一的管理依赖对于项目的可维护性和构建的一致性非常重要。

/  统一管理依赖的好处  /

统一管理依赖的好处在于提高项目的可维护性、一致性和开发效率。这种做法通常用于大型软件项目,特别是多模块项目,其设计目的主要包括以下方面:

  • 版本一致性:这是因为在多模块项目中,如果每个模块都单独管理依赖,就有可能出现不同模块使用不同版本的库,导致潜在的兼容性问题。通过统一管理依赖,可以集中管理版本信息,减少这类问题的发生。

  • 可维护性:统一将依赖版本信息和声明集中在一个地方,当需要更新或切换依赖版本时,只需修改一个地方即可,而不必在每个模块的构建文件中进行手动更改。这提高了项目的可维护性,减少了重复的劳动。

  • 简化构建版本:通过在单一的地方定义依赖版本,可以减小每个模块的构建脚本的复杂性。模块的构建文件变得更加清晰、简洁,使得代码更容易理解。这对于新加入的开发人员更容易上手,并有助于快速了解项目结构和依赖关系。

/  buildSrc 目录是什么?  /

buildSrc 是一个特殊的目录结构,用于在 Gradle 构建中管理共享的构建逻辑和代码。它允许你将构建逻辑和代码从项目的构建脚本中提取出来,以便更好地组织、重用和测试构建逻辑。在 Android 项目中,buildSrc 目录通常用于管理依赖关系和插件版本等信息。

下面将使用 Kotlin+buildSrc 并划分为两步骤演示如何集中管理依赖以及在各个模块中的应用。

01

整合依赖

1. 在根目录新建 buildSrc 目录

图片

2. 在 buildSrc 中新建 build.gradle.kts 文件

在 gradle 文件中引入以下插件块并添加 kotlin-dsl 插件。当您运行 gradle 时,它会检查是否存在名为 buildSrc 然后,Gradle 会自动编译和测试此代码,并将其放入构建脚本的类路径中。您无需提供任何进一步的说明。


// buildSrc/build.gradle.kts
plugins{
    `kotlin-dsl`
}

3. 添加资源目录

现在 buildSrc 被正确识别为特殊的模块,还需要在此处创建新的资源目录,用于存放我们的 kt 类文件等等。由于我们使用的是 kotlin 版本的 Gradle,所以看起来与普通 kotlin 一致。

图片

4. 集中管理依赖版本和依赖声明

在 buildSrc 模块中创建 Versions 和 Depenencies 两个文件,主要负责管理依赖版本和依赖声明。从代码中可以看出非常清晰,一目了然。


// buildSrc/src/main/kotlin/Versions
object Versions {
    const val compose = "1.4.3"
    const val composeMaterial3 = "1.1.1"
    const val composeCompiler = "1.4.6"
    const val hilt = "2.45"
    const val okHttp = "5.0.0-alpha.2"
    const val retrofit = "2.9.0"
    const val room = "2.5.0"
}


// buildSrc/src/main/kotlin/Dependencies
object Dependencies {
      const val composeMaterial = "androidx.compose.material3:material3:${Versions.composeMaterial3}"
      const val composeUi = "androidx.compose.ui:ui:${Versions.compose}"
      const val composeUiGraphics = "androidx.compose.ui:ui-graphics:${Versions.compose}"
      const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
      const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose}"
      const val composeRuntime = "androidx.compose.runtime:runtime:${Versions.compose}"

      const val hiltAndroid = "com.google.dagger:hilt-android:${Versions.hilt}"
      const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt}"
      const val hiltAgp = "com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}"

      const val okHttp = "com.squareup.okhttp3:okhttp:${Versions.okHttp}"
      const val okHttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}"

      const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
      const val moshiConverter = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}"

      const val roomRuntime = "androidx.room:room-runtime:${Versions.room}"
      const val roomCompiler = "androidx.room:room-compiler:${Versions.room}"
      const val roomKtx = "androidx.room:room-ktx:${Versions.room}"
}

02

添加依赖

在 buildSrc 中添加好常用的依赖项后,剩余的工作需要在对应的模块中应用即可。例如模块A负责数据、模块B负责UI,那么在模块A中添加 Room 依赖。

图片

如下代码:相比于原始方法确实方便了很多,只需要引入简单的依赖名称。省去管理版本的操作。但是还不够,因为总是为某一个库捆绑多个依赖项,例如 Room、Retrofit 等等,所以需要更好的办法可以一次性添加。接着往下看

图片


// module-a/build.gradle.kts
dependencies {
    ...

    implementation(Dependencies.roomKtx)
    implementation(Dependencies.roomRuntime)
    kapt(Dependencies.roomCompiler)
}

回到 buildSrc 的 Gradle 文件中,添加 Kotlin 插件和 Android 工具。


// buildSrc/build.gradle.kts
...

repositories {
      google()
      mavenCentral()
}

dependencies {
      implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10")
      implementation("com.android.tools.build:gradle:8.1.1")
}

注意:需要把项目级别的 build.gradle.kts 文件的插件引入删掉,因为在同步过程中会报重复引入插件的错误。

所以删除自带的代码,替换为如下代码,只需要添加 hilt 插件即可。


// DependencyManagement/build.gradle.kts
buildscript {
      repositories {
            google()
            mavenCentral()
      }

      dependencies {
            classpath(Dependencies.hiltAgp)
      }
}

同步一下,在 buildSrc 中创建扩展文件,把常用的几种依赖类型添加进去,方便后期直接使用。


// buildSrc/src/main/kotlin/DependencyHandleExt.kt
fun DependencyHandler.implementation(dependency: String) {
      add("implementation", dependency)
}

fun DependencyHandler.test(dependency: String) {
      add("test", dependency)
}

fun DependencyHandler.androidTest(dependency: String) {
      add("androidTest", dependency)
}

fun DependencyHandler.debugImplementation(dependency: String) {
      add("debugImplementation", dependency)
}

fun DependencyHandler.kapt(dependency: String) {
      add("kapt", dependency)
}

到 Denpendencies 文件中,通过扩展函数的方式一次性导入库的所有依赖,例如 Room 中的三条依赖。


// buildSrc/src/main/kotlin/Dependencies.kt
object Dependencies {
      ...
}

// 通过扩展函数的方式一次性导入库的所有依赖
fun DependencyHandler.room(){
      implementation(Dependencies.roomKtx)
      implementation(Dependencies.roomRuntime)
      kapt(Dependencies.roomCompiler)
}

fun DependencyHandler.retrofit(){
      implementation(Dependencies.retrofit)
      implementation(Dependencies.moshiConverter)
      implementation(Dependencies.okHttp)
      implementation(Dependencies.okHttpLoggingInterceptor)
}

fun DependencyHandler.compose(){
      implementation(Dependencies.composeUi)
      implementation(Dependencies.composeMaterial)
      implementation(Dependencies.composeRuntime)
      implementation(Dependencies.composeUiTooling)
      implementation(Dependencies.composeUiGraphics)
      implementation(Dependencies.composeUiToolingPreview)
}

fun DependencyHandler.hilt(){
      implementation(Dependencies.hiltAndroid)
      kapt(Dependencies.hiltCompiler)
}

到此为止,每当我们添加依赖时,只需要在对应的模块中像调用函数一样的引入即可,是不是感觉非常的简洁。如下图:

图片

「 module-a模块:依赖引入 」

图片

「 module-b模块:依赖引入 」

最后一步在 app 模块中添加另外两个模块,并运行测试一下没有问题。如果有疑问可以后台私信留言或者 GitHub 提交 Issues。

图片

源码地址:

https://github.com/AAnthonyyyy/DependencyManagementMultiModule

/  总结  /

总的来说,这种 Kotlin+buildSrc 依赖管理策略提高了项目的整体可维护性、一致性,简化了构建脚本,使得团队更容易协作,同时也带来了更高的灵活性和适应性。这种设计在大型多模块项目中特别有益,能够有效应对复杂性和变化。