一分为二
dependencies中原来的compile
一分为二,改为api
或者implementation
。
为什么compile
要一分为二呢?api
和implementation
的区别又是什么?
先来看官方文档说明:
The
api
configuration should be used to declare dependencies which are exported by the library API, whereas theimplementation
configuration should be used to declare dependencies which are internal to the component.
Dependencies appearing in the
api
configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in theimplementation
configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath.
以一个library为例,api
声明的依赖会暴露给library的使用者,而implementation
声明的不会。
看具体例子:
假设我们有一个叫做LibraryA
的library,它内部使用了另一个叫做LibraryB
的library的方法。
// 'LibraryA' module
public class LibraryA {
public static String myString(){
return LibraryB.giveMeAString(); // 使用LibraryB的方法
}
}
// 'LibraryB' module
public class LibraryB {
public static String giveMeAString(){
return "hello";
}
}
然后在我们的app中使用LibraryA
:
dependencies {
implementation project(':LibraryA')
}
然后我们分别来看api
和implementation
2种情况:
1.假设LibraryA
的build.gradle的dependencies中使用api
(或者deprecated的compile
)声明对LibraryB
的依赖:
dependencies {
api project(':LibraryB')
// compile project(':LibraryB') // deprecated
}
此时在app中我们可以使用LibraryA
和LibraryB
:
// 可以使用LibraryA
LibraryA.myString();
// 可以使用LibraryB
LibraryB.giveMeAString();
2.假设LibraryA
的build.gradle的dependencies中使用implementation
声明对LibraryB
的依赖:
dependencies {
implementation project(':LibraryB')
}
此时在app中我们只能使用LibraryA
,而无法使用LibraryB
:
// 可以使用LibraryA
LibraryA.myString();
// 无法使用LibraryB,编译报错
//LibraryB.giveMeAString();
由此可见,一分为二之后,api
基本等同于原来的compile
,而implementation
则可以让我们隐藏transitive dependency。
这样有什么好处呢?摘录如下:
- dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive dependency
- faster compilation thanks to reduced classpath size
- less recompilations when implementation dependencies change: consumers would not need to be recompiled
- cleaner publishing (don’t mix what is needed to compile the library itself and what is needed to compile against the library)
其中的二、三两条都有助于缩短编译时间。
那么,我们该如何正确的使用api
/implementation
呢?
如何正确使用
首要规则:
- Prefer the
implementation
configuration overapi
when possible
面向对象封装的概念,类似类成员的最小可见性原则,不暴露实现,方便修改,加速编译。
那么何时使用api
?
An API dependency is one that contains at least one type that is exposed in the library binary interface, often referred to as its ABI (Application Binary Interface). This includes, but is not limited to:
- types used in super classes or interfaces
- types used in public method parameters, including generic parameter types (where public is something that is visible to compilers. I.e. , public, protected and package private members in the Java world)
- types used in public fields
- public annotation types
By contrast, any type that is used in the following list is irrelevant to the ABI, and therefore should be declared as an implementation dependency:
- types exclusively used in method bodies
- types exclusively used in private members
- types exclusively found in internal classes (future versions of Gradle will let you declare which packages belong to the public API)
这里引用官方文档的示例:
// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class HttpClientWrapper {
private final HttpClient client; // private member: implementation details
// HttpClient is used as a parameter of a public method
// so "leaks" into the public API of this component
public HttpClientWrapper(HttpClient client) {
this.client = client;
}
// public methods belongs to your API
public byte[] doRawGet(String url) {
HttpGet request = new HttpGet(url);
try {
HttpEntity entity = doGet(request);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
entity.writeTo(baos);
return baos.toByteArray();
} catch (Exception e) {
ExceptionUtils.rethrow(e); // this dependency is internal only
} finally {
request.releaseConnection();
}
return null;
}
// HttpGet and HttpEntity are used in a private method, so they don't belong to the API
private HttpEntity doGet(HttpGet get) throws Exception {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
System.err.println("Method failed: " + response.getStatusLine());
}
return response.getEntity();
}
}
dependencies {
api 'org.apache.httpcomponents:httpclient:4.5.7'
implementation 'org.apache.commons:commons-lang3:3.5'
}
原理
那么,Gradle到底是如何实现这一区分的呢?
Java Library Plugin
先来看一张描述 Gradle main configurations setup 的图
标注:
- The configurations in green are the ones a user should use to declare dependencies
- The configurations in pink are the ones used when a component compiles, or runs against the library
- The configurations in blue are internal to the component, for its own use
- (C) for consumable configurations, (R) for resolvable configurations
绿色的是使用者声明的,红色的是暴露给consumer(编译/运行)用的,蓝色的是仅限自己(编译/运行)用的。
由上图可以看出,api
声明的依赖会进入真正用来编译的全部4个configuration:apiElements
,compileClasspath
,runtimeElements
,runtimeClasspath
,
而implementation
声明的依赖只会进入除apiElements
以外的其他3个configuration。
而apiElements
是用来"For compiling against this library",也就是说consumer编译时无法获得这些依赖,但运行时仍旧包含,所以虽然无法编译但依然能够运行。
Android Gradle Plugin
Android Gradle plugin中的实现略有不同:
Configuration | Behavior |
---|---|
implementation | Gradle adds the dependency to the compile classpath and packages the dependency to the build output. However, when your module configures an implementation dependency, it's letting Gradle know that you do not want the module to leak the dependency to other modules at compile time. That is, the dependency is available to other modules only at runtime. |
api | Gradle adds the dependency to the compile classpath and build output. When a module includes an api dependency, it's letting Gradle know that the module wants to transitively export that dependency to other modules, so that it's available to them at both runtime and compile time. |
区别在于implementation
的依赖被打包(package)后放到build output,而api
的直接放,
这样implementation
的依赖只在runtime能被其他module使用,而api
的在runtime和compile time都可以使用。
Gradle vs Maven
以上介绍的都是Gradle中的情况,但是当我们需要发布一个module时,我们采用的是Maven的形式。
此时就需要将Gradle的Configurations对应为Maven的Scopes,分别列出两者如下:
Gradle Dependency Configurations:
- api
- implementation
- compileOnly
- runtimeOnly
- testImplementation
- testCompileOnly
- testRuntimeOnly
Maven Dependency Scopes:
- compile
- provided
- runtime
- test
- system
- import
相对于Maven,Gradle更新,开发维护更活跃,所以Gradle的发展早已超越Maven。
以上两者大致的对应关系如下:
Maven Scope | Gradle Configuration |
---|---|
compile | api/implementation |
provided | compileOnly |
runtime | runtimeOnly |
Maven没有区分exposed/internal dependency的概念,所以Gradle的api
/implementation
依赖在写到Maven POM中时只能都映射为compile
。
发布的module再次被Gradle引入时,它的transitive dependencies就相当于都变成了api
类型的。