compileOnly仅用于编译期需类但运行时由宿主提供(如注解、SDK接口),误用于new实例或直接调用会导致NoClassDefFoundError;其安全前提为所有引用均通过反射+兜底逻辑处理。

compileOnly 什么时候用,又为什么容易崩溃
当你写的模块(比如一个 SDK 或工具库)在编译时需要某个类(比如 javax.annotation.Nullable 或 androidx.annotation.NonNull),但运行时完全不依赖它——或者更关键的是:**这个类由宿主 App 在运行时提供**,才该用 compileOnly。
常见误用是把 compileOnly 当成“轻量版 implementation”,比如给一个网络库加 compileOnly 'com.squareup.okhttp3:okhttp:4.12.0',结果一运行就抛 NoClassDefFoundError。因为 Gradle 根本没打包 OkHttp,而你的代码又直接 new 了 OkHttpClient,这属于编译可见、运行不可见的硬伤。
- ✅ 正确场景:写一个注解处理器兼容库,依赖
com.google.auto.service:auto-service:1.1.1,但只在编译期生成代码,不参与运行 - ✅ 正确场景:Android 库中引用
androidx.lifecycle:lifecycle-common:2.7.0的注解,但生命周期逻辑由 App 自己引入实现 - ❌ 错误场景:任何你代码里显式调用、new 实例、或反射加载的类,只要没被宿主 App 显式声明为 runtime 依赖,就不能靠
compileOnly
runtimeOnly 基本没人真用,除非你明确控制 classpath
runtimeOnly 表示:编译时看不到这个依赖,但会打进 APK/AAR/JAR 里,留给运行时加载。听起来像“隐藏依赖”,但实际落地极难——因为你无法在编译期引用它的任何类,连 import 都会报错。
这意味着你只能通过反射、SPI、或 ClassLoader.loadClass 动态加载它。而 Android 上 runtimeOnly 打包进 APK 后,ART 运行时默认能加载;但在 Java Web 或 Spring Boot 场景下,如果没手动把它加进 ClassLoader 或 spring.factories,照样找不到类。
- ⚠️ 它不是“懒加载开关”,而是彻底切断编译期链接能力
- ⚠️ 它和
apk(已弃用配置)行为一致,Gradle 官方文档里几乎不提使用案例 - ? 极少数可行场景:插件化框架中,主壳工程预埋一个空接口,插件模块用
runtimeOnly提供具体实现 JAR,再由壳动态加载
implementation 和 api 才是日常主力,compileOnly/runtimeOnly 是边界特例
绝大多数业务模块、App 模块、甚至普通 Library,都不该优先考虑 compileOnly 或 runtimeOnly。它们的存在意义是解决特定耦合问题,不是通用替代方案。
比如你写一个封装了 Gson 的 JSON 工具类,内部用了 Gson 和 TypeToken,那必须用 implementation 'com.google.code.gson:gson:2.10.1'——哪怕下游模块也想用 Gson,也应该自己声明,而不是靠你“泄露”过去。
- 用
api的唯一合理理由:你暴露的 public 方法签名里直接用了该依赖的类型(例如返回Call,就必须api 'com.squareup.retrofit2:retrofit:2.9.0') -
compileOnly的安全前提:你所有对该依赖的引用都包裹在try/catch (ClassNotFoundException)或Class.forName(...).isPresent()里,并有 fallback 行为 -
runtimeOnly的安全前提:你从不 import、不 new、不写泛型约束、不参与编译期类型推导——只靠字符串类名 + 反射活下来
最容易被忽略的点:transitive 和 exclude 的组合影响
compileOnly 和 runtimeOnly 都默认关闭传递依赖(transitive = false)。但如果你手动写了 compileOnly('xxx') { transitive = true },就可能意外带入一堆编译期不需要、运行时又缺失的间接依赖,导致构建成功但运行失败。
反过来,runtimeOnly 如果依赖了一个带 native 库的包(比如 net.java.dev.jna:jna:5.13.0),它虽然打进 APK,但 Android 不支持 JNA 的 so 加载机制,照样会 UnsatisfiedLinkError。
所以选配置之前,先跑一遍 ./gradlew app:dependencies --configuration runtimeOnlyClasspath 或 compileOnlyClasspath,确认实际解析出的依赖树是否符合预期——这才是真正卡住问题的地方。











