一文了解Gradle 依赖管理(五)- 依赖管理&缓存依赖

文章目录

  • [1. 版本目录 (Version Catalogs)](#1. 版本目录 (Version Catalogs))
  • [2. 依赖锁定(Dependency Locking)](#2. 依赖锁定(Dependency Locking))
      • [1. 依赖锁定的概念与重要性](#1. 依赖锁定的概念与重要性)
      • [2. 主要优势](#2. 主要优势)
      • [3. 如何启用依赖锁定](#3. 如何启用依赖锁定)
      • [4. 生成锁定文件](#4. 生成锁定文件)
      • [5. 更新锁定依赖](#5. 更新锁定依赖)
      • [6. 依赖锁定与版本目录结合使用](#6. 依赖锁定与版本目录结合使用)
  • [3. 依赖缓存与离线构建](#3. 依赖缓存与离线构建)
      • [1. Gradle 的依赖缓存机制](#1. Gradle 的依赖缓存机制)
      • [2. 配置缓存位置](#2. 配置缓存位置)
      • [3. 启用构建缓存](#3. 启用构建缓存)
      • [4. 离线模式构建](#4. 离线模式构建)
      • [5. 依赖缓存的最佳实践](#5. 依赖缓存的最佳实践)

1. 版本目录 (Version Catalogs)

版本目录是 Gradle 7.0 引入的一项功能,它提供了一种集中管理依赖版本和依赖组的强大方式,尤其适合多模块项目。

1. 版本目录的概念与优势

版本目录是一种将依赖定义从构建脚本中抽离出来的机制,使依赖管理更加结构化、集中化,并支持更好的 IDE 支持。

2. 主要优势

1. 集中管理 :在一个地方定义所有依赖版本和依赖组
2. 类型安全 :提供类型安全的依赖访问,减少拼写错误
3. IDE 支持 :更好的自动完成和导航支持
4. 依赖分组 :按逻辑组织依赖
5. 版本控制:轻松更新和跟踪依赖版本变化

3. 基本配置

版本目录通常在 settings.gradlesettings.gradle.kts 文件中定义:

groovy 复制代码
// 在settings.gradle中
dependencyResolutionManagement {
    versionCatalogs {
        libs {
            // 定义版本
            version('kotlin', '1.6.10')
            version('coroutines', '1.6.0')
            
            // 定义库
            library('kotlin-stdlib', 'org.jetbrains.kotlin', 'kotlin-stdlib').versionRef('kotlin')
            library('kotlin-reflect', 'org.jetbrains.kotlin', 'kotlin-reflect').versionRef('kotlin')
            library('coroutines-core', 'org.jetbrains.kotlinx', 'kotlinx-coroutines-core').versionRef('coroutines')
            
            // 定义依赖组(bundle)
            bundle('kotlin-bundle', ['kotlin-stdlib', 'kotlin-reflect'])
            
            // 定义插件
            plugin('kotlin-android', 'org.jetbrains.kotlin.android').versionRef('kotlin')
        }
    }
}

在 Kotlin DSL 中(settings.gradle.kts):

groovy 复制代码
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            version("kotlin", "1.6.10")
            version("coroutines", "1.6.0")
            
            library("kotlin-stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").versionRef("kotlin")
            library("kotlin-reflect", "org.jetbrains.kotlin", "kotlin-reflect").versionRef("kotlin")
            library("coroutines-core", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").versionRef("coroutines")
            
            bundle("kotlin-bundle", listOf("kotlin-stdlib", "kotlin-reflect"))
            
            plugin("kotlin-android", "org.jetbrains.kotlin.android").versionRef("kotlin")
        }
    }
}

4. 使用版本目录

一旦定义了版本目录,我们就可以在构建脚本中使用它们:

groovy 复制代码
plugins {
    id(libs.plugins.kotlin.android.get().pluginId)
}

dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(libs.coroutines.core)
    implementation(libs.bundles.kotlin.bundle)
}

5.使用外部版本目录文件

对于更复杂的项目,您可以将版本目录定义移动到专用的 TOML 文件中, 当然这也是Android官方推荐的做法,在新的AS项目中,默认就是使用的 TOML文件:

groovy 复制代码
# libs.versions.toml
[versions]
kotlin = "1.6.10"
coroutines = "1.6.0"
retrofit = "2.9.0"
okhttp = "4.9.3"
room = "2.4.1"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }

[bundles]
kotlin = ["kotlin-stdlib", "kotlin-reflect"]
coroutines = ["coroutines-core", "coroutines-android"]
retrofit = ["retrofit-core", "retrofit-gson", "okhttp-core", "okhttp-logging"]
room = ["room-runtime"]

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }

然后在 settings.gradle 中引用这个文件:

groovy 复制代码
dependencyResolutionManagement {
    versionCatalogs {
        libs {
            from(files("libs.versions.toml"))
        }
    }
}

6.实际项目中的版本目录最佳实践

1. 按功能或技术栈分组 :将依赖按照功能或技术栈分组,例如 networkuidatabase 等。
2. 使用有意义的别名 :为依赖创建直观的别名,如 retrofit 而不是 squareup-retrofit
3. 充分利用版本引用 :避免重复的版本号声明,使用版本引用确保相关库使用相同版本。
4. 创建有用的 bundle :将常一起使用的依赖组合成 bundle,简化依赖声明。
5. 保持 TOML 文件的良好组织:使用注释和分组保持文件可读性。

例如以下:

groovy 复制代码
# 网络相关依赖
[libraries.retrofit]
module = "com.squareup.retrofit2:retrofit"
version = "2.9.0"

# 注释说明特殊用途的依赖
[libraries.leak-canary]
module = "com.squareup.leakcanary:leakcanary-android"
version = "2.8.1"
# 仅在 debug 构建中使用

2. 依赖锁定(Dependency Locking)

依赖锁定是确保构建可重复性的重要技术,特别是当项目使用动态版本或版本范围时。

1. 依赖锁定的概念与重要性

依赖锁定将依赖图"冻结"在特定的时间点,记录解析的确切版本,确保后续构建使用相同的依赖版本,即使新版本发布。

2. 主要优势

  1. 构建可重现性:确保相同的源代码总是使用相同的依赖版本构建
  2. 防止意外升级:避免自动获取新发布的库版本
  3. 行为一致性:减少"在我的机器上能运行"问题
  4. 安全性:在审核新版本之前防止引入新依赖

3. 如何启用依赖锁定

settings.gradle 或特定项目的 build.gradle 中启用依赖锁定:

groovy 复制代码
// 为所有配置启用依赖锁定
dependencyLocking {
    lockAllConfigurations()
}

// 或者为特定配置启用
configurations.compileClasspath {
    resolutionStrategy.activateDependencyLocking()
}

4. 生成锁定文件

一旦启用了依赖锁定,您需要生成锁定文件:

groovy 复制代码
# 为所有配置生成锁定文件
./gradlew dependencies --write-locks

# 为特定项目生成锁定文件
./gradlew :app:dependencies --write-locks

这将在 gradle/dependency-locks 目录下创建锁定文件,每个配置一个文件。

5. 更新锁定依赖

当需要有意更新依赖版本时:

groovy 复制代码
# 更新所有依赖锁
./gradlew dependencies --update-locks "com.squareup.retrofit2:*"

# 或者完全刷新所有锁
./gradlew dependencies --write-locks

6. 依赖锁定与版本目录结合使用

依赖锁定和版本目录可以很好地结合使用,这种组合确保您既有集中的版本管理,又有确切的依赖锁定,提供最大的可重复性和控制。

  • 版本目录:声明您期望的依赖版本(理想状态)
  • 依赖锁定:记录实际解析的版本(实际状态)
groovy 复制代码
// 在build.gradle中
dependencies {
    implementation libs.retrofit.core // 从版本目录
}

dependencyLocking {
    lockAllConfigurations()
}

这里可能稍微难理解,我用个例子简单来描述一下。

假设我们开发的App中, Retrofit 2.9.0 依赖 OkHttp 4.9.0,但我们在版本目录中指定了更新的 OkHttp 4.9.3,

当没有依赖锁定时:

  • 第一个开发者构建项目时会使用 OkHttp 4.9.3
  • 下一个开发者几周后构建项目,可能 OkHttp 已更新到 4.9.5,他将使用 4.9.5

开启依赖锁定后:

  • 所有开发者都会使用锁定文件中的精确版本 OkHttp 4.9.3
  • 即使 OkHttp 发布了 4.9.5 版本,除非我们显式更新锁定文件,否则团队仍会使用 4.9.3

现在假设我们的目录版本中更新了Gson:

groovy 复制代码
[versions]
# 更新了版本
gson = "2.9.0" # 从 2.8.5 更新

有了依赖锁定:

  1. 即使版本目录更新了,项目仍会使用锁定文件中的 2.8.5
  2. 团队可以讨论并决定何时更新锁定文件
  3. 当团队准备好时,可以显式更新锁定:
groovy 复制代码
./gradlew dependencies --update-locks "com.google.gson*:*"

3. 依赖缓存与离线构建

有效管理依赖缓存可以显著提高构建性能,特别是在团队环境或 CI/CD 管道中。

1. Gradle 的依赖缓存机制

Gradle 在本地缓存所有下载的依赖,默认位置是用户主目录下的 .gradle/caches 目录。

2. 配置缓存位置

我们可以自定义缓存位置, 在 gradle.properties中:

groovy 复制代码
# gradle.properties
org.gradle.caching=true
org.gradle.caching.debug=false
org.gradle.cache.dir=/custom/cache/directory

3. 启用构建缓存

构建缓存可以在配置中启用:

groovy 复制代码
# settings.gradle
buildCache {
    local {
        enabled = true
    }
}

4. 离线模式构建

离线模式强制 Gradle 只使用缓存的依赖:

groovy 复制代码
# 命令行参数
./gradlew build --offline

# 或在 gradle.properties 中永久设置
org.gradle.offline=true

5. 依赖缓存的最佳实践

1. 共享缓存:考虑在团队之间或 CI 系统中共享缓存目录,减少重复下载

2. 定期清理:定期清理缓存,删除不再使用的依赖版本

groovy 复制代码
./gradlew cleanBuildCache

3. 预热缓存:在 CI 构建前或开发环境设置中预热缓存

groovy 复制代码
./gradlew downloadDependencies

4. 缓存备份:备份关键依赖的缓存,防止远程仓库不可用

5. 使用镜像或私有仓库:设置本地镜像或私有依赖仓库,减少外部依赖.

相关推荐
Hello.Reader5 小时前
Redis 延迟排查与优化全攻略
数据库·redis·缓存
代码的余温10 小时前
5种高效解决Maven依赖冲突的方法
java·maven
paishishaba10 小时前
Maven
java·maven
yzpyzp12 小时前
Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
android·gradle·android studio
在肯德基吃麻辣烫17 小时前
《Redis》缓存与分布式锁
redis·分布式·缓存
代码的余温21 小时前
Maven引入第三方JAR包实战指南
java·maven·jar
先睡1 天前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
Wyc724092 天前
Maven
java·数据库·maven
CodeWithMe2 天前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存
大春儿的试验田2 天前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存