本文主要从这几个方面来介绍gradle
。 希望看完这篇文章能够了解gradle
是什么,以及如何使用它。
一. 为什么要学习gradle
gradle
是基于Apache Ant
和Apache Maven
概念的项目自动化构建工具。并且基于groovy
的特定领域语言(DSL)来编写脚本。groovy
语言极其类似JAVA
语言,且更简于JAVA
,能够让使用者更加灵活的自定义构建配置。利用gradle
可以实现自动化执行和管理构建流程。
二. gradle 生命周期
因为gradle
是一种基于依赖的构建语言,也就是说需要在执行tasks
阶段前,确定所有参与构建中的project
,以及对定义的tasks
以及其依赖的tasks
能够形成一个正确的有向无环图。因而gradle
在构建过程中,有三个不同的时期,分别为:初始化阶段,配置阶段,执行阶段。
initialization: 初始化阶段
主要工作:解析整个工程中所有的project
,构建所有的Project
对应的project
对象。其中文件settings.gradle
就是在初始化阶段执行的,这个文件主要是用来决定哪些项目(project
)参与到构建中。当一个项目越来越多时,往往会添加多个library-module
,对于每一个module-libraray
的添加,我们都需要在settings.gradle
文件上声明,否在对于整个项目来说,添加的module
,只是一个包含了一些代码源文件的文件夹,并不能参与到整个项目的构建中来。例如:
include ':app',':module_base',':module_home',':module_h5'
configuration: 配置阶段
主要工作:解析所有的project
对象中的task
,构建好所有tasks
的拓扑图,也就是有向无环图,在配置阶段,会执行到文件build.gradle
. 并下载所有以apply plugin
声明的所有插件。
execution: 执行阶段
执行具体的task
和其依赖的task
.
三. gradle project
随着app模块的逐渐增多,一个android
项目通过组件化的方式,可以让各个模块分工明确,同时也可以让开发专注于自己的模块开发。那么gradle
如何管理这些模块呢?在gradle
里面,一个模块可以认为是一个project
.
而project
和build.gradle
文件之间是1对1的关系,具体一个项目有多少个project
, 是在构建生命周期中的initialization
阶段,查找settings.gradle
来确定的。
一个project
中又可以包含多个task
,我们可以自定义task
来处理一段逻辑。至于如何创建task
在接下来一节会介绍。
一个project
同时会有多个依赖,对于android
项目,依赖来自于这三个部分:Library modules
,aar modules
,jar libraray
. 对这三种依赖调用的方式也是不一样的。这点在project
具体的api
会介绍。
一个project
有一个名字,和一个唯一有效的路径来唯一确定它。同时一个project
可以有一些插件(plugin
), 如android的lib
模块,我们就可以使用:
apply plugin: 'com.android.library'
同时project
在gradle
作为一个类存在的话,具有很多属性。在构建脚本中,我们同样可以设置想要的属性和扩展需要的属性。比如通过ext
来定义。
接下来就介绍一些常用的project
的api
3.1 project api
allprojects
这个属性是包含该项目及其它的子项目。我们可以在其包含的块中添加每个project
都需要配置的东西,这样就可以避免重复配置和方便修改。
在顶级build.gradle
文件中加入这段代码后:
allprojects {
println "the project name is :: " + project.name
}
输出为:
> Configure project :
the project name is :: GradleDemo
the project name is :: app
可以看出这段代码在配置阶段的时候,就会被调用,同时会输出root
project 和它的子project
.
buildscript
管理编译这个项目的仓库和依赖。通常在android
项目中,设置在顶级的build.gradle
中,用来定义项目中所有模块公用的gradle
存储区和依赖项。 在buildscript
中,也有一个属性dependencies
,这里的dependencies
定义的是gradle
构建工具所依赖的库,而非是project
依赖的库。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
ext
通过在ext
命名空间来声明属性实现扩展属性的功能。
顶层build.gradle
:
ext {
test = "hello world"
}
app
下的build.gradle
task hello {
doLast {
println parent.ext.test
}
}
在终端执行:
./gradlew -q hello
可以正确得到hello world
的输出。
dependencies
:
project
的三方依赖,上面也说过,android
项目的依赖主要来自三种:库依赖,aar依赖,jar依赖,按照来源可分为外部依赖,文件依赖,和项目以来。对于三种依赖的写法稍有不同,
implementation fileTree(include:['*.jar'],dir:'libs') //文件依赖
implementation 'commons-lang:commons-lang:2.6' // 外部依赖
implementation project(path:':sdk') //项目依赖
这里要强调一下在声明依赖的时候,对于库的范围限制的关键字。首先是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.
也就是说呢:api
的依赖会传递给消费者,而implementation
的依赖只对本身可见。举个例子说:
case 1: 自定义了库A,A api
依赖了B,那么当消费者新建了工程,引用了库A时,该消费者依然可以使用库B的api。
case 2: 自定义了库A,A implementation
依赖了B,那么当消费者新建了工程,引用了库A时,想用B的api
,只有重新创建对B的依赖。
这样万一库A升级了,不再依赖B,那么消费者的项目就不会崩溃。
用一个官方文档上的java
代码,就恨容易看清楚了。
// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
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) {
GetMethod method = new GetMethod(url);
try {
int statusCode = doGet(method);
return method.getResponseBody();
} catch (Exception e) {
ExceptionUtils.rethrow(e); // this dependency is internal only
} finally {
method.releaseConnection();
}
return null;
}
// GetMethod is used in a private method, so doesn't belong to the API
private int doGet(GetMethod method) throws Exception {
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + method.getStatusLine());
}
return statusCode;
}
}
dependencies {
api 'commons-httpclient:commons-httpclient:3.1'
implementation 'org.apache.commons:commons-lang3:3.5'
}
如果有一个lib
模块下有这个类,那么由于开放了public
的构造函数,这个构造函数就会被消费者调用,那么httpClient
就需要暴露给消费者。为了保证库的版本一致性,这个时候用api
的依赖方式会更好。而在doGet
方法中,由于是private
,只对类内有效,所以只需要用implementation
的依赖方式即可。
Configuration name | Description |
---|---|
api | This is where you should declare dependencies which are transitively exported to consumers, for compile. |
implementation | This is where you should declare dependencies which are purely internal and not meant to be exposed to consumers. |
compileOnly | This is where you should declare 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 | This is where you should declare dependencies which are only required at runtime, and not at compile time. |
四. gradle task
项目和build.gradle
文件一一对应,但build.gradle
中可以有任意个task
. 而task
相当于java
类中的一个方法,可以处理一段逻辑。
task 的创建
task hello {
println "hello world 1"
}
task(hello2) {
println "hello world 2"
}
task("hello3") {
group = 'hello'
doFirst {
println "hello world3"
}
}
tasks.create(name:"hello4",group:"hello") {
doLast {
println "hello world4"
}
}
task 的依赖
hello3.mustRunAfter hello4
hello4.dependsOn hello2
执行命令./gradlew -q hello3 hello4
hello world 2
hello world4
hello world3
通过mustRunAfter
和dependsOn
就可以保证hello4
这个task
执行在hello2
和hello3
之间。
hello3.finalizedBy hello4
意思是hello3
执行完之后,必须要执行hello4
。 比如我们的集成测试task
就需要在单元测试task
执行之后才能执行。
执行命令./gradlew -q hello3
输出结果:
hello world3
hello world4
task 的输入输出
除了上述的依赖关系来连接两个task
外,还可以通过task
的input
和output
来关联两个task
. 另外gradle
通过与上一次执行task
的输入输出,如果没有发生变化,那么就不会重复执行该task
, 并且在终端上也会提示UP-TO-DATE
我们定义一个任务writeTask
,执行的逻辑就是将版本信息写入到releases.xml
中,然后在每次clean
这个task
之后执行它。然后我们执行两次clean
.
ext {
versionName='1.0.0'
versionCode='100'
versionInfo='app 的第一个版本,上线了一些基础核心的功能。'
destFile = file('releases.xml')
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
}
}
task writeTask {
inputs.property("versionCode", this.versionCode)
inputs.property("versionName", this.versionName)
inputs.property("versionInfo", this.versionInfo)
outputs.file destFile
doLast {
def data = inputs.getProperties()
destFile.withWriter('utf-8') {
writer -> writer.writeLine data.toMapString()
}
}
}
tasks.getByName("clean").finalizedBy(writeTask)
第一次输入:./gradlew clean
> Task :app:writeTask
Putting task artifact state for task ':app:writeTask' into context took 0.0 secs.
Up-to-date check for task ':app:writeTask' took 0.002 secs. It is not up-to-date because:
Task ':app:writeTask' has additional actions that have changed
:app:writeTask (Thread[Task worker for ':',5,main]) completed. Took 0.013 secs.
第二次输入./gradlew clean
> Task :app:writeTask UP-TO-DATE
Putting task artifact state for task ':app:writeTask' into context took 0.0 secs.
Skipping task ':app:writeTask' as it is up-to-date (took 0.0 secs).
:app:writeTask (Thread[Task worker for ':',5,main]) completed. Took 0.001 secs.
第二次执行的时候会提示UP-TO-DATE
,提示已经是最新的了。
五. gradle plugin
定义gradle
插件主要有三种方式:
脚本
继承
plugin
这个接口的自定义类远程仓库的jar包
所以对这三种方式的脚本的引入也是有区别的。这里呢,一一举例说明。
第一种
比较好理解。 通过新建gradle
文件,然后利用apply from
即可引入。比如,我想将我发包的脚本写成一个插件,引入进来。
apply plugin: 'maven'
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = "aa"
pom.artifactId = "bb"
pom.version = "cc"
repository(url: "http://www.baidu.com") {
authentication(userName: "admin", password: "123")
}
snapshotRepository(url: "http://www.baidu.com") {
authentication(userName: "admin", password: "13")
}
}
}
}
}
然后我在我的app
模块下的build.gradle
引入这个plugin
就可以了
apply from:rootProject.file("release.gradle")
第二种
在GreetingPlugin.groovy
新建类GreetingPlugin
实现接口Plugin<Project>
,在方法apply
中定义需要执行的任务。文件目录结构如下:
import org.gradle.api.Plugin
import org.gradle.api.Project
class GreetingPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task('sayHello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
在文件GreetingPlugin.properties
中添加如下代码
implementation-class=GreetingPlugin
在app
模块下的build.gradle
文件下添加如下代码即可。
apply plugin:GreetingPlugin
输入命令./gradlew -q sayHello
输出:
Hello from the GreetingPlugin
第三种
先上传到maven
.
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
compile gradleApi()
compile localGroovy()
}
repositories {
mavenCentral()
}
group='org.gradle:customPlugin'
version='1.0-SNAPSHOT'
uploadArchives
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
然后在buildscript
块下写上依赖的classpath
即可。
buildscript {
repositories {
maven {
url = uri(repoLocation)
}
}
dependencies {
classpath 'org.gradle:customPlugin:1.0-SNAPSHOT'
}
}
apply plugin: 'GreetingPlugin'
这样呢,gradle
差不多就介绍完了,还有一些其他的模块,比如sourceSet
,buildType
这些各位可以自己查看文档,应该很容易理解了。