Gradle 实现与 API 配置
Posted
技术标签:
【中文标题】Gradle 实现与 API 配置【英文标题】:Gradle Implementation vs API configuration 【发布时间】:2017-10-29 03:17:06 【问题描述】:在构建我的依赖项时,我试图弄清楚 api
和 implementation
配置之间的区别。
在文档中,它说implementation
有更好的构建时间,但是,
在类似的问题中看到这个comment,我想知道这是不是真的。
由于我不是 Gradle 方面的专家,希望有人能提供帮助。我已经阅读了documentation,但我想知道一个易于理解的解释。
【问题讨论】:
你读过here吗? 事实上,我确实做到了,但正如我所说,那条评论让人感到奇怪。所以我现在有点迷路了 你可能会将你的库依赖从compile
切换到api
。您在内部使用的库可以使用一些未在最终库中公开的私有实现,因此它们对您是透明的。那些“内部私有”依赖可以切换到implementation
,当 android gradle 插件编译你的应用程序时,它会跳过这些依赖的编译,从而缩短构建时间(但这些依赖将在运行时可用)。显然,如果你有本地模块库,你可以做同样的事情
这里是“api”和“实现”的简短图形解释:jeroenmols.com/blog/2017/06/14/androidstudio3
这是一个很棒的帖子!谢谢@albertbraun
【参考方案1】:
Gradle compile
关键字已被弃用,取而代之的是 api
和 implementation
关键字来配置依赖项。
使用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.gradle
在dependencies
中使用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 lib1
或app
模块中使用时:api lib1
或implementation 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】:关于api
与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
范围。
这允许该库的使用者避免在其编译类路径中存在运行时依赖项。
【讨论】:
【参考方案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 配置暴露了依赖关系,而实现却没有,这到底意味着啥?
GroovyGradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )