Gradle实施与API配置


231

我试图弄清楚在构建我的依赖项时apiimplementation配置之间的区别。
在文档中,它说implementation具有更好的构建时间,但是在类似的问题中看到此 注释后,我想知道这是否成立。
由于我不是gradle方面的专家,因此希望有人可以提供帮助。我已经阅读过文档,但是我想知道一个易于理解的解释。


1
在这里读过吗?
MatPag

实际上,我确实做了,但是正如我所说,那条评论使我对此感到惊讶。所以我现在有点迷路
reinaldomoreira

您可能会将库的依赖项从切换compileapi。您在内部使用的库可以使用一些私有实现,这些私有实现不会在最终库中公开,因此对您是透明的。可以将那些“内部-私有”依赖项切换到implementation,当Android gradle插件编译您的应用程序时,它将跳过那些依赖项的编译,从而缩短了构建时间(但这些依赖项将在运行时可用)。显然,如果您具有本地模块库,则可以执行相同的操作
MatPag

1
以下是对“ api”和“实现”的简短图形说明:jeroenmols.com/blog/2017/06/14/androidstudio3
albert c braun

1
那是一个很棒的帖子!谢谢@albertbraun
reinaldomoreira

Answers:


418

compile不推荐使用Gradle 关键字,而推荐使用apiand implementation关键字来配置依赖项。

using 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使用api配置dependencies{}如下:

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 (and you shouldn't)
System.out.println(InternalLibrary.giveMeAString());

这样,模块MyLibrary可能会“泄漏”某些东西的内部实现。您不应该(不能)使用它,因为它不是您直接导入的。

implementation引入的配置,以防止这一点。所以现在,如果您使用implementation而不是apiin MyLibrary

dependencies {
    implementation project(':InternalLibrary')
}

您将无法再调用InternalLibrary.giveMeAString()您的应用代码。

这种装箱策略使Android Gradle插件知道,如果您在其中进行编辑InternalLibrary,则它只能触发重新编译MyLibrary不是整个应用程序的重新编译,因为您无权访问InternalLibrary

当您有很多嵌套的依赖项时,此机制可以大大加快构建速度。(请观看最后链接的视频,以全面了解此内容)

结论

  • 当您切换到新的Android Gradle插件3.XX时,应将所有关键词替换compile(1 *)。然后尝试编译和测试您的应用程序。如果一切正常,则将代码保留原样,如果遇到问题,则可能是依赖项有问题,或者您使用的是现在私有的且无法访问的东西。Android Gradle插件工程师Jerome Dochez的建议(1)*implementation

  • 如果您是图书馆管理员,则应使用图书馆api公共API所需的每个依赖项,同时使用implementation测试依赖项或最终用户不得使用的依赖项。

有用的文章展示了实现api之间的区别

参考 (这是为节省时间而分割的同一视频)

Google I / O 2017-如何加快Gradle构建速度(FULL VIDEO)

Google I / O 2017-如何加快Gradle的构建速度(仅限NEW GRADLE PLUGIN 3.0.0部分)

Google I / O 2017-如何加快Gradle构建(参考1 *

Android文档


4
我注意到api在库模块中似乎无法正常运行。如果使用它,我仍然无法从我的应用程序项目中访问依赖项。我只能访问该库本身中的代码。
艾伦W

1
这很好,可以在调试版本上使用,但是在使用ProGuard(在发行版本上)时MyLibrary#myString()会崩溃,因为ProGuard已InternalLibrary被删除。在ProGuard应用中使用android-libs的最佳实践是什么?
hardysim

3
我认为答案是不准确的,应用程序可以使用MyLibrary所需的任何范围。它会根据MyLibrary是否使用api /实现来查看InternalLibrary。
Snicolas

2
谢啦。很棒的解释,比android官方文档中给出的解释更好
亨利

2
这是一个很好的解释。理论与混凝土融为一体。做得好。谢谢你
彼得·卡恩

133

我喜欢将api依赖项视为公共(由其他模块查看),而将implementation依赖项视为私有(仅由该模块看到)。

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


15
喜欢这个答案的简单性非常感谢您
Kevin Gilles

2
真正的区别(AFAICT)是,生成的pom文件将api依赖项置于“编译”范围内(它们将作为依赖项包含在您的库中,并且取决于库的任何内容)和implementation依赖项置于“运行时”范围中(最好将它们放在代码在运行时的类路径,但不需要其他代码来编译使用您的库的代码。
Shadow Man

@ShadowMan这是插件的实现细节,负责生成POM文件,以及如何将Gradle范围映射到Maven范围。
dev.bmax

1
您应该使用implementation运行所需的任何依赖项(以及要编译的库),但不应将其自动引入使用库的项目中。例如jax-rs,您的库可能使用RESTeasy,但不应将这些库拉入使用您的库的任何项目中,因为他们可能想使用Jersey。
Shadow Man

1
这就是您知道有人得到它的东西的原因:D感谢您简单清晰的回答
Elias Fazel

12

想想你拥有app它采用模块lib1作为一个库和lib1使用lib2作为一个库。像这样的东西:app -> lib1 -> lib2

现在,在中使用api lib2lib1,然后在使用或在模块中时app 可以看到 lib2代码。api lib1implementation lib1app

但是使用的时候implementation lib2lib1,则app 无法看到lib2代码。


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

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

[项目-b]-> [项目-a]-> [spring-boot-gradle-plugin]

然后,我测试了以下方案:

  1. 通过制作项目A依赖于'1.5.20.RELEASE org.springframework.boot:弹簧引导gradle这个-插件' 的实施

    gradle dependencies在终端B根目录中运行命令,并获得以下输出屏幕截图,我们可以看到“ spring-boot-gradle-plugin”出现在runtimeClasspath依赖关系树中,而不是出现在compileClasspath的树中,我认为这就是为什么我们不能制作使用通过实现声明的库,它将不会通过编译。

    在此处输入图片说明

  2. 使项目A取决于api的 'org.springframework.boot:spring-boot-gradle-plugin:1.5.20.RELEASE'

    gradle dependencies再次在终端B根目录中运行命令。现在,“ spring-boot-gradle-plugin”同时出现在compileClasspath和runtimeClasspath依赖关系树中。

    在此处输入图片说明

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


2

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类的公共接口。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.