前言
在AS中,Gradle是用于帮助我们构建app的,而构建的过程包括编译、打包等,我们可以为Gradle指定构建规则,然后它就会根据我们的“命令”自动为我们构建app。
有些同学可能会有疑问:“我用AS打包的时候不记得给Gradle指定过什么构建规则呀,最后不还是能搞出来个apk。”实际上,app的构建过程都是大同小异的,有一些过程是“通用”的,也就是每个app的构建都要经历一些公共步骤。因此,在我们在创建工程时,AS已经自动帮我们生成了一些通用的构建规则,很多时候我们甚至完全不用修改这些规则就能完成我们app的构建。
而有些时候,我们会有一些个性化的构建需求,比如我们引入了第三方库,或者我们想要在通用构建过程中做一些其他的事情,这时我们就要自己在系统默认构建规则上做一些修改。这时候我们就要自己向Gradle“下命令”了,而此时我们就需要用Gradle能听懂的话了,也就是Groovy。Groovy是一种基于JVM的动态语言,对Groovy语法不了解的可以看看【Gradle系列】Groovy筑基(一)变量与闭包、【Gradle系列】Groovy筑基(二)数据结构与文件操作这两篇文章。
本篇要讲的核心内容是:
- Gradle的生命周期
- Gradle的核心元素之Project
- Gradle的核心元素之Task
- 生命周期的监听
Gradle的优势
-
在灵活性上,Gradle支持基于Groovy语言编写脚本,侧重于构建过程的灵活性,适合于构建复杂度较高的项目,可以完成非常复杂的构建。
-
在粒度性上,Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的,在我们掌握了这一整套打包流程后,我们就可以通过去修改它的 Task 去动态改变其执行流程。
-
在扩展性上,Gradle 支持插件机制,所以我们可以复用这些插件,就如同复用库一样简单方便。
Gradle的生命周期
Gradle的生命周期可以分为三个部分:初始化阶段、配置阶段和执行阶段。
-
Initialization初始化阶段 Gradle为每个module创建了一个Project实例,在多module的项目构建过程中,Gradle会找出哪些Project实例需要参与到项目的构建中,本质上也就是执行
settings.gradle脚本,从而读取整个项目中有多少个Project实例。 -
Configuration配置阶段 配置阶段的任务是执行各module下的build.gradle脚本,从而完成Project的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task。
每个build.gradle对应一个Project对象,配置阶段执行的代码包括build.gradle中的各种语句、闭包以及Task中的配置语句。
- Execution执行阶段
在配置阶段结束后,Gradle会根据任务Task的依赖关系会创建一个
有向无环图,还可以通过Gradle对象的getTaskGraph方法访问,对应的类是TaskExecutionGraph,然后通过调用./gradlew <任务> 来执行对应的任务。
在初始化阶段与配置阶段之间、配置阶段结束之后、执行阶段结束之后,我们都可以加一些定制化的Hook,如下图所示:
Gradle的核心元素
Project
Gradle为每个module的build.gradle都会创建一个相应的Project领域对象,在编写gradle脚本的时候,我们实际上是在操作诸如Project这样的Gradle领域对象。
那么我们总共由于多少个projects对象呢?projects对象的数量等于module的数量 + 1,也就是所有的module + Root project。大家也可以使用命令行打印出来看一下:./gradlew projects。
Task
一个Project由一个或多个Task组成,它是构建过程中的原子任务,可以使编译class、上传jar包等。在定义一个Task的时候,需要的参数如下:
| 参数 | 含义 | 默认值 |
|---|---|---|
| name | task的名字 | 不能为空,必须指定 |
| type | task的“父类” | DefaultTask |
| overwrite | 是否替换已经存在的task | false |
| dependsOn | task依赖的task集合 | [] |
| group | task属于哪个组 | null |
| description | task的描述 | null |
我们平时执行的最多的命令应该是./gradlew build了吧,而这个命令可以说是最复杂的task命令了,我们也知道它每次都会执行很长时间,而这是因为build的task任务依赖了很多任务,它会把它所依赖的task执行完之后,才会执行自己的task。
定义一个task的方式有很多种,而task也分为两种类型:配置型Task和动作型Task。配置型Task会在Gradle的配置阶段执行,而动作型Task会在Gradle的执行阶段执行。
- 配置型Task 下面来看一个定义配置型task的简单例子:
task hello {
println "Hello World"
}
执行task的输出结果:
可以看到,这个task是在配置阶段就开始执行的。那如果我们希望task在执行阶段执行的话要怎么做呢?可以看下面的动作型task的示例。
- 动作型Task 下面先看一个动作型task的简单例子:
task hello {
doLast {
println "Hello World"
}
}
一个Task包含若干Action。所以,Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的Action,Action就是一个闭包。
执行结果如下:
可以看到,现在task是在执行阶段执行的了。但是上面举的例子有点简单,下面来看一个稍微复杂一点点的例子来加深对task action的学习。
class CreateFileTask extends DefaultTask {
@Input
@Optional
String inputPath
@OutputFile
@Optional
File outputPath
@TaskAction
void action() {
println "output path: $outputPath"
println "input path: $inputs.files.singleFile"
def inFile = inputs.files.singleFile
def outfile = outputs.files.singleFile
outfile.createNewFile() // 创建文件
outfile.text = inFile.text // 填充文件的内容为build.gradle文件中的内容
}
}
tasks.create('createFileTask', CreateFileTask) {
// 在当前目录定义一个文件
outputPath = file('helloWorld.txt')
// 使用当前目录下的build.gradle内容作为需要填充的文件内容
inputs.file file('build.gradle')
outputs.file file('helloWorld.txt')
}
这个文件笔者是直接在Android工程的app目录下的build.gradle直接写的,大家也可以自行创建一个新的groovy文件进行编写。
可以看到,一开始通过继承DefaultTask来自定义一个Task,这个Task的主要作用是创建一个新的文件helloWorld.txt,并把build.gradle中的所有内容填充到helloWorld.txt中,最后通过tasks.create('createFileTask', CreateFileTask)来把任务加入tasks中。
大家可以看一下这里定义task使用的是tasks.create(),那么这种定义task的方法与上面直接使用task xxx有什么区别呢?其实通过task xxx的方法创建的Task都被存放在了TaskContainer中,而Project又维护了一个TaskContainer类型的属性tasks,那么我们完全可以直接向TaskContainer里面添加Task,而TaskContainer向我们提供了大量重载的create()方法用于添加Task。
我们来看一下执行task的结果:
可以看到我们的工程目录下多了一个helloWorld.txt。那么现在关于动作型Task已经讲解完了。
生命周期的监听
在介绍完了Gradle的生命周期、Project以及Task之后,我们可以来学习一下Gradle生命周期的监听了。只有学会了生命周期的监听,我们才可以根据需求定制化一些Hook。
在Project中监听生命周期
我们先来看一下平时开发中最常用的监听方法:
//在 Project 进行配置前调用
void beforeEvaluate(Closure closure)
//在 Project 配置结束后调用
void afterEvaluate(Closure closure)
下面用个简单的例子来验证一下结果:
项目根目录下的settings.gradle
include ':app'
println '初始化阶段开始...'
项目根目录下的build.gradle
// 对子模块进行配置
subprojects { sub ->
sub.beforeEvaluate { proj ->
println "子项目beforeEvaluate回调..."
}
}
app目录下的build.gradle
println "app project配置开始"
afterEvaluate {
println "app project afterEvaluate回调..."
}
task appTest {
println "app project任务配置"
doLast {
println "执行子项目任务..."
}
}
打印结果如下所示:
在Gradle中监听生命周期
在setting.gradle中添加:
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println '构建开始'
}
@Override
void beforeSettings(Settings settings) {
super.beforeSettings(settings)
}
/**
* settings.gradle配置完后调用,只对settings.gradle设置生效
*/
@Override
void settingsEvaluated(Settings settings) {
// 注意:如果在这里通过 settings.gradle.rootProject 访问 Project 对象时会报错,因为还未完成 Project 的初始化。
println 'settings:执行settingsEvaluated...'
}
/**
* 当settings.gradle中引入的所有project都被创建好后调用,只在该文件设置才会生效
*/
@Override
void projectsLoaded(Gradle gradle) {
println 'settings:执行projectsLoaded...'
println '初始化结束,可访问根项目:' + gradle.gradle.rootProject
}
/**
* 所有project配置完成后调用
*/
@Override
void projectsEvaluated(Gradle gradle) {
println 'settings: 执行projectsEvaluated...'
}
@Override
void buildFinished(BuildResult buildResult) {
println '构建结束'
}
})
/**
* 在project进行配置前调用,child project必须在root project中设置才会生效,root project必须在settings.gradle中设置才会生效
*/
gradle.beforeProject {
println "settings:执行${it.name} beforeProject"
}
/**
* 在project配置后调用
*/
gradle.afterProject {
println "settings:执行${it.name} afterProject"
}
下面来看一下输出结果:
在Task中监听生命周期
Gradle在配置完成之后,会对所有的task生成一个有向无环图,这里叫做task执行图,他们决定了task的执行顺序等,而task的监听是通过TaskExecutionGraph这个类来进行的。
TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
println "task whenReady"
}
taskGraph.beforeTask { Task task ->
println "任务名称:${task.name} beforeTask"
}
taskGraph.afterTask { Task task ->
println "任务名称:${task.name} afterTask"
}
这里笔者是通过执行./gradlew build任务来测试的,由于打印出来的task太多,这里就不作打印结果的展示了,有兴趣的可以自行测试一下。
总结
本篇主要介绍了Gradle作为自动化构建工具的优势、Gradle的生命周期、Project与Task的介绍以及在Gradle中的生命周期监听。