Android Gradle 依赖配置:implementation & api

背景:
Android Gradle plugin 3.0开始(对应Gradle版本 4.1及以上),原有的依赖配置类型compile已经被废弃,开始使用implementationapiannotationProcessor类型分别替代。对应的,这三种替代配置类型针对具体的使用场景,具备不一样的依赖行为。其中,implementationapi依赖又相对最为经常使用,对其具体含义也须要理解清,在实际项目中选择依赖配置时,也才能游刃有余。java

首先看一下Android官方文档中关于依赖配置的详细介绍:Add build dependencies git

Android Gradle依赖配置
为陈述方便且不容易理解歧义,先划分出以下几个术语。

  • 被引入的依赖模块,简称 依赖模块
  • 引入了被依赖模块的当前模块,简称 当前模块
  • 依赖了当前模块的上层模块,简称 其余上层模块

因而,官方文档中的描述翻译后对应的含义为:
1,implementation
此依赖配置,使Gradle意识到,当前模块引入的依赖模块,在编译期间对其余上层模块不可见,仅在运行时对其余上层模块可见。这将会加快多模块依赖的项目总体编译速度,由于经过implementation引入的依赖模块,若是依赖模块内部有进行过Api的改动,因为其对其余上层模块不可见,所以只需从新编译依赖模块自身以及使用到此改动的Api的当前模块便可。api

2, api
等同于原有的compile,此依赖配置,使Gradle意识到,其引入的依赖模块,不管在编译期仍是在运行时,都对其余上层模块可见,即经过api配置引入的依赖模块,在依赖关系上具备传递性。这将会使得其余上层模块能够直接使用依赖模块的Api,但这同时意味着一旦依赖模块发生Api的改动,将致使全部已经使用了依赖模块改动了的Api的上层模块都须要从新执行编译。所以,通常会加大整个项目的编译时间,如非必要,通常建议优先使用implementation依赖配置。bash

如此描述通常状况下还不是很容易理解。描述中最关键的几个词有:可见性依赖传递编译期运行时,和 使Gradle意识到app

下面先经过一个具体的例子感性认识下implementationapi 二者的区别。 新建一个项目HappyCorn,具体项目结构以下:单元测试

Root project 'HappyCorn'
+--- Project ':app'
+--- Project ':LibA'
+--- Project ':LibB'
+--- Project ':LibC'
\--- Project ':LibD'
复制代码

其中,app为application类型,LibA、LibB、LibC、LibD分别是四个library类型。
LibA中新建以下类:测试

package com.happycorn.librarya;

public class LibAClass {
    public static String getName() {
        return "Library A";
    }
}
复制代码

一样的,LibB中:gradle

package com.happycorn.libraryb;

public class LibBClass {
    public static String getName() {
        return "Library B";
    }
}
复制代码

LibC中:ui

package com.happycorn.libraryc;

public class LibCClass {
    public static String getName() {
        OkHttpClient okHttpClient = new OkHttpClient();
        return "Library C";
    }
}
复制代码

LibD中:spa

package com.happycorn.libraryd;

public class LibDClass {
    public static String getName() {
        return "Library D";
    }
}
复制代码

进行依赖配置,使得项目总体依赖相似于树形结构:

对应的依赖配置分别以下:

  • :app依赖(implementationapi):LibA和:LibB
  • :LibA implementation 依赖:LibC
  • :LibB api 依赖:LibD

执行graldew命令,查看:app对那些进行了依赖:

./gradlew :app::dependencies
复制代码

输出结果为(单元测试等不太相干信息先去掉):

...
debugCompileClasspath - Resolved configuration for compilation for variant: debug
+--- project :LibA
\--- project :LibB
     \--- project :LibD

debugRuntimeClasspath - Resolved configuration for runtime for variant: debug
+--- project :LibA
|    \--- project :LibC
\--- project :LibB
     \--- project :LibD

releaseCompileClasspath - Resolved configuration for compilation for variant: release
+--- project :LibA
\--- project :LibB
     \--- project :LibD

releaseRuntimeClasspath - Resolved configuration for runtime for variant: release
+--- project :LibA
|    \--- project :LibC
\--- project :LibB
     \--- project :LibD
...
复制代码

从输出信息中能够看出,不管是debug仍是release变体,在编译时与运行时所依赖的依赖模块是不一样的。对于:LibC,在编译时对:app不可见,但在运行时对:app是可见的。

执行./gradlew :LibA:dependencies,确认下:LibA的依赖。 对应输出结果:

debugCompileClasspath - Resolved configuration for compilation for variant: debug
\--- project :LibC

debugRuntimeClasspath - Resolved configuration for runtime for variant: debug
\--- project :LibC

releaseCompileClasspath - Resolved configuration for compilation for variant: release
\--- project :LibC

releaseRuntimeClasspath - Resolved configuration for runtime for variant: release
\--- project :LibC
复制代码

可见,:LibA确实已经依赖了:LibC。

进一步,若是此时在:app中分别调用:LibA、:LibB、:LibC、:LibD模块的Api,发现:app中是没法直接调用:LibC的方法的。

所以,能够证明,经过 implementation引入的依赖模块,在编译期对其余上层模块是不可见的,对应的依赖关系不具备传递性。

接下来继续看依赖关系与模块编译之间的关系。 先执行命令清理掉历史构建结果:

./gradlew clean
复制代码

执行build task assembleDebug 或 :app:compileDebugJavaWithJavac:

./gradlew :app:compileDebugJavaWithJavac  --info
复制代码

编译成功,其中,关键信息输出记录为:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.
> Task :LibC:compileDebugJavaWithJavac 
...
Compiling with JDK Java compiler API.
Class dependency analysis for incremental compilation took 0.003 secs.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.023 secs.

...

:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.
> Task :LibA:compileDebugJavaWithJavac 
...
Compiling with JDK Java compiler API.
Class dependency analysis for incremental compilation took 0.001 secs.
Created jar classpath snapshot for incremental compilation in 0.001 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.024 secs.

...

:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.
> Task :LibD:compileDebugJavaWithJavac 
...
Compiling with JDK Java compiler API.
Class dependency analysis for incremental compilation took 0.0 secs.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.018 secs.

...

:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.
> Task :LibB:compileDebugJavaWithJavac 
...
Compiling with JDK Java compiler API.
Class dependency analysis for incremental compilation took 0.002 secs.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.033 secs.

...

:app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.
> Task :app:compileDebugJavaWithJavac 
...
Compiling with JDK Java compiler API.
Class dependency analysis for incremental compilation took 0.004 secs.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.099 secs.

...
复制代码

每一个模块都进行了对应的compile过程。且对应的顺序为:LibC >> :LibA >> :LibD >> :LibB >> :app

再次执行build task compileDebugJavaWithJavac:

./gradlew :app:compileDebugJavaWithJavac  --info
复制代码

编译成功,此时,关键信息输出记录为:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started.
> Task :LibC:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date.
:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs.

...

:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started.
> Task :LibA:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date.
:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs.

...

:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started.
> Task :LibD:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibD:compileDebugJavaWithJavac' as it is up-to-date.
:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.002 secs.

...

:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started.
> Task :LibB:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date.
:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs.

...

:app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started.
> Task :app:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date.
:app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.024 secs.
复制代码

咱们发现,对应的compileDebugJavaWithJavac task都直接Skip掉了,由于此时代码没有更新,无需从新编译。

修改:libD中LibDClass类中的代码,先修改方法内的代码:

public class LibDClass {
    public static String getName() {
        return "Library D ... change code";
    }
}
复制代码

再次执行build task compileDebugJavaWithJavac:

./gradlew :app:compileDebugJavaWithJavac --info
复制代码

对应关键编译信息为:

Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date.

...

Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date.

...

:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started.
> Task :LibB:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date.
:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.004 secs.


...

Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date.

...

Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date.
复制代码

咱们发现,修改:LibD中的方法中的代码,task compileDebugJavaWithJavac只是从新编译了:LibD。其余模块,包括依赖此模块的各上层模块,都没有从新执行编译task。

接下来,修改:LibD中的方法名,对应以下:

public class LibDClass {
    public static String getNewName() {
        return "Library D";
    }
}
复制代码

执行:

./gradlew :app:compileDebugJavaWithJavac  --info
复制代码

关键信息输出为:

Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date.

...

Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date.

...

:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started.

> Task :LibD:compileDebugJavaWithJavac 
Task ':LibD:compileDebugJavaWithJavac' is not up-to-date because:
  Input property 'source' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/src/main/java/com/happycorn/libraryd/LibDClass.java has changed.
Compiling with source level 1.7 and target level 1.7.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
Compiling with JDK Java compiler API.
Incremental compilation of 1 classes completed in 0.008 secs.
Class dependency analysis for incremental compilation took 0.001 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibD:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.013 secs.

...

> Task :LibB:javaPreCompileDebug 
Task ':LibB:javaPreCompileDebug' is not up-to-date because:
  Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed.

:LibB:javaPreCompileDebug (Thread[Task worker for ':',5,main]) completed. Took 0.002 secs.
:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started.

> Task :LibB:compileDebugJavaWithJavac UP-TO-DATE
Task ':LibB:compileDebugJavaWithJavac' is not up-to-date because:
  Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed.
Compiling with source level 1.7 and target level 1.7.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
None of the classes needs to be compiled! Analysis took 0.001 secs. 
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.005 secs.

...

> Task :app:javaPreCompileDebug 
Task ':app:javaPreCompileDebug' is not up-to-date because:
  Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed.

:app:javaPreCompileDebug (Thread[Task worker for ':',5,main]) completed. Took 0.002 secs.
:app:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started.

> Task :app:compileDebugJavaWithJavac UP-TO-DATE
Task ':app:compileDebugJavaWithJavac' is not up-to-date because:
  Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed.
Compiling with source level 1.7 and target level 1.7.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
None of the classes needs to be compiled! Analysis took 0.0 secs. 
Written jar classpath snapshot for incremental compilation in 0.0 secs.

:app:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.004 secs.
复制代码

可见,此时,:LiD,:LiB,:app依次都从新进行了编译task。

新增类,新增或修改非private的对外方法名等,在api引入的方式下,都会使得上层模块从新编译,由于上层模块可能会直接用到此类方法,但在上层模块的实际编译过程当中,并不会对模块内的类都进行从新编译,而是只会编译确实已经使用了依赖模块的API的类。

这也正是文档中提到的:
if an api dependency changes its external API, Gradle recompiles all modules that have access to that dependency at compile time.

一样的,咱们改变:LibC中的getName()方法实现,方便编译信息跟改变:LibD中的getName()方法实现同样,其余上层模块都没有从新执行编译task。

一样的,改变:LibC中的方法名,再次执行:

./gradlew :app:compileDebugJavaWithJavac --info
复制代码

关键信息输出为:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.

> Task :LibC:compileDebugJavaWithJavac 
Task ':LibC:compileDebugJavaWithJavac' is not up-to-date because:
  Input property 'source' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/src/main/java/com/happycorn/libraryc/LibCClass.java has changed.
Compiling with source level 1.7 and target level 1.7.
Created jar classpath snapshot for incremental compilation in 0.0 secs.
file or directory '/Users/corn/AndroidStudioProjects/HappyCorn/LibC/src/debug/java', not found
Compiling with JDK Java compiler API.
Incremental compilation of 1 classes completed in 0.009 secs.
Class dependency analysis for incremental compilation took 0.004 secs.
Written jar classpath snapshot for incremental compilation in 0.0 secs.
:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.02 secs.

...

> Task :LibA:javaPreCompileDebug 
Task ':LibA:javaPreCompileDebug' is not up-to-date because:
  Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/build/intermediates/intermediate-jars/debug/classes.jar has changed.
:LibA:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.018 secs.
:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.

> Task :LibA:compileDebugJavaWithJavac UP-TO-DATE
Task ':LibA:compileDebugJavaWithJavac' is not up-to-date because:
  Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/build/intermediates/intermediate-jars/debug/classes.jar has changed.
Compiling with source level 1.7 and target level 1.7.
Created jar classpath snapshot for incremental compilation in 0.001 secs.
None of the classes needs to be compiled! Analysis took 0.001 secs. 
Written jar classpath snapshot for incremental compilation in 0.0 secs.
:LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.009 secs.

...

> Task :LibD:javaPreCompileDebug UP-TO-DATE
Skipping task ':LibD:javaPreCompileDebug' as it is up-to-date.

...

> Task :LibB:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date.

...

:app:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) started.

> Task :app:javaPreCompileDebug UP-TO-DATE
Skipping task ':app:javaPreCompileDebug' as it is up-to-date.

:app:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.008 secs.
:app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started.

> Task :app:compileDebugJavaWithJavac UP-TO-DATE
Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date.
复制代码

可见,此时:LibA从新执行了编译task,但:LibA的上层模块:app并无从新执行编译task。由于:app的依赖关系在编译期并不包含:LibC相吻合。

至此,相信对implementationapi的含义已经有了必定的理解。也已经对上文中的最关键的几个词有:可见性依赖传递编译期运行时有了必定的认知。

下面继续阐述下使Gradle意识到具体含义。

实际项目开发中,对于第三方的功能模块,或者项目中抽取出去的独立的功能模块,每每造成独立的git库,进行单独的维护和管理,并生成对应的jar包或aar文件,上传到marven库。主工程中的各模块经过依赖配置去引入对应marven库上的构件。其引入的构件有时又每每经过引入了其余的marven库上的构件。此时,通过marven引入的构件内部,不管是经过implementation仍是api的依赖配置去依赖了其余的marven构件,效果对于当前模块来讲,都是等同的。由于implementation仍是api的依赖传递关系也好,可见性也罢,都是针对当前项目的Gradle而言的。引入的marven上的构件,不管是jar包仍是aar文件,都已是经过自身编译以后的构件,其内部的依赖配置对当前项目的Gradle已经失效。

以下图,是项目中外部依赖aar构建X中implementation依赖了另一个aar构建Y,在X的aarpom文件中,其对应的依赖关系声明中,已经再也不具备任何implementation的表述,自动变成了compile形式。

项目中引入的marven库中的构件,其内部的依赖配置对当前项目的Gradle是失效的。

例如:

  • :app api 依赖 :LibB
  • :LiB api依赖 :LibD
  • :LibD api依赖了 marven库中的构件 :LibX
  • :LibX项目内部implementation依赖了marven库中的另外一构件 :LibY

此时,LibD依然能够直接使用LibY中的对外Api,也就是说,此时即便:LibX项目经过implementation引入的:LibY,但:LibY对:LibD 依然具备依赖传递,具备可见性。

此即官方文档中说起的 it's letting Gradle know that的内在含义。

总结:
implementationapi依赖配置主要是控制依赖模块对上层模块的依赖关系传递及可见性,在实际进行项目构建时,编译期和运行时,又可能具备不一样的依赖传递关系。理解不一样的依赖配置,对具体的编译期和运行时的依赖关系具备重要意义,也是解决依赖冲突等问题的关键。

相关文章
相关标签/搜索