gradle的api implementation compileOnly有什么区别?

小豆丁 24天前 ⋅ 10 阅读

gradle 的api implementation compileOnly有什么区别?


在 Gradle 的依赖配置中,implementationapicompileOnly 是三个常用的选项,它们的主要区别在于依赖的作用范围(编译时、运行时)依赖的传递性。理解它们对于构建效率、避免依赖冲突和减小应用包体积至关重要。

以下是它们的详细区别:

  1. implementation (推荐使用)

    • 作用范围:
      • 编译时: 依赖对当前模块是可见的,当前模块的代码可以引用依赖中的类。
      • 运行时: 依赖会被打包到当前模块的输出(如 JAR、AAR、APK)中。
    • 传递性:
      • 不传递: 当前模块的 implementation 依赖不会暴露给依赖当前模块的其他模块。其他模块在编译时无法访问这个依赖。
    • 优点:
      • 构建加速:implementation 依赖发生变化时,Gradle 只需要重新编译当前模块和直接依赖它的模块。依赖它的模块的依赖项不需要重新编译,因为那些模块在编译时根本“看不到”这个 implementation 依赖。
      • 减少泄漏: 避免无意中将内部依赖暴露出去,防止依赖冲突和意外的 API 使用。
    • 使用场景: 绝大多数情况下都应该使用 implementation,尤其是对于模块内部的实现细节依赖。例如,一个网络库模块内部使用的 JSON 解析库就应该用 implementation
  2. api (原名 compile,已废弃)

    • 作用范围:
      • 编译时: 依赖对当前模块是可见的,当前模块的代码可以引用依赖中的类。
      • 运行时: 依赖会被打包到当前模块的输出中。
    • 传递性:
      • 传递: 当前模块的 api 依赖暴露给依赖当前模块的其他模块。其他模块在编译时和运行时都能访问这个依赖,就像它们自己声明了这个依赖一样。
    • 缺点:
      • 构建变慢:api 依赖发生变化时,Gradle 需要重新编译当前模块、所有直接或间接依赖当前模块的模块(因为它们都“看到”了这个变化的依赖)。这可能导致大型项目的构建时间显著增加。
      • 增加泄漏风险: 容易将模块的内部实现依赖无意中暴露为公共 API 的一部分,可能导致上游模块的依赖冲突。
    • 使用场景: 只有当依赖项是当前模块公开 API 的一部分时才使用 api。也就是说,如果当前模块的类在其公共方法签名、公共字段或继承/实现的公共接口/类中使用了该依赖项的类型,那么该依赖项就是模块 API 的一部分。例如,你的模块是一个提供 User 对象的库,而 User 类实现了某个接口库中的 Serializable 接口,那么那个接口库就应该用 api 声明。
  3. compileOnly

    • 作用范围:
      • 编译时: 依赖在编译时对当前模块可见。当前模块的代码在编译时可以引用依赖中的类。
      • 运行时: 依赖不会被打包到当前模块的输出中。运行时环境中必须已经存在该依赖(通常由容器、平台或其他依赖提供)。
    • 传递性:
      • 不传递: 不会传递给任何其他模块。
    • 使用场景:
      • 仅需编译时支持的库: 依赖项只在编译时需要,运行时环境会提供它。最常见的是 Servlet API。你写 Web 应用时需要它来编译,但最终的 WAR 包会部署到 Tomcat/Jetty 等容器中,这些容器已经包含了 Servlet API 的实现,所以你不需要把 API 本身打包进 WAR。
      • 注解处理器 (Annotation Processors): 像 Lombok 或 Dagger 这样的库,它们在编译时生成代码,但生成的代码运行时并不需要这些库本身。通常使用 annotationProcessor 配置更精确,但 compileOnly 有时也用于此目的(尤其是在 Android 或旧项目中)。
      • 替代 provided (旧配置): 在 Android 和某些旧项目中,compileOnly 取代了 provided 配置的功能。

总结表:

配置 编译时可见 运行时包含 传递性 主要目的/场景 对构建性能影响
implementation ✅ 当前模块 模块内部实现依赖。推荐默认使用。 ✅ 好 (局部)
api ✅ 当前模块 模块公开 API 的一部分。谨慎使用。 ❌ 差 (全局)
compileOnly ✅ 当前模块 仅在编译时需要,运行时由环境提供或不需要。 -

关键建议:

  1. 默认使用 implementation 这是最安全、对构建性能最友好的选择。
  2. 谨慎使用 api 只在明确需要将依赖作为模块公共 API 的一部分暴露出去时才使用。滥用 api 会导致构建变慢和依赖地狱。
  3. 理解 compileOnly 的用途: 用于那些编译时需要但运行时不需要或不应该打包的依赖。
  4. 检查依赖树: 使用 gradle :<module-name>:dependencies 命令查看依赖树,理解依赖是如何传递的,有助于发现配置错误。

通过正确选择依赖配置,你可以构建出更健壮、更高效且更容易维护的项目。

全部评论: 0

    我有话说: