- 作者:老汪软件技巧
- 发表时间:2024-08-25 04:01
- 浏览量:
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
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)
}
同样支持代码提示,而且支持点击跳转。
动图感受一下:
是不是非常舒服~,除了版本管理,配置相关(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]
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、总结
通过上述几种版本管理方式,你可以根据项目规模、团队协作方式等,来选择适合的依赖版本管理策略,不用局限于某一种,也可以组合来使用。
没有绝对的最好,只有适合的才是最好的。
8、GitHub
/yechaoa/Gra…
9、相关文档