简介这份资源聚焦 Android 工程中 Gradle 依赖的统一管理面向需要规范多模块依赖版本、减少重复配置的 Android 开发者尤其适合已掌握 Gradle 基础、希望进一步优化构建脚本的中级工程师。压缩包共 34 个文件约 100KB以 xml 配置、bat 批处理脚本、gradle 构建脚本、properties 属性文件为主另含 png 示意图、java 源码、jar 包及 gradlew 启动脚本覆盖从依赖声明到批量执行的完整环节。资源中整理了 Windows 与类 Unix 环境下常用的 Gradle 命令如下载更新、查看版本、构建输出、运行检测与测试、清理 build 目录、编译打包 debug 与 release 等并借助多个 bat 脚本演示批量处理思路config.gradle 与 settings.gradle 则体现集中式依赖版本管理方式。目前已有 3656 人学习下载可帮助读者理解依赖统一管理的目录组织与脚本写法快速迁移到自己的多模块项目中。1. 从一次构建翻车说起Gradle 依赖统一管理到底管什么上周帮一个朋友看他那个 Android 项目./gradlew assembleDebug跑了三分半报错信息刷了满屏最后定位到问题是两个模块引了同一个库的不同版本一个走implementation com.squareup.okhttp3:okhttp:4.9.0另一个走api com.squareup.okhttp3:okhttp:4.12.0编译期没炸运行期直接NoSuchMethodError。这种问题在单模块项目里几乎不会遇到但只要模块数超过五个依赖版本就开始各写各的谁也不知道哪个版本是对的。Gradle 依赖统一管理要解决的就是这件事把版本号从各个build.gradle里抽出来集中到一处声明让所有模块引用同一份版本定义。它适合已经进入多模块阶段、或者正准备拆模块的 Android 工程也适合任何用 Gradle 构建的 JVM 项目。核心思路不复杂但落地时的细节和坑比想象中多下面按我实际拆过的顺序讲。2. 版本目录与 BOM两种统一管理路线的选型对比2.1 为什么不能靠手动对齐版本号最原始的做法是每个模块的build.gradle里各写各的版本靠人肉保证一致。模块少的时候还能撑住一旦到了十几个模块每次升级一个库就要全局搜索替换漏掉一个就是运行时崩溃。更麻烦的是传递依赖A 模块依赖了库 X库 X 又依赖了库 Y 的 1.2 版本而 B 模块直接依赖了库 Y 的 2.0 版本Gradle 默认按最高版本优先策略解析结果 A 模块在运行时拿到的是 Y 2.0如果 2.0 有 breaking changeA 模块就炸了。这种问题排查起来极其痛苦因为编译期完全看不出来。所以统一管理的本质是两层第一层是显式声明的直接依赖版本要统一第二层是传递依赖的版本冲突要有策略。只做第一层不做第二层等于只堵了一半的洞。2.2 路线一ext扩展属性 config.gradle这是最老牌的做法兼容性最好几乎所有 Gradle 版本都能用。在项目根目录建一个config.gradle把所有版本号和依赖坐标写进去// config.gradle ext { // 版本号集中定义改这里就够了 versions [ compileSdk : 34, minSdk : 24, targetSdk : 34, kotlin : 1.9.22, coreKtx : 1.12.0, appcompat : 1.6.1, retrofit : 2.9.0, okhttp : 4.12.0, glide : 4.16.0 ] // 依赖坐标引用上面的版本号避免硬编码 deps [ coreKtx : androidx.core:core-ktx:${versions.coreKtx}, appcompat : androidx.appcompat:appcompat:${versions.appcompat}, retrofit : com.squareup.retrofit2:retrofit:${versions.retrofit}, okhttp : com.squareup.okhttp3:okhttp:${versions.okhttp}, glide : com.github.bumptech.glide:glide:${versions.glide} ] }然后在根build.gradle里apply from: config.gradle各模块直接用rootProject.ext.deps.retrofit引用。逻辑很直白ext是 Gradle 的扩展属性机制挂到rootProject上之后所有子项目都能读到。参数改的时候只动versions块deps块自动跟着变。这个方案的好处是零依赖、零学习成本团队里谁都能看懂。缺点是 IDE 对rootProject.ext的补全支持一般写错了要到编译期才发现另外它只管直接依赖传递依赖冲突还是得靠resolutionStrategy单独处理。2.3 路线二Version Cataloglibs.versions.tomlGradle 7.0 之后官方推出版本目录机制用 TOML 文件声明版本和依赖IDE 补全和类型安全都好很多。在gradle/目录下建libs.versions.toml# gradle/libs.versions.toml [versions] kotlin 1.9.22 coreKtx 1.12.0 retrofit 2.9.0 okhttp 4.12.0 [libraries] core-ktx { group androidx.core, name core-ktx, version.ref coreKtx } retrofit { group com.squareup.retrofit2, name retrofit, version.ref retrofit } okhttp { group com.squareup.okhttp3, name okhttp, version.ref okhttp } [bundles] network [retrofit, okhttp] [plugins] android-application { id com.android.application, version.ref agp }模块里引用时写implementation(libs.core.ktx)多个库打包引用写implementation(libs.bundles.network)。version.ref指向[versions]里的键改一处全局生效。[bundles]是可选的分组机制把经常一起用的库打包减少重复书写。选哪条路线我的判断标准是Gradle 版本低于 7.0 或者团队对 TOML 不熟走ext方案Gradle 7.0 以上且用 Android Studio 新版本直接上 Version CatalogIDE 体验差距很明显。两条路线不冲突迁移期可以共存但别长期混用否则新人不知道该看哪个文件。2.4 路线三Platform / BOM 约束传递依赖前面两条路线解决的是直接依赖版本统一传递依赖冲突还得靠 BOMBill of Materials。BOM 本身是一个只包含依赖约束的 POM 文件引入之后它会告诉 Gradle这个库及其所有子依赖都用我指定的版本。// 使用官方 BOM 约束整个网络栈的版本 dependencies { implementation platform(com.squareup.okhttp3:okhttp-bom:4.12.0) // 下面这些不需要写版本号BOM 会统一指定 implementation com.squareup.okhttp3:okhttp implementation com.squareup.okhttp3:logging-interceptor }platform()是 Gradle 的依赖约束语法它不引入实际依赖只引入版本约束。如果项目里有多个 BOM 冲突用enforcedPlatform()强制生效。常见做法是 Retrofit、OkHttp、Kotlin 标准库各引一个 BOM把最容易出传递依赖问题的几个生态圈住。三条路线的关系是互补的Version Catalog 管声明BOM 管传递resolutionStrategy管兜底。实际项目里我一般三个都用只是分工不同。3. 落地步骤从零搭一套可维护的依赖管理体系3.1 第一步盘点现有依赖生成版本清单别急着改先把现状摸清楚。在根目录执行# 输出所有模块的依赖树重定向到文件方便搜索 ./gradlew :app:dependencies --configuration releaseRuntimeClasspath deps-app.txt # 只看某个具体库被哪些模块引了、版本分别是多少 ./gradlew dependencyInsight --dependency okhttp --configuration releaseRuntimeClasspathdependencies任务输出的是完整依赖树包含传递依赖文件可能几千行。dependencyInsight更精准指定一个库名它会告诉你这个库最终解析到了哪个版本、为什么是这个版本是被直接声明还是被传递引入。把这两个命令的输出存下来作为迁移前的基线。3.2 第二步建立版本目录文件并迁移声明以 Version Catalog 为例把盘点出来的版本号填进libs.versions.toml然后逐个模块替换。替换时有个技巧先替换implementation行api、compileOnly、annotationProcessor等配置的替换逻辑一样但annotationProcessor在 kapt 和 KSP 下的写法不同要单独确认。// 模块级 build.gradle.kts 迁移后的写法 dependencies { implementation(libs.core.ktx) implementation(libs.appcompat) implementation(libs.bundles.network) // 一次引入 retrofit okhttp implementation(libs.glide) kapt(libs.glide.compiler) // 注解处理器单独声明 }注意libs.bundles.network这种写法要求 TOML 里[bundles]块已经定义好。迁移过程中 IDE 会标红别慌同步一次 Gradle 就好。如果某个库在 TOML 里找不到对应条目说明盘点时漏了补进去再同步。3.3 第三步配置传递依赖冲突策略直接依赖统一之后传递依赖冲突用resolutionStrategy兜底。在根build.gradle里// 根 build.gradle 中统一配置所有子项目 allprojects { configurations.all { resolutionStrategy { // 强制指定某个库的版本覆盖传递依赖带来的版本 force com.squareup.okhttp3:okhttp:4.12.0 force org.jetbrains.kotlin:kotlin-stdlib:1.9.22 // 版本冲突时直接失败而不是静默选最高版本 failOnVersionConflict() // 缓存动态版本 10 分钟避免每次构建都去查 cacheDynamicVersionsFor 10, minutes cacheChangingModulesFor 0, seconds } } }force是硬约束优先级最高但用多了会掩盖真实的版本冲突建议只对核心库用。failOnVersionConflict()是个双刃剑开了之后任何版本冲突都会让构建失败逼着你去解决但如果是第三方库之间的冲突你控制不了就会很烦。我一般在新项目上开老项目迁移时先不开等依赖清理干净了再打开。3.4 第四步验证统一管理是否生效改完之后必须验证不能靠看起来没问题。三个验证手段# 1. 检查依赖树里目标库是否只剩一个版本 ./gradlew :app:dependencies --configuration releaseRuntimeClasspath | grep okhttp # 2. 检查所有模块的依赖声明是否还有硬编码版本号 grep -rn implementation .*:.*:.*[0-9] --includebuild.gradle* . # 3. 完整构建 单元测试确认没有运行时问题 ./gradlew clean assembleDebug test第一条命令的输出里同一个库应该只出现一个版本号。第二条命令是兜底检查防止有模块漏改。第三条是最终验证编译通过不代表运行没问题单元测试能覆盖一部分运行时依赖问题。提示迁移完成后把deps-app.txt和迁移后的依赖树做一次 diff确认没有意外引入或丢失的依赖。这一步能抓到很多肉眼看不出的问题。4. 避坑指南依赖统一管理里最容易翻车的五个点4.1 现象迁移后编译通过运行时报NoSuchMethodError原因force强制了一个低版本但某个库的 API 在高版本才引入编译期用的是高版本的 API 签名运行期加载的是低版本实现。这是最典型的编译期和运行期版本不一致问题。解决用dependencyInsight确认冲突来源优先升级而不是降级。如果必须降级检查所有调用方是否用到了高版本才有的 API。force的版本应该是所有模块需求版本的并集上限不是随便选一个。4.2 现象Version Catalog 里改了版本号但构建用的还是旧版本原因Gradle 配置缓存configuration cache或者依赖缓存没刷新。Gradle 7.0 之后配置缓存默认开启TOML 文件的变更有时不会触发缓存失效。解决先./gradlew --stop停掉守护进程再./gradlew clean清构建产物必要时删掉~/.gradle/caches/modules-2/下对应的缓存目录。如果用了配置缓存在gradle.properties里临时关掉org.gradle.configuration-cachefalse排查确认是缓存问题后再打开。4.3 现象annotationProcessor或kapt的依赖在 TOML 里找不到对应写法原因注解处理器的依赖声明方式和普通依赖不同Version Catalog 对kapt、ksp、annotationProcessor的支持需要额外配置不是所有 Gradle 版本都开箱即用。解决KSP 用ksp(libs.xxx)直接引用即可kapt 在 Kotlin DSL 下需要kapt(libs.xxx)Groovy DSL 下是kapt libs.xxx注意没有括号。如果 TOML 里没有对应条目先补[libraries]条目再引用。实在不行注解处理器这部分暂时保留硬编码版本不要为了统一而统一。4.4 现象多模块项目里api和implementation混用导致依赖泄漏原因api会把依赖暴露给下游模块implementation不会。如果 A 模块用api引入了库 XB 模块依赖 A 之后也能直接用 X 的类但 B 模块的build.gradle里没有声明 X依赖树里看起来 X 是凭空出现的。解决统一管理时顺便梳理api和implementation。原则是只有模块的公开 API 签名里用到的类型才用api其余一律implementation。这个梳理比版本统一本身更有价值能显著减少模块间的隐式耦合。4.5 现象failOnVersionConflict()打开后构建大面积失败原因第三方库之间的传递依赖冲突你控制不了比如库 A 依赖 Guava 30库 B 依赖 Guava 31两者都没有直接声明但failOnVersionConflict会直接报错。解决不要全局开failOnVersionConflict改成对特定 configuration 开或者用resolutionStrategy.eachDependency做精细化处理configurations.all { resolutionStrategy.eachDependency { details - // 只对 Guava 做版本对齐其余冲突不干预 if (details.requested.group com.google.guava) { details.useVersion 32.1.3-jre } } }eachDependency比force更灵活可以按 group、name 做条件匹配适合处理只关心某几个库的场景。5. 进阶技巧用dependencyInsight和构建扫描定位隐性依赖问题统一管理做到后面真正难的不是版本号怎么写而是这个依赖到底从哪来的。dependencyInsight是排查这类问题的核心工具但很多人只会用最基础的--dependency参数。实际排查时配合--configuration和输出过滤能省很多时间# 查看 okhttp 在 releaseRuntimeClasspath 下的解析路径 ./gradlew dependencyInsight \ --dependency okhttp \ --configuration releaseRuntimeClasspath # 输出示例简化 # com.squareup.okhttp3:okhttp:4.12.0 # variant releaseRuntimeClasspath [ # ... # ] # Selection reasons: # - Was requested: 4.12.0 # - Selected by rule # \--- com.squareup.retrofit2:retrofit:2.9.0 # \--- project :app输出里的Selection reasons是关键它会告诉你这个版本是被直接请求的还是被规则选中的。如果看到Selected by rule说明有resolutionStrategy在起作用如果看到Conflict resolution说明发生了版本冲突Gradle 按默认策略选了最高版本。另一个手段是 Gradle 构建扫描build scan在settings.gradle里加一行plugins { id com.gradle.enterprise version 3.16 }构建时加--scan参数会生成一个网页报告里面能看到完整的依赖解析过程、每个依赖的下载耗时、缓存命中情况。排查为什么这个依赖被下载了这类问题时构建扫描比命令行直观得多。我自己的习惯是每次升级核心库版本之前先跑一次dependencyInsight把当前解析路径存下来升级后再跑一次做对比。这样能精确知道升级影响了哪些模块而不是靠构建过了就行来赌。从那以后我每次动依赖版本都强制走一遍dependencyInsight加完整构建再也没遇到过升级后运行时才炸的情况。希望帮到你。本文还有配套的精品资源点击获取