Gradle构建分析与Task详解

前言

我们现在已经对如何使用Gradle来构建Android工程有了认识,并且大致了解了Groovy的语法和Maven仓库的相关知识,本篇文章计划从原理和API接口角度来分析一下我们配置的脚本文件,以及是如何工作的,只有学会了如何查看API文档才能灵活的去配置构建。

我们已经知道Gradle中待编译的工程叫project,每个project由多个task来组成,而这些task就是具体完成任务的基本单位。你是否还记得第一次使用Android Studio来创建新工程,发现里面默认有很多任务,它们是怎么来的呢?它们又能做什么呢?

Gradle的Task列表

构建分析

Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。

1
2
3
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'com.android.application'

唯一不同的是插件javagroovy是内置的标准gradle插件,而com.android.application是第三方插件。这个区别就在于是否需要使用dependencies { }来引入插件的库。

目前而言Gradle框架内置了4个语言相关插件(分别是:java、groovy、scala、antlr),后面可能会更多。这些插件添加了让各种语言可以在 JVM 中被编译和执行的支持。另外还内置了几个实验性的语言插件:assembler、c、cpp、objective-c、objective-cpp、windows-resource.

除了语言支持相关插件外还有一些集成插件,例如:

插件说明
application添加了一些用于运行和捆绑 Java 项目的任务作为命令行应用程序
maven添加将项目发布到 Maven 仓库的支持
war添加装配 web 应用程序的 WAR 文件的支持
maven-publish这个插件提供了一个新的 DSL,用于支持发布工件到 Maven 存储库,它改进了现有的 DSL

除了这些还有软件开发相关的插件,例如我们常用的wrapper等。

这里我们需要注意并关心的是这些插件都继承自一些基础插件,但是请注意,它们还没有被视为 Gradle 公共 API 的一部分。因此,这些插件都不在用户指南中记录。你可以参考它们的 API 文档来了解更多关于它们的信息。

插件说明
base添加标准的生命周期任务,并为归档任务默认进行合理的配置
java-base对项目添加源集的概念。它不会添加任何特定的源集
groovy-base向项目添加 Groovy 源集的概念
scala-base向项目添加 Scala 源集的概念
reporting-base将一些共享的约定属性添加到项目中,它们与报告的生成有关

我们暂且不去细究这些内置插件,来看看第三方插件com.android.application的使用。

Gradle基于Groovy,Groovy又基于Java。所以,Gradle执行的时候和Groovy一样,会把脚本转换成Java对象。Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对端:

  • Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
  • Project对象:每一个build.gradle会转换成一个Project对象。
  • Settings对象:显然,每一个settings.gradle都会转换成一个Settings对象。

当我们执行gradle的时候,gradle首先是按顺序解析各个gradle文件, 查看官方文档

第一步:根据settings.gradle配置来创建Settings对象。

1
include ':app', ':buildsrc'

第二步:根据Settings对象的配置来分别寻找build.gradle并创建Project对象。

第三步:检查和加载build.gradle配置中相关task和相关依赖。

在Project对象中调用了接口PluginAware的apply()函数,这其实就是加载插件了:

Modifier and TypeMethodDescription
voidapply​(Closure closure)Applies zero or more plugins or scripts.
voidapply​(Map<String,​?> options)Applies a plugin or script, using the given options provided as a map.
voidapply​(Action<? super ObjectConfigurationAction> action)Applies zero or more plugins or scripts.

此外每一个gradle脚本都实现了Script接口,此接口定义了很多可以在脚本中使用的属性和方法。

脚本块描述
allprojects { }配置此项目及每个子项目共同的属性
artifacts { }依赖配置也可以用来发布文件
buildscript { }用于声明gardle脚本自身所需要使用的资源,包括依赖项、maven仓库地址、第三方插件等
configurations { }配置此项目的依赖的配置文件
dependencies { }配置此项目的依赖项
repositories { }配置此项目的库存储
sourceSets { }配置此项目的源文件
subprojects { }配置此项目的子项目
publishing { }配置 PublishingExtension 添加发布插件

我们的Android Gradle 插件继承自Application,更多DSL相关官方文档请查看这里

现在来回答上面所提到的问题,事实上我们新建工程都会有很多固定的默认tasks,比如help,我们可以使用gradle help --task tasks来查看指定Task的帮助。同样的在Android Gradle插件中也默认创建了很多与之相关的tasks,这些task是构建Android工程的基础,涵盖了一些基础构建和自带其他工具构建任务, 下面列出几个常用的help分类的task。

| 分组 | task | type | 说明 |
| help | tasks | TaskReportTask |显示项目中的任务列表 |
| help | projects | ProjectReportTask | 显示构建中的项目列表 |
| help | dependencies | DependencyReportTask | 显示项目的依赖关系树 |

最后再强调一点,请详细查看该文档, 所有的基础task定义都在这里,这也是理解默认tasks的关键。

Task详解

我们前面已经提到如何去自定义一个task来执行一段任务,例如下面这样:

1
2
3
task testTask{
println "hello world"
}

你会发现这个task会在config testTask后执行,也就是说上面的打印会在代码配置阶段执行,其实很多时候我们需要的是在构建阶段执行,所以我们需要将逻辑定义在doFirst或者doLast方法中来执行。

1
2
3
4
5
6
7
8
9
10
11
task testTask{
println "hello world"
}

testTask.doFirst{
println "do First"
}

testTask.doLast{
println "do Last"
}

这样配置的执行结果如下(注意前面提到过doLast还有一种 <<的缩写形式):

1
2
3
4
$ hello world
$ app: testTask
$ do First
$ do Last

其实Gradle已经为我们提供了一些方便的基础Task,例如:Copy、Delete、Sync等,所以我们可以直接继承它们来实现对应逻辑。

更多基础Task可参考官方文档

1
task testTask(type: Copy) {  /* ... */}

我们还可以通过API的方式动态创建Task并制定参数,可以指定的参数如下:

参数含义默认值
nametask的名字不能为空,必须指定
typetask的父类DefaultTask
overwrite是否替换已经存在的taskfalse
dependsOntask依赖的task集合[]
grouptask属于哪个组null
descriptiontask的描述null
1
2
3
4
5
6
7
8
9
10
11
12
task myTask1 << {
println "execute myTask1"
}

task myTask2 << {
println "execute myTask2"
}

// 定义一个名字为rygTask的task,属于renyugang分组,并且依赖myTask1和myTask2两个task。
project.task('rygTask', group: "renyugang", description: "我自己的Task", dependsOn: ["myTask1", "myTask2"] ).doLast {
println "execute rygTask"
}

尝试执行gradle rygTask,结果如下:

1
2
3
4
5
6
7
8
:app:myTask1
execute myTask1

:app:myTask2
execute myTask2

:app:rygTask
execute rygTask