第1章 Gradle依赖管理基础
说实话,我刚开始用Gradle那会儿,光搞懂依赖是怎么来的就折腾了一下午。好多同学觉得build.gradle里写几行坐标就完事了,结果一跑就撞上版本冲突、找不到jar包之类的问题。先打好地基,后面解决冲突才不慌。
1.1 依赖声明 – 把需要的库写清楚
Gradle的依赖声明都在dependencies {}块里,说白了就是把每个依赖的坐标告诉构建系统。格式很简单,但作用域不一样,行为差异很大。我个人习惯把依赖分成两类:项目自身的和项目之间传递的。
implementation和api混着写,结果模块间暴露了不该暴露的东西。
先看一个最常见的声明例子:
dependencies {
implementation 'com.google.guava:guava:31.1-jre'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
}
这里implementation和testImplementation就是作用域。我建议把依赖写在build.gradle里,别写在gradle.properties那种奇怪的地方——当年我真见过有人这么干,结果Gradle根本不认。
1.2 仓库配置 – 从哪里拉jar包
依赖声明了,Gradle得知道去哪里下载。这就是repositories {}块干的事。默认可能只有mavenCentral(),但实际项目经常要配私有仓库或本地maven。我自己的经验是:仓库顺序影响构建速度,把访问速度快的放前面。
repositories {
mavenCentral()
mavenLocal()
maven { url 'https://nexus.yourcompany.com/repository/maven-public/' }
google() // Android项目常见
}
| 仓库类型 | 用法 | 注意点 |
|---|---|---|
mavenCentral() | 公共Maven仓库 | 默认有,但国内访问慢 |
mavenLocal() | 本机 ~/.m2 仓库 | 有时会缓存坏版本 |
jcenter() | 已弃用,不建议再用 | 2022年停止服务 |
| 自定义Maven仓库 | 公司内部Nexus等 | 记得配认证 |
allowInsecureProtocol,结果一直报HTTPS错误。如果你用http://协议,必须显式设置allowInsecureProtocol = true。好在那只是个内部测试仓库,不然后果很严重。
1.3 常见依赖作用域 – 搞清楚谁能用
作用域控制着依赖的传播范围和编译、运行时可见性。Gradle官方文档列了一大堆,但实际项目里常用的就这几个。我整理了一个表格方便对照:
| 作用域 | 什么时候用 | 是否透传给消费者 | 例子 |
|---|---|---|---|
implementation | 模块内部使用的依赖 | ❌ 不透传 | Logback、内部工具类 |
api | 消费者也需要该依赖 | ✅ 透传 | 公共SDK、fragment接口 |
compileOnly | 仅编译需要,运行时由环境提供 | ❌ 不透传 | Lombok、javax.annotation |
runtimeOnly | 运行时需要,编译时不用 | ❌ 不透传 | MySQL驱动、某些ClassLoader |
testImplementation | 测试代码专用 | ❌ 不透传 | JUnit、Mockito |
annotationProcessor | 注解处理器的声明 | ❌ 不透传 | Dagger、Glide |
有个经典场景:你用implementation引入了Guava,模块B依赖你,B的代码里直接引用Guava会编译报错。这就是“不透传”的好处——各模块自扫门前雪,减少冲突。我记得有一个项目就是因为到处用api,结果所有子模块都暴露了各种版本的Jackson,冲突叠罗汉。
implementation,只在需要暴露接口给消费者时用api。compileOnly能帮你省掉冗余jar,但别滥用,否则运行时ClassNotFoundException会教你做人。
1.4 传递性依赖与版本锁定
你声明了A,A又依赖B和C,B和C可能还依赖D。这个传递链是Gradle自动解析的。但如果B和C都依赖同一个D但版本不同呢?冲突就这么来了。Gradle默认选择最高版本,但有时候这个“最高”并不是你想要的那个。
我个人习惯在dependencies块里加一句constraints或者用force = true来锁定关键版本。比如:
dependencies {
implementation('com.google.guava:guava:31.1-jre') {
version {
strictly '31.1-jre'
}
}
}
这里strictly强制Gradle必须用这个版本,如果传递依赖里出现更高版本,它会报错。嗯,有点激进,但适合安全敏感场景。我一般在多模块项目的根项目里做好统一管理,后面章节会详细讲。
1.5 本章知识体系图谱
下面这张图把这一章的核心概念串起来了。从仓库拿到依赖、声明时指定作用域、再通过传递性影响下游。你想想看,如果任何一环没搞对,构建就可能出岔子。
这张图里,仓库配置是源头,依赖声明是中间人,作用域决定了传播规则,传递依赖则是引发冲突的直接原因。后面几章我们会围绕这张图里的每一个分支深入展开。
1.6 小试牛刀 – 快速检查你的理解
- 现在你的项目需要引入 OkHttp 3.12.0,其他模块也可能用到它,你会用
implementation还是api?为什么? - 如果公司 Maven 仓库地址写错了,Gradle 会从哪里下载依赖?
- 你在运行时抛了 ClassNotFoundException,但编译时一切正常。猜猜这可能是哪个作用域造成的?
这些问题答案不写在正文里,但我建议你动手改几行依赖声明试试,跑一下./gradlew dependencies看看树状图。我当年就是这么学会的——读一百遍文档不如亲手写错一次。
./gradlew :app:dependencies --configuration compileClasspath 可以列出指定配置的依赖树,方便查传递关系。
好了,这一章先到这儿。依赖管理的基础打牢了,后面讲版本冲突解决时你就能更快理解为什么Gradle会那样选版本、为什么有些冲突看似无解其实只是作用域设错了。