Gradle 实现与 API 配置

Posted

技术标签:

【中文标题】Gradle 实现与 API 配置【英文标题】:Gradle Implementation vs API configuration 【发布时间】:2017-10-29 03:17:06 【问题描述】:

在构建我的依赖项时,我试图弄清楚 apiimplementation 配置之间的区别。 在文档中,它说implementation 有更好的构建时间,但是, 在类似的问题中看到这个comment,我想知道这是不是真的。 由于我不是 Gradle 方面的专家,希望有人能提供帮助。我已经阅读了documentation,但我想知道一个易于理解的解释。

【问题讨论】:

你读过here吗? 事实上,我确实做到了,但正如我所说,那条评论让人感到奇怪。所以我现在有点迷路了 你可能会将你的库依赖从compile切换到api。您在内部使用的库可以使用一些未在最终库中公开的私有实现,因此它们对您是透明的。那些“内部私有”依赖可以切换到implementation,当 android gradle 插件编译你的应用程序时,它会跳过这些依赖的编译,从而缩短构建时间(但这些依赖将在运行时可用)。显然,如果你有本地模块库,你可以做同样的事情 这里是“api”和“实现”的简短图形解释:jeroenmols.com/blog/2017/06/14/androidstudio3 这是一个很棒的帖子!谢谢@albertbraun 【参考方案1】:

Gradle compile 关键字已被弃用,取而代之的是 apiimplementation 关键字来配置依赖项。

使用api 等同于使用已弃用的compile,因此如果将所有compile 替换为api,一切都会照常运行。

要理解 implementation 关键字,请考虑以下示例。

示例

假设您有一个名为 MyLibrary 的库,它在内部使用另一个名为 InternalLibrary 的库。像这样的:

// 'InternalLibrary' module
public class InternalLibrary 
    public static String giveMeAString()
        return "hello";
    

// 'MyLibrary' module
public class MyLibrary 
    public String myString()
        return InternalLibrary.giveMeAString();
    

假设MyLibrary build.gradledependencies 中使用api 配置,如下所示:

dependencies 
    api project(':InternalLibrary')

您想在您的代码中使用MyLibrary,因此在您的应用程序的build.gradle 中添加此依赖项:

dependencies 
    implementation project(':MyLibrary')

使用api 配置(或已弃用的compile),您可以在应用程序代码中访问InternalLibrary

// Access 'MyLibrary' (granted)
MyLibrary myLib = new MyLibrary();
System.out.println(myLib.myString());

// Can ALSO access the internal library too (but you shouldn't)
System.out.println(InternalLibrary.giveMeAString());

以这种方式,模块MyLibrary 可能会“泄漏”某些东西的内部实现。您不应该(能够)使用它,因为它不是您直接导入的。

引入了implementation 配置来防止这种情况。 所以现在如果你在MyLibrary 中使用implementation 而不是api

dependencies 
    implementation project(':InternalLibrary')

您将无法再在您的应用代码中调用InternalLibrary.giveMeAString()

这种装箱策略让 Android Gradle 插件知道,如果您在 InternalLibrary 中编辑某些内容,它必须只触发重新编译 MyLibrary而不是重新编译整个应用程序,因为你无权访问InternalLibrary

当您有很多嵌套依赖项时,这种机制可以大大加快构建速度。 (观看最后链接的视频以全面了解这一点)

结论

当您切换到新的 Android Gradle 插件 3.X.X 时,您应该将所有 compile 替换为 implementation 关键字 *(1)。然后尝试编译和测试您的应用程序。如果一切正常,请保持代码不变,如果您遇到问题,您的依赖项可能有问题,或者您使用了现在私有且不易访问的东西。 *Android Gradle 插件工程师 Jerome Dochez 的建议(1))

如果您是库管理员,您应该对库的公共 API 所需的每个依赖项使用 api,而对最终用户不得使用的测试依赖项或依赖项使用 implementation

Useful article 展示实现api

的区别

参考文献 (这是为节省时间而拆分的同一视频)

Google I/O 2017 - How speed up Gradle builds (FULL VIDEO)

Google I/O 2017 - How speed up Gradle builds (NEW GRADLE PLUGIN 3.0.0 PART ONLY)

Google I/O 2017 - How speed up Gradle builds (reference to 1*)

Android documentation

【讨论】:

我注意到 api 在库模块中似乎不能很好地工作。如果我使用它,我仍然无法从我的应用程序项目中访问依赖项。我只能访问该库本身的代码。 这很好,适用于调试版本,但在使用 ProGuard(在发布版本上)时,MyLibrary#myString() 将崩溃,因为 ProGuard 将删除 InternalLibrary。在 ProGuard 应用程序中使用 android-libs 的最佳实践是什么? 我认为答案不准确,应用程序可以为 MyLibrary 使用它想要的任何范围。它会根据 MyLibrary 是否使用 api / implementation 来查看 InternalLibrary。 谢谢伙计。很棒的解释,比android官方文档中给出的解释要好得多 这是一个很好的解释。理论和具体的完美结合。做得好。谢谢你【参考方案2】:

我喜欢将api 依赖项视为public(其他模块可见),而implementation 依赖项视为private(仅本模块可见)。

请注意,与public/private 变量和方法不同,api/implementation 依赖项不是由运行时强制执行的。这只是一个构建时优化,它允许Gradle 在其中一个依赖项更改其 API 时知道需要重新编译哪些模块。

【讨论】:

真正的区别(AFAICT)是生成的 pom 文件将 api 依赖项放在“编译”范围内(它们将作为依赖项包含在您的库中以及依赖于您的库的任何内容中)和 @ 987654329@“运行时”范围内的依赖项(当您的代码运行时,它们最好位于类路径上,但编译使用您的库的其他代码不需要它们)。 @ShadowMan 是插件的一个实现细节,负责生成POM文件,如何将Gradle作用域映射到Maven作用域。跨度> 您应该将implementation 用于运行(以及编译您的库)所需的任何依赖项,但不应将其自动拉入使用您的库的项目中。一个例子是 jax-rs,你的库可能使用 RESTeasy,但它不应该将这些库拉入任何使用你的库的项目中,因为他们可能想要使用 Jersey。【参考方案3】:

假设您有 app 模块,它使用 lib1 作为库,lib1 使用 lib2 作为库。像这样的东西:app -> lib1 -> lib2

现在当在lib1 中使用api lib2 时,那么app 可以看到 lib2 代码在api lib1app 模块中使用时:api lib1implementation lib1

但是当在lib1 中使用implementation lib2 时,则app 看不到 lib2 代码。

【讨论】:

【参考方案4】:

来自gradle documentation:

让我们看看一个非常简单的基于 JVM 项目的构建脚本。

plugins 
    id 'java-library'


repositories 
    mavenCentral()


dependencies 
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
    api 'com.google.guava:guava:23.0'
    testImplementation 'junit:junit:4.+'

实施

编译项目的生产源所需的依赖项,这些依赖项不属于项目公开的 API 的一部分。例如,该项目使用 Hibernate 作为其内部持久层实现。

api

编译项目生产源所需的依赖项,它们是项目公开的 API 的一部分。例如,该项目使用 Guava 并在其方法签名中公开带有 Guava 类的公共接口。

【讨论】:

【参考方案5】:

@matpag 和@dev-bmax 的回答非常清晰,足以让人们理解实现和 api 之间的不同用法。我只是想从另一个角度做一个额外的解释,希望对有同样问题的人有所帮助。

我创建了两个测试项目:

项目 A 作为名为 'frameworks-web-gradle-plugin' 的 java 库项目依赖于 'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE' 项目 B 通过实现 'com.example.frameworks.gradle:frameworks-web-gradle-plugin:0.0.1-SNAPSHOT' 依赖于项目 A

上面描述的依赖层次结构如下:

[project-b] -> [project-a] -> [spring-boot-gradle-plugin]

然后我测试了以下场景:

    通过实现使项目A依赖于'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'。

    在项目B根目录的终端中运行gradle dependencies命令,通过以下输出截图我们可以看到'spring-boot-gradle-plugin'出现在runtimeClasspath依赖树中,但没有出现在compileClasspath中,我想这正是为什么我们不能使用声明为 using 实现的库,它只是不会通过编译。

    使项目 A 依赖于 api 的 'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'

    再次在项目 B 根目录的终端中运行 gradle dependencies 命令。 现在 'spring-boot-gradle-plugin' 同时出现在 compileClasspath 和 runtimeClasspath 依赖树中。

我注意到一个显着的区别是,生产者/库项目中以实现方式声明的依赖不会出现在消费者项目的compileClasspath中,因此我们无法在消费者项目中使用相应的lib。

【讨论】:

【参考方案6】:

关于apiimplementation 的更多技术说明。假设你有以下依赖:

dependencies 
  api "com.example:foo:1.0"
  implementation "com.example:bar:1.0"

如果您在本地 Maven 存储库中安装生成的 jar 文件(借助 maven-publish 插件),您将看到生成的 pom.xml 文件如下所示:

    <dependency>
      <groupId>com.example</groupId>
      <artifactId>foo</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>bar</artifactId>
      <version>1.0</version>
      <scope>runtime</scope>
    </dependency>

注意:api 已转换为 compile 范围,implementation - 已转换为 runtime 范围。

这允许该库的使用者避免在其编译类路径中存在运行时依赖项。

【讨论】:

【参考方案7】:

现在documentation有很好的解释

应该使用 api 配置来声明依赖项,这些依赖项是 由库 API 导出,而实现配置 应该用于声明内部的依赖项 组件。

【讨论】:

【参考方案8】:

请参考链接:Android Studio Dependency Configuration 可在安卓开发者官网获取。

在依赖项块中,您可以使用几种不同的依赖项配置之一声明库依赖项(例如上面显示的实现)。每个依赖配置都为 Gradle 提供了关于如何使用依赖的不同说明。

实现

Gradle 将依赖项添加到编译类路径并将依赖项打包到构建输出。但是,当您的模块配置实现依赖项时,它会让 Gradle 知道您不希望该模块在编译时将依赖项泄漏给其他模块。也就是说,依赖项仅在运行时对其他模块可用。 使用此依赖配置而不是 api 或 compile(已弃用)可以显着缩短构建时间,因为它减少了构建系统需要重新编译的模块数量。例如,如果一个实现依赖改变了它的 API,Gradle 只会重新编译那个依赖和直接依赖它的模块。大多数应用和测试模块都应该使用这种配置。

api

Gradle 将依赖项添加到编译类路径和构建输出。当一个模块包含一个 api 依赖项时,它让 Gradle 知道该模块想要将该依赖项传递到其他模块,以便它们在运行时和编译时都可以使用它。 此配置的行为与 compile 类似(现已弃用),但您应谨慎使用它,并且仅与需要传递到其他上游使用者的依赖项一起使用。这是因为,如果 api 依赖项更改了其外部 API,Gradle 会在编译时重新编译所有有权访问该依赖项的模块。因此,拥有大量 api 依赖项会显着增加构建时间。除非您想将依赖项的 API 公开给单独的模块,否则库模块应改为使用实现依赖项。

【讨论】:

以上是关于Gradle 实现与 API 配置的主要内容,如果未能解决你的问题,请参考以下文章

在 Gradle 中,api 配置暴露了依赖关系,而实现却没有,这到底意味着啥?

Gradle 与 AGP 构建 API: 配置您的构建文件

Gradle 与 AGP 构建 API: 配置您的构建文件

Gradle 与 AGP 构建 API: 如何编写插件

GroovyGradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )

使用Gradle与Ant实现可配置不同环境的自动打包