/  引言  /

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

/  统一管理依赖的好处  /

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

  • 版本一致性:这是因为在多模块项目中,如果每个模块都单独管理依赖,就有可能出现不同模块使用不同版本的库,导致潜在的兼容性问题。通过统一管理依赖,可以集中管理版本信息,减少这类问题的发生。
  • 可维护性:统一将依赖版本信息和声明集中在一个地方,当需要更新或切换依赖版本时,只需修改一个地方即可,而不必在每个模块的构建文件中进行手动更改。这提高了项目的可维护性,减少了重复的劳动。
  • 简化构建版本:通过在单一的地方定义依赖版本,可以减小每个模块的构建脚本的复杂性。模块的构建文件变得更加清晰、简洁,使得代码更容易理解。这对于新加入的开发人员更容易上手,并有助于快速了解项目结构和依赖关系。

/  buildSrc 目录是什么?  /

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

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

01

整合依赖

1. 在根目录新建 buildSrc 目录

微服务项目某个模块打包_ci

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

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


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

3. 添加资源目录

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

微服务项目某个模块打包_ci_02

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 依赖。

微服务项目某个模块打包_ci_03

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

微服务项目某个模块打包_ci_04


// 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)
 }

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

微服务项目某个模块打包_微服务项目某个模块打包_05

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

微服务项目某个模块打包_微服务项目某个模块打包_06

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

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

微服务项目某个模块打包_ci_07

源码地址:

https://github.com/AAnthonyyyy/DependencyManagementMultiModule

/  总结  /

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