Gradle实现vs API配置

我试图弄清楚在构建我的依赖关系时,apiimplementation配置之间有什么区别。

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

因为我不是Gradle的专家,我希望有人能帮助我。我已经读了文档,但我想知道一个容易理解的解释。

164215 次浏览

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,你应该用implementation关键字*(1)替换你所有的compile。然后试着编译和测试你的应用程序。如果一切正常,保持代码原样,如果你有问题,你可能是你的依赖关系有问题,或者你使用了一些现在是私有的,不太容易访问的东西。* Android Gradle插件工程师Jerome Dochez的建议(1))

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

有用的文章显示实现api之间的差异

< >强引用 (为了节省时间,这是同一段视频)

谷歌I/O 2017 -如何加快Gradle构建(完整视频)

< a href = " https://youtu.be/7ll-rkLCtyk?t=22m21s" rel="noreferrer">谷歌I/O 2017 -如何加速Gradle构建(新Gradle插件3.0.0部分ONLY) .

< a href = " https://youtu.be/7ll-rkLCtyk?t=30m37s" rel="noreferrer">谷歌I/O 2017 -如何加快Gradle构建(参考1*)

Android文档

我喜欢将api依赖项视为公共(其他模块可以看到),而将implementation依赖项视为私人(仅本模块可以看到)。

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

假设你有app模块,它使用lib1作为库,而lib1使用lib2作为库。类似这样:app -> lib1 -> lib2

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

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

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

我创建了两个项目进行测试:

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

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

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

然后我测试了以下场景:

  1. 使项目A依赖于“org.springframework.boot:spring-boot-gradle-plugin:1.5.20”。释放` 实现

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

    enter image description here < / p >

  2. 使项目A依赖于“org.springframework.boot:spring-boot-gradle-plugin:1.5.20”。释放` api

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

    enter image description here < / p >

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

gradle文档:

让我们看一下基于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类的公共接口。

现在在文档中有很好的解释

api配置应该用来声明依赖关系 由库API导出,而实现配置 应用于声明依赖项,这些依赖项是 组件。< / p >

关于api vs implementation还有一个技术注意事项。假设你有以下依赖项:

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作用域。

这允许这个库的使用者避免在他们的编译类路径中有运行时依赖项。

请参考android开发者官方网站上的链接:Android Studio依赖配置

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

实现

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

api

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