ChatGPT解决这个技术问题 Extra ChatGPT

Gradle 实现与 API 配置

在构建我的依赖项时,我试图弄清楚 apiimplementation 配置之间的区别。

在文档中,它说 implementation 具有更好的构建时间,但是,在类似的问题中看到这个 comment,我想知道它是否属实。

由于我不是 Gradle 方面的专家,希望有人能提供帮助。我已经阅读了 documentation,但我想知道一个易于理解的解释。

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

G
Grigory Zhadko

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

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

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官方文档中给出的解释要好得多
这是一个美丽的解释。理论和具体的完美结合。做得好。感谢那
d
dev.bmax

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

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


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

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

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

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


C
Camilo Silva

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.+'
}

implementation 编译项目生产源所需的依赖项,它们不是项目公开的 API 的一部分。例如,该项目将 Hibernate 用于其内部持久层实现。 api 编译项目生产源所需的依赖项,它们是项目公开的 API 的一部分。例如,该项目使用 Guava 并在其方法签名中公开带有 Guava 类的公共接口。


R
Rong.l

@matpag@dev-bmax 的回答很清楚,足以让人们理解 implementation 和 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]

然后我测试了以下场景:

通过 implementation 使项目 A 依赖于 'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'。在项目 B 根目录的终端中运行 gradle dependencies 命令,通过以下输出截图我们可以看到“spring-boot-gradle-plugin”出现在 runtimeClasspath 依赖关系树中,但没有出现在 compileClasspath 中,我认为这正是我们可以做到的原因不要使用声明使用实现的库,它不会通过编译。通过 api 使项目 A 依赖于 'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE' 在项目 B 根目录的终端中再次运行 gradle dependencies 命令。现在 'spring-boot-gradle-plugin' 出现在 compileClasspath 和 runtimeClasspath 依赖树中。

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


I
Islam Alshnawey

请参考链接:Android Studio Dependency Configuration 可在 android 开发者的官方网站上找到。

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

执行

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

api

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


k
kolobok

关于 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 范围。

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


Y
Yvgen

现在在 documentation 中有很好的解释

api 配置应该用于声明库 API 导出的依赖项,而 implementation 配置应该用于声明组件内部的依赖项。