【Gradle】Gradle的生命周期详解

·  阅读 1010

前言

在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的时候,需要的参数如下:

参数含义 默认值
nametask的名字不能为空,必须指定
typetask的“父类”DefaultTask
overwrite是否替换已经存在的taskfalse
dependsOntask依赖的task集合[]
grouptask属于哪个组null
descriptiontask的描述null

我们平时执行的最多的命令应该是./gradlew build了吧,而这个命令可以说是最复杂的task命令了,我们也知道它每次都会执行很长时间,而这是因为build的task任务依赖了很多任务,它会把它所依赖的task执行完之后,才会执行自己的task。

定义一个task的方式有很多种,而task也分为两种类型:配置型Task动作型Task配置型Task会在Gradle的配置阶段执行,而动作型Task会在Gradle的执行阶段执行。

  • 配置型Task 下面来看一个定义配置型task的简单例子:
task hello {
    println "Hello World"
}
复制代码

执行task的输出结果:

image.png

可以看到,这个task是在配置阶段就开始执行的。那如果我们希望task在执行阶段执行的话要怎么做呢?可以看下面的动作型task的示例。

  • 动作型Task 下面先看一个动作型task的简单例子:
task hello {
    doLast {
        println "Hello World"
    }
}
复制代码

一个Task包含若干Action。所以,Task有doFirstdoLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的ActionAction就是一个闭包。

执行结果如下:

image.png

可以看到,现在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的结果:

image.png

image.png

可以看到我们的工程目录下多了一个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 "执行子项目任务..."
    }
}
复制代码

打印结果如下所示:

image.png

在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"
}
复制代码

下面来看一下输出结果:

image.png

在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中的生命周期监听。

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改