运行时依赖不能只看implementation,因为implementation仅控制编译期可见性与传递性,而实际打包进APK的类由runtimeClasspath配置决定,它包含最终参与dex生成、资源合并和运行期加载的所有JAR/AAR,且受构建变体、版本仲裁、force规则等动态影响。

运行时依赖为什么不能只看 implementation?
因为 implementation 声明的依赖默认不会传递给下游模块,但真正打包进 APK 的类,是那些在 runtimeClasspath 配置中实际解析出来的——它包含所有最终参与 dex 生成、资源合并和运行期加载的 JAR/AAR。比如你写了 api 'com.squareup.okhttp3:okhttp:4.12.0',但它被另一个库用 compileOnly 引入,就不会出现在运行时依赖里。
./gradlew :app:dependencies --configuration runtimeClasspath 是最准的
这条命令输出的就是构建时实际参与打包的依赖集合,不含编译期-only 的内容,也不含测试相关路径。关键点:
- 必须指定模块名(如
:app),多模块项目不写会报错 -
runtimeClasspath是 Gradle 通用配置名,Android 插件下它等价于releaseRuntimeClasspath或debugRuntimeClasspath,但不加 build variant 时默认取当前 active build - 输出里
->表示版本被替换,(*)表示该条依赖已被前面某处提供,不再重复拉取 - 如果项目用了 flavor(如
devRelease),得显式写成--configuration devReleaseRuntimeClasspath
导出到文件后怎么快速定位问题?
直接终端刷屏容易漏信息,尤其当项目有 200+ 依赖时。推荐:
- 导出:
./gradlew :app:dependencies --configuration runtimeClasspath > deps.txt - 搜索冲突库:
grep -A5 -B5 "okhttp" deps.txt(显示匹配行及前后 5 行) - 过滤掉传递依赖中的冗余路径:
grep -v "^s*\+---.**" deps.txt | grep -E "(com.|androidx.|org.)" - 注意:不要依赖
androidDependencies任务,它已过时且输出结构混乱,Gradle 8.x 起基本不可靠
为什么 dependencyInsight 比树形输出更实用?
当你已经知道某个库(比如 com.google.guava:guava)有多个版本共存,dependencies 树只能让你“找”,而 dependencyInsight 直接告诉你“谁在用、为什么留、哪个赢了”:
- 命令:
./gradlew :app:dependencyInsight --dependency com.google.guava:guava --configuration runtimeClasspath - 输出中会明确标出
selected by rule(仲裁规则)和declared by(声明位置) - 如果看到某条路径标记为
forced,说明你在build.gradle里写了force = true,这可能掩盖真实冲突 - Android Studio 的图形化依赖图(右键 → Show Dependencies)底层调用的就是这个逻辑,但命令行输出更完整、无折叠
真正容易被忽略的是 build variant 和 flavor 的组合态——runtimeClasspath 不是静态配置,它随当前构建变体动态生成。没切对 variant 就执行命令,看到的可能是 debug 环境的依赖,而线上 crash 却发生在 release 包里。


















