专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
郭霖  ·  一文了解 Gradle 的文件api ·  3 天前  
鸿洋  ·  掌握这17张图,掌握RecyclerView ... ·  2 天前  
郭霖  ·  Android - 监听网络状态 ·  6 天前  
鸿洋  ·  5种常见Gradle依赖版本管理指南 ·  6 天前  
51好读  ›  专栏  ›  鸿洋

5种常见Gradle依赖版本管理指南

鸿洋  · 公众号  · android  · 2024-12-06 08:35

正文


本文作者


作者:yechaoa

链接:

https://juejin.cn/post/7406147963876278312

本文由作者授权发布。


1
版本管理是什么

版本管理(Version Management)是指在开发过程中对项目依赖的各个库、框架、插件等版本进行管理和控制。这个过程确保项目中的所有组件以正确的版本组合在一起运行,以避免兼容性问题、漏洞和其他潜在问题。

对于一个复杂的项目来说,良好的版本管理是至关重要的,它能显著提高项目的可维护性和稳定性。
下面介绍几种常见的依赖版本管理方式。

2
直接指定版本号

新建项目时,在app > build.gradle文件中会有一些官方默认的组件依赖,会在dependencies{ }中声明并直接制定具体的版本号。

代码如下:
dependencies {

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")

    // ...

}

这里core-ktx依赖的版本号就是一个具体的版本号1.9.0。

这种方式简单直观,但随着项目的依赖变多时,版本管理分散,升级版本也会比较繁琐,维护成本较高,所以适用于依赖少且变化不频繁的项目。

3
变量占位符

顾名思义,不直接定义具体的版本号,而是通过占位符的方式来代替。

3.1、直接定义

通常来说,很少使用DSL语法在app > build.gradle文件中直接定义变量来用做版本号的,因为跟写死具体版本号也没什么区别了,一般会使用额外扩展属性(ext)或项目全局属性(gradle.properties)。如果是开发SDK,SDK中的依赖版本信息不需要依赖方感知,这么用也可以。
示例:
dependencies {
    val room_version = "2.6.1"

    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")

    // To use Kotlin annotation processing tool (kapt)
    kapt("androidx.room:room-compiler:$room_version")
    // To use Kotlin Symbol Processing (KSP)
    ksp("androidx.room:room-compiler:$room_version")

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:$room_version")

    // optional - RxJava2 support for Room
    implementation("androidx.room:room-rxjava2:$room_version")

    // optional - RxJava3 support for Room
    implementation("androidx.room:room-rxjava3:$room_version")

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation("androidx.room:room-guava:$room_version")

    // optional - Test helpers
    testImplementation("androidx.room:room-testing:$room_version")

    // optional - Paging 3 Integration
    implementation("androidx.room:room-paging:$room_version")
}


这个示例来自于Android Jetpack中的 room 组件库,从依赖声明来看,必选项不止一个,这种情况就可以把版本号抽出来统一管理,即val room_version = "2.6.1"

3.2、ext

ext全称Extra Properties Extension,是额外扩展属性的意思,以键值对的形式进行存储。
ext主要特性就是其扩展属性都可以通过该扩展的对象进行读写,比如把ext扩展属性写在根目录的build.gradle文件中,此时ext的扩展对象就是rootProject,那么就可以通过rootProject对象对ext中的属性进行读写。
所以我们可以利用ext的扩展特性,在项目的根目录build.gradle文件中来定义依赖的版本号,供所有module来读取,定义在根目录中也有利于属性全局共享和复用。
在根目录build.gradle文件中配置如下:
extra.apply {
    set("core-ktx""1.9.0")
    set("appcompat""1.6.1")
}

在app > build.gradle文件中读取:

dependencies {
    implementation("androidx.core:core-ktx:${rootProject.extra.get("core-ktx")}")
    implementation("androidx.appcompat:appcompat:${rootProject.extra.get("appcompat")}")
}

除了依赖的版本号,Android中的一些配置信息也可以使用ext来进行统一管理。

配置代码如下:
extra.apply {
    set("applicationId""com.yechaoa.gradlex")
    set("minSdk", 23)
    set("targetSdk", 33)

    set("core-ktx""1.9.0")
    set("appcompat""1.6.1")
}


使用:

android {
    defaultConfig {
        applicationId = rootProject.extra.get("applicationId"as String
        minSdk = rootProject.extra.get("minSdk"as Int
        targetSdk = rootProject.extra.get("targetSdk"as Int
        // ...
    }
}


其他属性以此类推。
这种方式使得版本和参数配置管理更加集中,方便更新,维护性好,缺点是不能直观的看到具体的版本号,需要手动切到根目录的build.gradle文件中查看。

3.3、gradle.properties

gradle.properties是用于定义构建脚本中的配置信息和属性的文件,位于项目的根目录下,以键值对的形式进行存储。
gradle.properties文件中配置的属性所有module都可以读取,也可用于项目版本管理,。
配置代码如下:
# 版本管理示例
applicationId=com.yechaoa.gradlex
minSdk=23
targetSdk=33

core_ktx="1.9.0"
appcompat="1.6.1"

使用:

android {
    defaultConfig {
        applicationId = properties["applicationId"].toString()
        minSdk = properties["minSdk"].toString().toInt()
        targetSdk = properties["targetSdk"].toString().toInt()
        // ...
    }
    dependencies {
        implementation("androidx.core:core-ktx:${properties["core_ktx"]}")
        implementation("androidx.appcompat:appcompat:${properties["appcompat"]}")
    }
}

properties["minSdk"]获取的值是Any对象,需要转为所需要的数据结构。

3.4、小结

这种方式跟ext方式不论是配置上还是使用上,都很相似,优缺点也差不多。版本和参数配置管理更加集中,维护性好,但是不能直观的看到具体的版本号,需要手动切到根目录的gradle.properties文件中查看。
那ext方式和gradle.properties方式怎么选呢?
ext本身比较灵活,可以定义复杂的对象、方法和逻辑,允许嵌套和更丰富的数据结构,还可以根据条件进行动态赋值,但是灵活也会伴随着维护成本的增加。
gradle.properties除了全局属性之外,也可以包含一些构建配置,比如开启并行构建、守护进程,其属性值是静态的,只能定义简单的键值对,不支持复杂的逻辑。

单论版本管理来讲,这二者差别不大,怎么选择就看自己的使用习惯。

4
buildSrc


buildSrc全称Build Sources,它是Gradle提供的一个特殊目录(约定),只能有一个buildSrc目录,且必须位于项目的根目录下,用于子项目之前的构建逻辑共享。
在构建时,Gradle识别到有buildSrc目录就会将其加入到构建流程中,因此buildSrc中定义的类、插件等都可以在项目的构建脚本中使用,不需要额外配置,我们可以理解它是一个专门为构建而生的独立模块。
在buildSrc中,可以自定义Task、插件、脚本函数等,以及依赖版本管理。
下面介绍一下如何使用buildSrc来进行依赖版本管理。

4.1、新建buildSrc文件夹

在project根目录下新建buildSrc目录:

4.2、新建构建脚本

在buildSrc目录下新建构建脚本build.gradle.kts文件:
在buildSrc > build.gradle.kts文件中添加编译buildSrc模块的基础配置。
配置如下:
plugins{
    kotlin("jvm") version "1.7.20"
}
repositories {
    google()
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
}

自己按需添加。

4.3、编写版本管理代码

buildSrc/src/main/kotlin 目录下创建一个Kotlin文件,例如Versions.kt,并在其中定义依赖版本号。
object Versions {
    const val core_ktx = "1.9.0"
    const val appcompat = "1.6.1"
}


4.4、使用

在app > build.gradle.kts文件中引用buildSrc目录下Versions.kt文件中的版本号。
代码如下:
dependencies {
    implementation("androidx.core:core-ktx:${Versions.core_ktx}")
    implementation("androidx.appcompat:appcompat:${Versions.appcompat}")
}


使用buildSrc的方式,在编写是也支持代码提示。
如下图:
此时,我们还可以更进一步,把依赖也用版本管理的方式进行管理。

4.5、进阶

buildSrc/src/main/kotlin目录下创建一个Kotlin文件,例如Libs.kt,并在其中定义依赖项。
object Libs {
    const val core_ktx = "androidx.core:core-ktx:${Versions.core_ktx}"
    const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
}


依赖中的版本号使用Versions.kt文件中定义的版本号。

然后在app > build.gradle.kts文件中修改依赖方式:
dependencies {
    implementation(Libs.core_ktx)
    implementation(Libs.appcompat)
}


同样支持代码提示,而且支持点击跳转。
动图感受一下:
[gif图]
是不是非常舒服~,除了版本管理,配置相关(targetSdk等)也可以这么使用。

4.6、小结

使用buildSrc进行版本管理,依赖和版本集中在一个模块,结构清晰,提升了代码的可读性和维护性,支持代码提示和跳转,使得开发体验也大大提升,适用于多模块或大型项目,对于小型项目来说可能显得复杂了一些。

5
version catalogs

Version Catalogs全称是version catalog libs,中文是「版本目录」的意思,是Gradle 7.0引入的一项新特性,允许你在一个集中式文件中管理所有依赖版本和插件版本。

version catalogs支持两种使用方式,一种是在settings.gradle(.kts)文件中声明,另一种是在单独的libs.versions.toml文件中声明,这种更解耦一些,推荐使用。

5.1、创建版本目录文件

在根项目的 gradle 文件夹中,创建一个名为 libs.versions.toml的文件。Gradle 默认会在 libs.versions.toml 文件中查找目录,因此建议使用此默认名称,这个叫约定大于协议
libs.versions.toml 文件中,添加以下配置:
[versions]

[libraries]

[plugins]
  • versions:定义用于保存依赖项和插件版本的变量。可以在libraries和plugins中使用这些变量。

  • libraries:定义依赖项。
  • plugins:定义插件。

5.2、定义依赖项和插件

libs.versions.toml文件中定义依赖项和插件。
代码如下:
[versions]
agp = "8.1.1"
ktx = "1.9.0"
appcompat = "1.6.1"

[libraries]

androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }

[plugins]

android-application = { id = "com.android.application", version.ref = "agp" }


建议为目录中的依赖项块命名采用kebab case,以便更好的代码补全。
kebab case是一种流行的命名规则,它的特点是单词之间使用连字符 (-) 分隔,且所有单词都小写,在web开发中应用比较广泛,比如css属性:font-size

5.3、修改依赖项和插件

定义好依赖项和插件之后,记得Sync同步一下项目。
然后我们先来修改app > build.gradle文件中依赖项。
代码如下:
dependencies {
    implementation(libs.androidx.ktx)
    implementation(libs.androidx.appcompat)
}


libs为libs.versions.toml文件中的开头名称,androidx.ktx对应[libraries]中的androidx-ktx
同buildSrc方式一样,也支持代码提示和点击跳转。
再来修改根目录build.gradle文件中的插件。
代码如下:
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
    alias(libs.plugins.android.application) apply false
    // ...
}


如果使用的是低于 8.1 的 Gradle 版本,则需要在使用版本目录时为 plugins{} 代码块添加注解 (@Suppress("DSL_SCOPE_VIOLATION")),原因是Gradle没做Kotlin DSL的扩展适配。

以及修改app > build.gradle文件中的插件引用。
代码如下:
plugins {
    alias(libs.plugins.android.application)
    // ...
}


插件的引用不再是使用id来声明,而是使用alias。

5.4、小结

version catalogs不只是版本管理,还有依赖项和插件的管理,支持模块间共享,集中在一个文件虽然方便维护,但是依赖较多的情况也会libs.versions.toml 文件变得庞大,特别是对于大型项目。
那version catalogs和buildSrc两种版本管理方式应该怎么选呢?
二者有一些相似的地方,比如都支持模块共享,以及代码提示和点击跳转。
对于大型项目来说,个人还是推荐使用buildSrc,不管是依赖管理还是版本管理,可以进行模块化拆分,而不是像version catalogs只有一个libs.versions.toml文件,而且支持构建逻辑共享,比version catalogs更灵活。

version catalogs对于中小型项目来说更友好一些,配置简单,标准化且易读,易于维护。

6
其他

除了上面介绍的几种依赖版本管理方式之外,还有一些其他方式,介绍两个。

6.1、动态添加

通过插件plugin的方式来进行管理,把依赖信息声明在一个配置文件中,类似于一个版本基线的东西,然后在Gradle配置阶段,读取配置中的依赖动态添加到项目中,这种方式多适用于自动化构建的场景,本地开发的话,个人觉得有些增加项目的复杂度了。
示例代码:
class DynamicDependencyPlugin : Plugin<Project{
    override fun apply(target: Project) {
        target.afterEvaluate {
            if (target.hasProperty("dynamicEnable")) {
                target.configurations.all {
                    dependencies.add("implementation""com.yechaoa.plugin:library:1.0.0")
                }
            }
        }
    }
}


6.2、版本约束
在主项目中利用resolutionStrategy处理机制强制指定依赖的版本,确保一致性,这种虽然手动维护成本较高,但是也能解决版本不一致带来的问题。
示例代码:
configurations.configureEach {
    resolutionStrategy.eachDependency {
        val details = this as DependencyResolveDetails
        val requested = details.requested
        if (requested.group == "com.squareup.okhttp3" && requested.name == "okhttp") {
            details.useVersion("4.10.0")
        }
        // ...
    }
}

7
总结

通过上述几种版本管理方式,你可以根据项目规模、团队协作方式等,来选择适合的依赖版本管理策略,不用局限于某一种,也可以组合来使用。

没有绝对的最好,只有适合的才是最好的。

GitHub

https://github.com/yechaoa/GradleX

相关文档

ExtraPropertiesExtension

https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html

Sharing Build Logic between Subprojects

https://docs.gradle.org/current/userguide/sharing_build_logic_between_subprojects.html#sec:using_buildsrc

Use buildSrc to abstract imperative logic

https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources

Sharing dependency versions between projects

https://docs.gradle.org/current/userguide/platforms.html

Declaring Rich Versions

https://docs.gradle.org/current/userguide/rich_versions.html#rich-version-constraints

TOML document

https://toml.io/en/

将 build 迁移到版本目录

https://developer.android.google.cn/build/migrate-to-catalogs?hl=zh-cn

【Gradle-6】一文搞懂Gradle的依赖管理和版本决议

https://juejin.cn/post/7215579793261117501




最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!