Gradle 实施与 API 配置
Gradle Implementation vs API configuration
我在构建 dependencies.
时试图找出 api
和 implementation
配置之间的区别
在文档中,它说 implementation
有更好的构建时间,但是,在类似的问题中看到这个 comment 我开始怀疑这是不是真的。
由于我不是Gradle方面的专家,希望有人能提供帮助。我已经阅读了 documentation 但我想知道一个易于理解的解释。
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))
如果您是图书馆管理员,您应该为图书馆的 public 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*)
我喜欢将 api
依赖视为 public(被其他模块看到),而 implementation
依赖视为 private(仅本模块可见)
请注意,与 public
/private
变量和方法不同,api
/implementation
依赖项不会由运行时强制执行。这仅仅是一个构建时优化,它允许 Gradle
知道当其中一个依赖项更改其 API.
时需要重新编译哪些模块
假设您有 app
模块,它使用 lib1
作为库,lib1
使用 lib2
作为库。像这样:app -> lib1 -> lib2
.
现在在lib1
中使用api lib2
,那么app
可以看到 lib2
使用时的代码:api lib1
或 implementation lib1
在 app
模块中。
但是当在 lib1
中使用 implementation lib2
时,app
看不到 lib2
代码。
@matpag and @dev-bmax 的回答足够清楚,可以让人们理解实现和 api 之间的不同用法。我只是想从另一个角度做一个额外的解释,希望对有同样问题的人有所帮助。
我创建了两个项目用于测试:
- 项目 A 作为名为 '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]
然后我测试了以下场景:
通过实现使项目A依赖于'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'。
运行 gradle dependencies
命令在项目 B 根目录的终端中,通过以下输出截图我们可以看到 'spring-boot-gradle-plugin' 出现在 runtimeClasspath 依赖树中,但没有出现在 compileClasspath 中,我认为这正是我们不能使用使用实现声明的库的原因,它不会通过编译。
通过api
使项目A依赖于'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'
运行 gradle dependencies
再次在项目 B 根目录中的终端中执行命令。
现在 'spring-boot-gradle-plugin' 同时出现在 compileClasspath 和 runtimeClasspath 依赖关系树中。
我注意到的一个显着差异是producer/library项目中以实现方式声明的依赖不会出现在消费者项目的compileClasspath中,因此我们无法在消费者项目中使用相应的库.
让我们看一下基于 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
The dependencies required to compile the production source of the project which are not part of the API exposed by the project. For example the project uses Hibernate for its internal persistence layer implementation.
api
The dependencies required to compile the production source of the project which are part of the API exposed by the project. For example the project uses Guava and exposes public interfaces with Guava classes in their method signatures.
中有很好的解释
The api configuration should be used to declare dependencies which are
exported by the library API, whereas the implementation configuration
should be used to declare dependencies which are internal to the
component.
关于 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
范围。
这允许此库的使用者避免在其编译类路径中存在运行时依赖项。
请参阅 link:Android Studio Dependency Configuration 可在 android 开发者的官方网站上找到。
在 dependencies 块中,您可以使用几种不同的依赖配置之一(例如上面显示的实现)来声明库依赖。每个依赖项配置都提供 Gradle 关于如何使用依赖项的不同说明。
实施
Gradle 将依赖项添加到编译类路径并将依赖项打包到构建输出。但是,当您的模块配置实现依赖项时,它会让 Gradle 知道您不希望模块在编译时将依赖项泄露给其他模块。也就是说,依赖项仅在运行时对其他模块可用。
使用此依赖项配置而不是 api 或编译(已弃用)可以显着缩短构建时间,因为它减少了构建系统需要重新编译的模块数量。例如,如果一个实现依赖改变了它的 API,Gradle 只重新编译那个依赖和直接依赖它的模块。大多数应用和测试模块应使用此配置。
api
Gradle 将依赖项添加到编译类路径和构建输出。当一个模块包含 api 依赖项时,它会让 Gradle 知道该模块想要将该依赖项传递给其他模块,以便它们在运行时和编译时都可用。
此配置的行为就像编译(现已弃用)一样,但您应该谨慎使用它,并且仅在需要传递到其他上游消费者的依赖项中使用。这是因为,如果 api 依赖项更改其外部 API,Gradle 会重新编译在编译时有权访问该依赖项的所有模块。因此,拥有大量 api 依赖项会显着增加构建时间。除非您想将依赖项的 API 暴露给单独的模块,否则库模块应该使用实现依赖项。
我在构建 dependencies.
时试图找出api
和 implementation
配置之间的区别
在文档中,它说 implementation
有更好的构建时间,但是,在类似的问题中看到这个 comment 我开始怀疑这是不是真的。
由于我不是Gradle方面的专家,希望有人能提供帮助。我已经阅读了 documentation 但我想知道一个易于理解的解释。
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))如果您是图书馆管理员,您应该为图书馆的 public 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*)
我喜欢将 api
依赖视为 public(被其他模块看到),而 implementation
依赖视为 private(仅本模块可见)
请注意,与 public
/private
变量和方法不同,api
/implementation
依赖项不会由运行时强制执行。这仅仅是一个构建时优化,它允许 Gradle
知道当其中一个依赖项更改其 API.
假设您有 app
模块,它使用 lib1
作为库,lib1
使用 lib2
作为库。像这样:app -> lib1 -> lib2
.
现在在lib1
中使用api lib2
,那么app
可以看到 lib2
使用时的代码:api lib1
或 implementation lib1
在 app
模块中。
但是当在 lib1
中使用 implementation lib2
时,app
看不到 lib2
代码。
@matpag and @dev-bmax 的回答足够清楚,可以让人们理解实现和 api 之间的不同用法。我只是想从另一个角度做一个额外的解释,希望对有同样问题的人有所帮助。
我创建了两个项目用于测试:
- 项目 A 作为名为 '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]
然后我测试了以下场景:
通过实现使项目A依赖于'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'。
运行
gradle dependencies
命令在项目 B 根目录的终端中,通过以下输出截图我们可以看到 'spring-boot-gradle-plugin' 出现在 runtimeClasspath 依赖树中,但没有出现在 compileClasspath 中,我认为这正是我们不能使用使用实现声明的库的原因,它不会通过编译。通过api
使项目A依赖于'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'运行
gradle dependencies
再次在项目 B 根目录中的终端中执行命令。 现在 'spring-boot-gradle-plugin' 同时出现在 compileClasspath 和 runtimeClasspath 依赖关系树中。
我注意到的一个显着差异是producer/library项目中以实现方式声明的依赖不会出现在消费者项目的compileClasspath中,因此我们无法在消费者项目中使用相应的库.
让我们看一下基于 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
The dependencies required to compile the production source of the project which are not part of the API exposed by the project. For example the project uses Hibernate for its internal persistence layer implementation.
api
The dependencies required to compile the production source of the project which are part of the API exposed by the project. For example the project uses Guava and exposes public interfaces with Guava classes in their method signatures.
The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.
关于 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
范围。
这允许此库的使用者避免在其编译类路径中存在运行时依赖项。
请参阅 link:Android Studio Dependency Configuration 可在 android 开发者的官方网站上找到。
在 dependencies 块中,您可以使用几种不同的依赖配置之一(例如上面显示的实现)来声明库依赖。每个依赖项配置都提供 Gradle 关于如何使用依赖项的不同说明。
实施
Gradle 将依赖项添加到编译类路径并将依赖项打包到构建输出。但是,当您的模块配置实现依赖项时,它会让 Gradle 知道您不希望模块在编译时将依赖项泄露给其他模块。也就是说,依赖项仅在运行时对其他模块可用。 使用此依赖项配置而不是 api 或编译(已弃用)可以显着缩短构建时间,因为它减少了构建系统需要重新编译的模块数量。例如,如果一个实现依赖改变了它的 API,Gradle 只重新编译那个依赖和直接依赖它的模块。大多数应用和测试模块应使用此配置。
api
Gradle 将依赖项添加到编译类路径和构建输出。当一个模块包含 api 依赖项时,它会让 Gradle 知道该模块想要将该依赖项传递给其他模块,以便它们在运行时和编译时都可用。 此配置的行为就像编译(现已弃用)一样,但您应该谨慎使用它,并且仅在需要传递到其他上游消费者的依赖项中使用。这是因为,如果 api 依赖项更改其外部 API,Gradle 会重新编译在编译时有权访问该依赖项的所有模块。因此,拥有大量 api 依赖项会显着增加构建时间。除非您想将依赖项的 API 暴露给单独的模块,否则库模块应该使用实现依赖项。