Gradle中的实现、api和编译有什么区别?

在更新到Android Studio 3.0并创建一个新项目后,我注意到在build.gradle中有一种添加新依赖项的新方法,而不是compile,有implementation,而不是testCompiletestImplementation

示例:

 implementation 'com.android.support:appcompat-v7:25.0.0'testImplementation 'junit:junit:4.12'

而不是

 compile 'com.android.support:appcompat-v7:25.0.0'testCompile 'junit:junit:4.12'

它们之间有什么区别,我应该使用什么?

476923 次浏览

tl; dr

只需替换:

  • compileimplementation(如果你不需要传递性)或api(如果你需要传递性)
  • testCompiletestImplementation
  • debugCompiledebugImplementation
  • androidTestCompileandroidTestImplementation
  • compileOnly仍然有效。它是在3.0中添加的,用于替换提供而不是编译。(当Gradle没有该用例的配置名称并以Maven提供的范围命名时引入了provided。)

这是Google在IO17宣布Android Gradle插件3.0带来的突破性变化之一。

compile配置为现已弃用,应替换为implementationapi

Gradle留档

dependencies {api 'commons-httpclient:commons-httpclient:3.1'implementation 'org.apache.commons:commons-lang3:3.5'}

api配置中出现的依赖项将是传递给图书馆的消费者,因此将出现在消费者的编译类路径上。

implementation配置中找到的依赖项将在另一方面,不会暴露给消费者,因此不会泄漏到消费者的编译类路径。这有几个好处:

  • 依赖项不再泄漏到消费者的编译类路径中,因此您永远不会意外地依赖于传递依赖关系
  • 由于减少了类路径大小,编译速度更快
  • 实现依赖项更改时更少的重新编译:消费者不需要重新编译
  • 更清洁的发布:当与新的maven发布插件一起使用时,Java库生成的POM文件准确区分编译所需的内容库以及在运行时使用库所需的内容(在其他话,不要混淆编译库本身所需的内容和什么需要对库进行编译)。

编译配置仍然存在,但不应使用,因为它不会提供apiimplementation配置提供的保证。


备注:如果您只在应用模块中使用库-常见情况-您不会注意到任何差异。
只有当您有一个模块相互依赖的复杂项目,或者您正在创建一个库时,您才会看到差异。

Compile配置已弃用,应替换为implementationapi

您可以在API和实现分离部分阅读文档。

简短的部分是

标准Java插件和Java的主要区别库插件是后者引入了API的概念库是一个Java的组成部分被其他组件消耗。这是一个非常常见的用例多项目构建,但也只要你有外部依赖关系。

插件公开了两个配置,可用于声明依赖项:api和实现。api配置应该是用于声明由库API导出的依赖项,而实现配置应该用于声明组件内部的依赖项。

有关详细说明,请参阅此图像。简要说明

简要解决方案:

更好的方法是用implementation依赖项替换所有compile依赖项。只有在你泄漏模块接口的地方,你才应该使用api。这应该会导致更少的重新编译。

 dependencies {implementation fileTree(dir: 'libs', include: ['*.jar']) 
implementation 'com.android.support:appcompat-v7:25.4.0'implementation 'com.android.support.constraint:constraint-layout:1.0.2'// … 
testImplementation 'junit:junit:4.12'androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})}

解释更多:

Android Gradle插件3.0之前:我们遇到了一个大问题,就是一次代码更改会导致所有模块重新编译。造成这种情况的根本原因是Gradle不知道你是否通过另一个模块泄露了模块的接口。

Android Gradle插件3.0之后:最新的Android Gradle插件现在要求您明确定义是否泄漏模块的接口。基于此,它可以正确选择应该重新编译的内容。

因此,compile依赖项已被弃用并被两个新依赖项取代:

  • api:你通过自己的接口泄漏了这个模块的接口,这意味着与旧的compile依赖完全相同

  • implementation:您仅在内部使用此模块,不会通过您的接口泄漏它

所以现在你可以明确地告诉Gradle重新编译一个模块,如果使用的模块的接口改变或没有改变。

图片来源:Jeroen Molsblog

此答案将演示项目中implementationapicompile之间的区别。


假设我有一个包含三个Gradle模块的项目:

  • app(Android应用程序)
  • myandroidLibrary(一个Android库)
  • Java图书馆

appmyandroidlibrary作为依赖项。myandroidlibrarymyjavalibrary作为依赖项。

Dependency1

myjavalibrary有一个MySecret

public class MySecret {
public static String getSecret() {return "Money";}}

myandroidlibraryMyAndroidComponent类,它操作MySecret类的值。

public class MyAndroidComponent {
private static String component = MySecret.getSecret();
public static String getComponent() {return "My component: " + component;}}

最后,app只对myandroidlibrary的值感兴趣

TextView tvHelloWorld = findViewById(R.id.tv_hello_world);tvHelloWorld.setText(MyAndroidComponent.getComponent());

现在,让我们来谈谈依赖关系…

app需要消耗:myandroidlibrary,所以在app中build.gradle使用implementation

说明:您也可以使用api/compile。但请暂时保持这种想法。)

dependencies {implementation project(':myandroidlibrary')}

Dependency2

你认为myandroidlibrarybuild.gradle应该是什么样子?我们应该使用哪个范围?

我们有三种选择:

dependencies {// Option #1implementation project(':myjavalibrary')// Option #2compile project(':myjavalibrary')// Option #3api project(':myjavalibrary')}

Dependency3

它们之间有什么区别,我应该使用什么?

编译或Api(选项#2或#3)Dependency4

如果您使用的是compileapi。我们的Android应用程序现在能够访问myandroidcomponent依赖项,这是一个MySecret类。

TextView textView = findViewById(R.id.text_view);textView.setText(MyAndroidComponent.getComponent());// You can access MySecrettextView.setText(MySecret.getSecret());

实施(选项#1)

Dependency5

如果您使用的是implementation配置,则不会公开MySecret

TextView textView = findViewById(R.id.text_view);textView.setText(MyAndroidComponent.getComponent());// You can NOT access MySecrettextView.setText(MySecret.getSecret()); // Won't even compile

那么,您应该选择哪种配置?这实际上取决于您的要求。

如果您使用想要暴露依赖关系,请使用apicompile

如果你不想暴露依赖关系(隐藏你的内部模块),那么使用implementation

备注:

这只是Gradle配置的要点,请参阅表49.1.Java库插件-用于声明依赖项的配置以获得更详细的解释。

此答案的示例项目可在https://github.com/aldoKelvianto/ImplementationVsCompile上找到

外行术语的简单区别是:

  • 如果您正在处理通过公开声明的依赖项的成员来为其他模块提供支持的接口或模块,您应该使用“api”。
  • 如果您要在内部实现或使用声明的依赖项的应用程序或模块,请使用“实现”。
  • “编译”与“api”的工作方式相同,但是,如果您只实现或使用任何库,“实现”会更好地工作并节省您的资源。

阅读@aldok的答案,了解一个全面的例子。

+--------------------+----------------------+-------------+--------------+-----------------------------------------+| Name               | Role                 | Consumable? | Resolveable? | Description                             |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| api                | Declaring            |      no     |      no      | This is where you should declare        ||                    | API                  |             |              | dependencies which are transitively     ||                    | dependencies         |             |              | exported to consumers, for compile.     |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| implementation     | Declaring            |      no     |      no      | This is where you should                ||                    | implementation       |             |              | declare dependencies which are          ||                    | dependencies         |             |              | purely internal and not                 ||                    |                      |             |              | meant to be exposed to consumers.       |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| compileOnly        | Declaring compile    |     yes     |      yes     | This is where you should                ||                    | only                 |             |              | declare dependencies                    ||                    | dependencies         |             |              | which are only required                 ||                    |                      |             |              | at compile time, but should             ||                    |                      |             |              | not leak into the runtime.              ||                    |                      |             |              | This typically includes dependencies    ||                    |                      |             |              | which are shaded when found at runtime. |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| runtimeOnly        | Declaring            |      no     |      no      | This is where you should                ||                    | runtime              |             |              | declare dependencies which              ||                    | dependencies         |             |              | are only required at runtime,           ||                    |                      |             |              | and not at compile time.                |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| testImplementation | Test dependencies    |      no     |      no      | This is where you                       ||                    |                      |             |              | should declare dependencies             ||                    |                      |             |              | which are used to compile tests.        |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| testCompileOnly    | Declaring test       |     yes     |      yes     | This is where you should                ||                    | compile only         |             |              | declare dependencies                    ||                    | dependencies         |             |              | which are only required                 ||                    |                      |             |              | at test compile time,                   ||                    |                      |             |              | but should not leak into the runtime.   ||                    |                      |             |              | This typically includes dependencies    ||                    |                      |             |              | which are shaded when found at runtime. |+--------------------+----------------------+-------------+--------------+-----------------------------------------+| testRuntimeOnly    | Declaring test       |      no     |      no      | This is where you should                ||                    | runtime dependencies |             |              | declare dependencies which              ||                    |                      |             |              | are only required at test               ||                    |                      |             |              | runtime, and not at test compile time.  |+--------------------+----------------------+-------------+--------------+-----------------------------------------+

由于版本5.6.3Gradle留档提供了简单的经验法则来识别旧的compile依赖项(或新依赖项)是否应该替换为implementationapi依赖项:

  • 如果可能,首选implementation配置而不是api

这使依赖项远离消费者的编译类路径。此外,如果任何实现类型意外泄漏到公共API中,消费者将立即无法编译。

那么什么时候应该使用api配置呢?API依赖项是至少包含一种在库二进制接口中公开的类型,通常称为其ABI(应用程序二进制接口)。这包括但不限于:

  • 超类或接口中使用的类型
  • 公共方法参数中使用的类型,包括泛型参数类型(其中public是编译器可见的东西。即Java世界中的公共、受保护和包私有成员)
  • 公共领域使用的类型
  • 公共注释类型

相比之下,以下列表中使用的任何类型都与ABI无关,因此应声明为implementation依赖项:

  • 专门用于方法体的类型
  • 专用于私人成员的类型
  • 仅在内部类中找到的类型(Gradle的未来版本将允许您声明哪些包属于公共API)

Gradle依赖配置

Gradle 3.0引入了下一个更改:

  • compile->api

    api关键字与已弃用compile相同,它公开了所有级别的这种依赖关系

  • compile->implementation

    implementation在构建时仅公开上一层的依赖项(依赖项在运行时可用)。因此,您的构建速度更快(无需重新编译高于1级的消费者)

  • provided->compileOnly

    此依赖项仅在编译时间中可用(此依赖项在运行时不可用)。此依赖项不能是可传递的,并且是.aar。它可以与编译时注释处理器关于我们一起使用,并允许您减少最终输出文件

  • compile->annotationProcessor

    compileOnly非常相似,但也保证了传递依赖对消费者不可见

  • apk->runtimeOnly

    依赖项在编译时不可用,但在运行时可用。

[POM依赖类型]

  • 执行:我们主要使用实现配置。它隐藏了模块对其消费者的内部依赖,以避免意外使用任何传递依赖,因此更快的编译和更少的重新编译。

  • api:必须非常小心地使用,因为它会泄露给消费者的编译类路径,因此滥用api可能会导致依赖污染。

  • 只编译:当我们在运行时不需要任何依赖项时,因为compileOnly依赖项不会成为最终构建的一部分。我们将获得更小的构建大小。

  • 只运行时当我们想要在运行时(在最终构建中)更改或交换库的行为时。

我创建了一个帖子,对工作示例:源代码的每一个都有深入的了解

https://medium.com/@gauraw.negi/how-gradle-dependency-configurations-work-underhood-e934906752e5

Gradle配置

继续之前的一些注意事项;不建议使用编译,文档状态您应该使用实现,因为编译将在Gradle 7.0版中删除。如果您使用--warning-mode all运行Gradle构建,您将看到以下消息;

不建议使用编译配置来声明依赖项。这将失败并在Gradle 7.0中出现错误。请改用实现配置。


只需查看帮助页面中的图像,它就很有意义。

所以你有蓝色的盒子compileClasspathruntimeClassPath
compileClasspath是在运行gradle build时成功构建所需的。编译时类路径上出现的库将是使用compileOnlyimplementation在您的gradle构建中配置的所有库。

然后我们有runtimeClasspath,这些都是您使用implementationruntimeOnly添加的包。所有这些库都将添加到您部署在服务器上的最终构建文件中。

正如您在图中看到的,如果您希望一个库既用于编译,又希望将其添加到构建文件中,那么应该使用implementation

runtimeOnly的示例可以是数据库驱动程序。
compileOnly的一个例子可以是servlet-api。
implementation的一个例子可以是sping-core。

Gradle

当您在gradle项目中声明依赖项时代码库及其依赖项(声明为api)可以由消费者gradle项目使用。

让我们举个例子

我们有1级,2级,3级的Gradle项目。

级别1使用级别2。级别2使用级别3。

1级<-2级<-3级

使用api和实现,我们可以控制级别3的类是否应该暴露给级别1。

输入图片描述

这如何使构建更快:

级别3中的任何更改都不需要重新编译级别1。特别是在开发中,节省时间。

其他答案解释了差异。

只要确保对于静态编程语言DSL(build.gradle.kts),函数应该有括号和它们的字符串参数用双引号括起来,而不是单引号:

  • Groovy(build.gradle)
    implementation 'com.android.support:appcompat-v7:25.0.0'testImplementation 'junit:junit:4.12'
  • 静态编程语言(build.gradle.kts)
    implementation("com.android.support:appcompat-v7:25.0.0")testImplementation("junit:junit:4.12")