在构建我的依赖项时,我试图弄清楚 api
和 implementation
配置之间的区别。
在文档中,它说 implementation
具有更好的构建时间,但是,在类似的问题中看到这个 comment,我想知道它是否属实。
由于我不是 Gradle 方面的专家,希望有人能提供帮助。我已经阅读了 documentation,但我想知道一个易于理解的解释。
compile
切换到 api
。您在内部使用的库可以使用一些未在最终库中公开的私有实现,因此它们对您是透明的。这些“内部私有”依赖项可以切换到 implementation
,当 Android gradle 插件编译您的应用程序时,它将跳过这些依赖项的编译,从而缩短构建时间(但这些依赖项将在运行时可用)。显然,如果你有本地模块库,你可以做同样的事情
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.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*)
我喜欢将 api
依赖项视为 public(其他模块可见),而将 implementation
依赖项视为 private(仅此模块可见)。
请注意,与 public
/private
变量和方法不同,api
/implementation
依赖项不是由运行时强制执行的。这只是一种构建时优化,它允许 Gradle
在其中一个依赖项更改其 API 时知道需要重新编译哪些模块。
api
依赖项放在“编译”范围内(它们将作为依赖项包含在您的库中以及任何依赖于您的库的内容中)和 implementation
依赖项在“运行时”范围内(当您的代码运行时,它们最好位于类路径上,但编译使用您的库的其他代码不需要它们)。
implementation
用于运行(以及编译您的库)所需的任何依赖项,但不应将其自动拉入使用您的库的项目中。一个例子是 jax-rs,你的库可能使用 RESTeasy,但它不应该将这些库拉入使用你的库的任何项目中,因为他们可能想要使用 Jersey。
假设您有 app
模块,它使用 lib1
作为库,而 lib1
使用 lib2
作为库。像这样的东西:app -> lib1 -> lib2
。
现在当在 lib1
中使用 api lib2
时,在 app
模块中使用 api lib1
或 implementation lib1
时,app
可以看到 lib2
代码。
但是在 lib1
中使用 implementation lib2
时,app
看不到 lib2
代码。
让我们看一个非常简单的基于 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 类的公共接口。
@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中,因此我们无法在消费者项目中使用相应的库。
请参考链接:Android Studio Dependency Configuration 可在 android 开发者的官方网站上找到。
在依赖项块中,您可以使用几种不同的依赖项配置之一声明库依赖项(例如上面显示的实现)。每个依赖项配置都为 Gradle 提供了有关如何使用依赖项的不同说明。
执行
Gradle 将依赖项添加到编译类路径并将依赖项打包到构建输出。但是,当您的模块配置实现依赖项时,它会让 Gradle 知道您不希望该模块在编译时将依赖项泄漏给其他模块。也就是说,依赖项仅在运行时对其他模块可用。使用此依赖配置而不是 api 或 compile(已弃用)可以显着缩短构建时间,因为它减少了构建系统需要重新编译的模块数量。例如,如果一个实现依赖改变了它的 API,Gradle 只会重新编译那个依赖和直接依赖它的模块。大多数应用程序和测试模块都应使用此配置。
api
Gradle 将依赖项添加到编译类路径和构建输出。当一个模块包含一个 api 依赖项时,它让 Gradle 知道该模块希望将该依赖项传递到其他模块,以便它们在运行时和编译时都可以使用它。此配置的行为与 compile 类似(现已弃用),但您应谨慎使用它,并且仅与需要传递到其他上游使用者的依赖项一起使用。这是因为,如果 api 依赖项更改了其外部 API,Gradle 会在编译时重新编译所有有权访问该依赖项的模块。因此,拥有大量 api 依赖项会显着增加构建时间。除非您想将依赖项的 API 公开给单独的模块,否则库模块应改为使用实现依赖项。
关于 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
范围。
这允许该库的使用者避免在其编译类路径中存在运行时依赖项。
MyLibrary#myString()
将崩溃,因为 ProGuard 将删除InternalLibrary
。在 ProGuard 的应用程序中使用 android-libs 的最佳实践是什么?