7. Gradle依赖管理:仓库配置、依赖声明、传递与排除、动态版本与快照版本、依赖锁定

依赖管理,说白了就是告诉Gradle:你的代码需要哪些“零件”,从哪里拿,拿哪个版本。我刚开始接触Android时,觉得这不就是加几行代码吗?直到有一次因为依赖冲突,编译花了两个小时,最后发现是某个库偷偷升级了……嗯,从那以后我再也不敢小看依赖管理了。

7.1 仓库配置:你的“零件仓库”

Gradle从哪里下载依赖?答案就是仓库。常见的仓库有这几个:

仓库名称 说明 现状
mavenCentral Sonatype维护的中央仓库 推荐首选,稳定可靠
jcenter JFrog维护的仓库 已停止服务,别用了
google Google官方仓库 Android必备,放AndroidX、Material等
mavenLocal 本地Maven缓存 调试本地库时用

我个人习惯在settings.gradle.kts里统一配置仓库,这样所有模块都能共享:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // 别加jcenter了,它已经凉了
    }
}
注意:我曾经接手过一个老项目,里面还配着jcenter。结果一编译就报错,因为好多库已经找不到了。赶紧迁移到mavenCentral吧。

7.2 依赖声明方式:三种姿势

在Gradle里声明依赖,主要有三种方式。我按使用频率排个序:

  1. 模块依赖:依赖自己项目里的其他模块
  2. 外部依赖:从远程仓库下载
  3. 文件依赖:直接引用本地的jar/aar文件

代码里长这样:

dependencies {
    // 模块依赖
    implementation(project(":core:network"))
    
    // 外部依赖
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    
    // 文件依赖
    implementation(files("libs/some-local-lib.jar"))
}

你想想看,implementationapi有什么区别?简单说:implementation不会把依赖暴露给外部,编译更快;api会传递出去。我建议能用implementation就用它,别偷懒。

7.3 依赖传递与排除:管好“连锁反应”

依赖传递,就是A依赖B,B依赖C,结果你的项目里自动有了C。听起来方便,但坑也在这里。

我曾经遇到一个bug:App里用了两个库,都依赖了不同版本的Gson。结果运行时随机崩溃,查了两天才发现是Gson版本冲突。解决方案就是排除传递依赖

implementation("com.example:library-a:1.0.0") {
    exclude(group = "com.google.code.gson", module = "gson")
}

或者全局排除:

configurations.all {
    exclude(group = "com.google.code.gson")
}
小技巧:./gradlew :app:dependencies命令,可以查看完整的依赖树。我每次遇到依赖问题,第一件事就是跑这个命令。

7.4 动态版本与快照版本:方便但危险

动态版本用+号表示,比如com.example:lib:1.+。意思是“取1.x系列的最新版”。快照版本则是com.example:lib:1.0.0-SNAPSHOT,每次构建都去检查有没有新快照。

说实话,我强烈不建议在生产项目里用动态版本。为什么?因为构建结果不可复现。今天编译好好的,明天可能就挂了。我曾经有个同事用了动态版本,结果CI构建时突然拉到一个有bug的版本,整个团队排查了一下午……

如果你非要用,记得设置缓存时间:

configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor(10, "minutes")
    resolutionStrategy.cacheChangingModulesFor(0, "seconds")
}

7.5 依赖锁定:给依赖上把锁

依赖锁定,就是把你项目里所有依赖的精确版本记录到一个文件里。这样不管什么时候构建,用的都是同一套版本。

Gradle提供了gradle.lockfile机制。启用方式很简单:

dependencyLocking {
    lockAllConfigurations()
}

然后运行:

./gradlew :app:dependencies --write-locks

这会生成一个gradle.lockfile文件。把它提交到Git里,以后构建就会严格按照这个文件来。如果有人改了依赖版本,锁文件会报错,必须手动更新。

核心思路:依赖锁定 = 可复现构建 + 团队一致性。我参与过的几个大型项目,全都用了依赖锁定。没有它,你永远不知道“昨天还能编译的代码,今天为什么不行了”。

知识体系图

Gradle依赖管理 仓库配置 mavenCentral / google jcenter已废弃 依赖声明方式 implementation / api / files 传递与排除 exclude / transitive 依赖树分析 动态版本与快照 1.+ / -SNAPSHOT 缓存策略 依赖锁定 gradle.lockfile 可复现构建 目标:稳定、可控、可复现的依赖管理

这张图把依赖管理的五个核心模块串起来了。从上到下,从左到右,你可以看到:仓库是源头,声明是入口,传递与排除是控制手段,动态版本是双刃剑,依赖锁定是最终保障。

我的建议:新项目直接上依赖锁定,别犹豫。老项目可以逐步迁移,先从核心模块开始。记住一句话:依赖管理不是写一次就完事的,它需要持续维护

公众号:蓝海资料掘金营,微信deep3321