概述

目前Android Studio中建立的工程都是基于gradle进行构建的,Gradle框架是使用Groovy语言实现的,关于Groovy语言的学习将不再赘述可以参考(精通Groovy),不光是在项目工程配置中通过配置gradle,比如extention、SourceSet;打包生成apk过程,目前很多技术领域开始使用Gradle的plugin 在transform阶段做些特殊操作,比如模块化、热修复、SPI的优化等等。

重要的概念

在进行本文之前,首先来了解Gradle中两个核心的概念

  • Project
    每次构建至少由一个project构成,项目结构中的每个build.gradle文件代表一个project

  • Task
    一个project由一到多个task构成。task又可以细分为3大类,增量、非增量、Transform,,在这编译脚本文件中可以定义一系列的task;task 本质上又是由一组被顺序执行的Action对象构成,Action其实是一段代码块,类似于Java中的方法(transform比较特殊点,这个后面再介绍)

Project为Task提供了执行上下文。 我们在打包执行assembleDebug 也是触发Gradle的一个个内置Task,最终将Apk生成

Gradle 构建生命周期

Gradle 也像Activity一样具备有生命周期主要分为3个核心阶段

  • 初始化阶段
  • 配置阶段
  • 执行阶段

下面将深入分析一下各个阶段所做的事情

三个阶段

每次构建的执行本质上是执行一系列的task,并且某些task还需要依赖其他task,这些task的依赖关系都是在构建阶段确定的。每次构建分为3个阶段(Build phases 文档 )

  • Initialization: 初始化阶段

初始化阶段会执行项目根目录下的settings.gradle文件,它用于告诉构建系统哪些模块需要包含到构建过程中,并根据每个build.gradle文件创建一个个与之对应的Project实例。

  • Configuration:配置阶段

这个阶段,会解析Preoject对象,下载依赖文件,通过执行构建脚本来为每个project创建并配置Task,根据task的执行顺序构建Task的有向无环图。主要包括 build.gradle中的配置(如android、plugin、dependencies)、Task配置。在配置完成后,整个项目工程的Task依赖关系都已经确定了(Gradle对象的getTaskGraph对象访问Task,对应TaskExecutionGraph)

  • Execution:执行阶段

这是Task真正被执行的阶段,我们所有的构建,编译,打包,debug,test等都是执行了某一个task

比如我们执行一个打包命令,经历了初始化解析settings文件,配置解析project构建task执行,可以看到执行如下关键task

./gradlew app:assembleDebug –console=plain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Task :app:preBuild UP-TO-DATE
Task :app:preDebugBuild UP-TO-DATE
Task :app:generateDebugBuildConfig
Task :app:javaPreCompileDebug
Task :app:mainApkListPersistenceDebug
Task :app:generateDebugResValues
Task :app:createDebugCompatibleScreenManifests
Task :app:extractDeepLinksDebug
Task :app:compileDebugAidl NO-SOURCE
Task :app:compileDebugRenderscript NO-SOURCE
Task :app:generateDebugResources
Task :app:processDebugManifest
Task :app:mergeDebugResources
Task :app:processDebugResources
Task :app:compileDebugJavaWithJavac
Task :app:compileDebugSources
Task :app:mergeDebugShaders
Task :app:compileDebugShaders
Task :app:generateDebugAssets
Task :app:mergeDebugAssets
Task :app:processDebugJavaRes NO-SOURCE
Task :app:checkDebugDuplicateClasses
Task :app:dexBuilderDebug
Task :app:mergeLibDexDebug
Task :app:mergeDebugJavaResource
Task :app:mergeDebugJniLibFolders
Task :app:validateSigningDebug
Task :app:mergeProjectDexDebug
Task :app:mergeDebugNativeLibs
Task :app:stripDebugDebugSymbols
Task :app:desugarDebugFileDependencies
Task :app:mergeExtDexDebug
Task :app:packageDebug
Task :app:assembleDebug

可以apk构建是非常解耦的,各个Task各司其职,后续升级迭代也非常的方便,而且开发者需要自己定义Task也是可以

监听生命周期

在gradle的构建过程中,在关键的声明周期上 gradle为我们提供了钩子,帮助我们针对项目的需求定制构建的逻辑,网上有一些比较详细的图,如下图所示:

网上有个比较详细的图 借用一下

可以看到,整个Gradle生命周期的流程包含如下四个部分

  1. 解析settings.gradle来获取模块信息 (初始化)
  2. 配置每个模块,此时只是解析project,构建Task图,配置完了之后有个重要的回调 project.afterEvaluate 表示配置完成可以执行task
  3. 执行task

我们可以在生命周期的关键节点 做一些监听

关于可用的钩子可以参考Gradle和Project中的定义,常用的钩子包括:

Gradle

  • beforeProject()/afterProject()
    等同于Project中的beforeEvaluate和afterEvaluate

  • settingsEvaluated()
    settings脚本被执行完毕,Settings对象配置完毕

  • projectsLoaded()
    所有参与构建的项目都从settings中创建完毕

  • projectsEvaluated()
    所有参与构建的项目都已经被评估完

TaskExecutionGraph

  • whenReady()
    task图生成。所有需要被执行的task已经task之间的依赖关系都已经确立

Project

  • beforeEvaluate()
  • afterEvaluate()

可以搜下项目中的配置加深体会。

Project

Project 是与Gradle交互的主接口,比如我们在自定义Plugin时候实现Plugin接口将阐述传给

1
void apply(Project project)

通过Project可以使用gradle的所有特性。前面也提到在初始化阶段 build解析,它本质上就是project对象的委托,在脚本中的配置方法都对应着Project中的API,当构建进程启动后Gradle基于build.gradle中的配置实例化org.gradle.api.Project类,本质上可以认为是包含多个Task的容器,所有的Task都存放在TaskContainer中,Project对象的类图如下所示

image.png

从上图可以了解到ProjectApi主要分为以下部分

  1. Project API: 让当前的Project拥有操作它的父Project以及管理它的子Project的能力,以及相关属性
  2. Task相关API: 为当前Project提供了新增Task以及管理已有Task的能力
  3. File相关API:主要用来操作当前Project下的一些文件处理
  4. Gradle 生命周期API

Project API

先看一个项目结构

  1. getRootProject
    获取根Project对象
1
2
3
4
5
println getRootProject()

//打印结果
> Configure project :app
root project 'GradleDemo'
  1. getRootDir方法
    获取根目录文件夹路径
1
2
3
4
5
println getRootDir()

//打印结果
> Configure project :app
/Users/xsf/learning/GradleDemo
  1. getBuildDir方法

获取当前Project的build文件夹路径

1
2
3
4
5
println getBuildDir()

//打印结果
> Configure project :app
/Users/xsf/learning/GradleDemo/app/build
  1. getParent方法
    获取当前父Parent对象

    1
    2
    3
    4
    5
    println getParent()

    //打印结果
    > Configure project :app
    root project 'GradleDemo'
  2. getAllprojects方法
    获取当前Project及其子Project对象,返回值是一个Set集合

    1
    2
    3
    4
    5
    6
    //当前在根工程的 build.gradle 文件下
    println getAllprojects()

    //打印结果
    > Configure project :
    [root project 'GradleDemo', project ':app']

    或者使用其闭包形式

1
2
3
4
5
6
7
8
allprojects {
println it
}

//打印结果
> Configure project :
root project 'GradleDemo'
project ':app'

我们通常会使用闭包的语法在根 build.gradle 下进行相关的配置,如下:

1
2
3
4
5
6
allprojects {
repositories {
google()
mavenCentral()
}
}
  1. getSubprojects方法

获取当前Project下的所有子Project对象,返回值是一个Set集合

1
2
3
4
5
6
//当前在根工程的 build.gradle 文件下
println getSubprojects()

//打印结果
> Configure project :
[project ':app']

同样也可以使用其闭包形式

1
2
3
4
5
6
7
subprojects {
println it
}

//打印结果
> Configure project :
project ':app'

扩展属性

有2种定义方式

  • ext关键字

    1
    2
    3
    4
    5
    6
    7
    8
    //当前在根 build.gradle 下
    //方式1:ext.属性名
    ext.test = 'test000'

    //方式2:ext 后面接上一个闭包
    ext{
    test1 = 'test111'
    }
  • 在gradle.properties下定义扩展属性

1
test2=test222

文件操作 API

主要也分为2大类

  • 路径获取
  • 文件操作
    包括 文件定位、文件拷贝、文件树遍历
  1. 文件定位
    接收一个相对路径从当前project工程开始查找,如果我们通过new File的方式需要传入一个绝对路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //============================== 1、file 方法应用============================
    //通过 file 方法传入一个相对路径,返回值是一个 file 对象
    println file('../config.gradle').text

    //通过 new File 方式传入一个绝对路径
    def file = new File('/Users/xsf/learning/GradleDemo/config.gradle')
    println file.text

    //上述两者打印结果相同

    //============================== 2、files 方法应用============================
    //通过 files 方法传入多个相对路径,返回值是一个 ConfigurableFileCollection 即文件集合
    files('../config.gradle','../build.gradle').each {
    println it.name
    }

    //打印结果
    > Configure project :app
    config.gradle
    build.gradle
  2. copy文件拷贝
    Project对象提供了copy方法,使得我们拷贝文件或者文件夹变得十分简单,该方法接受一个闭包,具体可以参考传送门

文件拷贝

1
2
3
4
5
6
7
8
9
10
11
12
//1、传入路径
copy {
from getRootDir().path + "/config.gradle"
into getProjectDir().path
}

//2、传入文件
copy {
from file('../config.gradle')
into getProjectDir()
}

文件夹拷贝(如果没有会创建)

1
2
3
4
copy {
from file('../gradle/')
into getProjectDir().path + "/gradle/"
}
  1. fileTree文件树映射

该方法十分方便的讲一个目录转变成文件树,比如变量根目录gradle文件夹,打印文件以及文件夹名称

1
2
3
4
5
6
7
8
9
10
11
fileTree('../gradle/'){ FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
println fileTreeElement.name
}
}

//打印结果
> Configure project :app
wrapper
gradle-wrapper.jar
gradle-wrapper.properties

通常我们在build.gradle中看到如下的配置

1
implementation fileTree(include: ['*.jar'], dir: 'libs')

其实就是调用了fileTree接收map参数的重载方法

1
ConfigurableFileTree fileTree(Map<String, ?> var1);

作用就是引入当前project目录下的libs文件夹下的所有jar包

  1. exec外部命令执行

Project对象提供了exec方法,方便我们执行外部的命令,比如一些git操作,文件移动等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
task taskMove() {
doLast {
// 在 gradle 的执行阶段去执行
def sourcePath = buildDir.path + "/outputs/apk"
def destinationPath = getRootDir().path
def command = "mv -f $sourcePath $destinationPath"
exec {
try {
executable "bash"
args "-c", command
println "The command execute is success"
} catch (GradleException e) {
e.printStackTrace()
println "The command execute is failed"
}
}
}
}

Task

Task是gradle执行的一个任务,gradle将一个个Task串起来完成具体的任务构建,task是gradle的原子性操作,简单理解为task是Gradle脚本中的最小可执行单元,下面是Task的类图

image.png

Task介绍

  1. Task的几种常见写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
task myTask1 {
doLast {
println "doLast in task1"
}
}

task myTask2 << {
println "doLast in task2"
}

//采用 Project.task(String name) 方法来创建
project.task("myTask3").doLast {
println "doLast in task3"
}

//采用 TaskContainer.create(String name) 方法来创建
project.tasks.create("myTask4").doLast {
println "doLast in task4"
}

project.tasks.create("myTask5") << {
println "doLast in task5"
}
  • doFirst 表示:Task 执行最开始时被调用的 Action
  • doLast 表示: task 执行完时被调用的 Action

上面都是在gradle 简单创建的task,通常我们在自定义Plugin时需要自定义继承Task通过注解@TaskAction来表示自定义Task,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class XmlViewScanTask extends DefaultTask {
private BaseVariant variant

@Inject
XmlViewScanTask(BaseVariant variant) {
this.variant = variant
}

/**
* 执行 xml 扫描 Task
*/
@TaskAction
void performXmlScanTask() {
try {
println 'performXmlScanTask start...'
}

关于自定义Task下文会有详细介绍。

除了这些默认的 Gradle还内置了一些常用的类型,比如

  • Copy
  • Delete
  • Sync task
    更多类型可以参考官方文档 传送门
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 1、删除根目录下的 build 文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 2、将 doc 复制到 build/target 目录下
task copyDocs(type: Copy) {
from 'src/main/doc'
into 'build/target/doc'
}
// 3、执行时会复制源文件到目标目录,然后从目标目录删除所有非复制文件
task syncFile(type:Sync) {
from 'src/main/doc'
into 'build/target/doc'
}

在 Gradle 中定义 Task 的时候,可以指定更多的参数,如下所示:

参数名 含义 默认值
name task的名字 必须指定,不能为空
type task的父类 默认值为org.gradle.api.DefaultTask
overwrite 是否替换已经存在的同名task false
group task所属的分组名 null
description task的描述 null
dependsOn task依赖的task集合
constructorArgs 构造函数参数
  1. Task 依赖

gradle中任务的顺序是不确定的,通过task之间的依赖关系,gradle在配置阶段就能准确的构建出有向无环图,使用task的dependsOn()方法,允许我们为task声明一个或者多个task依赖。

1
2
3
4
5
6
7
8

task test(dependsOn:[second,first]){
doLast{
println("first")
}
}

third.dependsOn(test)

Task 管理

在前面的 Project Api类图中我们可以看到Project.getTasks()来获取TaskContainer实例,该类
是可以用来管理当前Task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

//创建task
create(name: String): Task
create(name: String, configure: Closure): Task
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task

//当task被加入到TaskContainer时的监听
whenTaskAdded(action: Closure)


//当有task创建时
getTasks().whenTaskAdded { Task task ->
println "The task ${task.getName()} is added to the TaskContainer"
}

//采用create(name: String)创建
getTasks().create("task1")

//采用create(options: Map<String, ?>)创建
getTasks().create([name: "task2", group: "MyGroup", description: "这是task2描述", dependsOn: ["task1"]])

//采用create(options: Map<String, ?>, configure: Closure)创建
getTasks().create("task3", {
group "MyGroup"
setDependsOn(["task1", "task2"])
setDescription "这是task3描述"
})

自定义Task

Gradle 中通过 task 关键字创建的 task,默认的父类都是 org.gradle.api.DefaultTask,这里定义了一些 task 的默认行为。看看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//自定义Task类,必须继承自DefaultTask
class SayHelloTask extends DefaultTask {

String msg = "default name"
int age = 18

//构造函数必须用@javax.inject.Inject注解标识
@javax.inject.Inject
SayHelloTask(int age) {
this.age = age
}

//通过@TaskAction注解来标识该Task要执行的动作
@TaskAction
void sayHello() {
println "Hello $msg ! age is ${age}"
}

}

//通过constructorArgs参数来指定构造函数的参数值
task hello1(type: SayHelloTask, constructorArgs: [30])

//通过type参数指定task的父类,可以在配置代码里修改父类的属性
task hello2(type: SayHelloTask, constructorArgs: [18]) {
//配置代码里修改 SayHelloTask 里的字段 msg 的值
msg = "xsf"
}

Gradle所说的Task是org.gradle.api.Task接口,默认实现是org.gradle.api.DefaultTask类,其类图如下

从这里可以看到丰富的操作Task的API,除了DefaultTask类外

自定义Task挂载到构建流程

从前文我们知道gradle提供了一系列生命周期让我们去操作,我们可以在需要的时刻挂在自己需要的task,通常有2种方式

  1. 通过dependsOn

让构建流程中的Task依赖我们自定义的Task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}

afterEvaluate {
//1、找到需要的构建流程 Task
def mergeDebugResources = tasks.findByName("mergeDebugResources")
//2、通过 dependsOn 指定
mergeDebugResources.dependsOn(myCustomTask)

//如果换成下面这种写法则自定义 Task 不会生效
//myCustomTask.dependsOn(mergeDebugResources)
}


执行下面的命令查看 tree

1
./gradlew build taskTree --no-repeat


build一下 就可以看到Task打印的日志了

  1. 通过 finalizedBy指定

这个关键字时表示在某个task执行完成之后,指定需要执行的Task

1
2
3
4
5
6
7
8
9
10
11
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}

afterEvaluate {
def mergeDebugResources = tasks.findByName("mergeDebugResources")
//将 myCustomTask 挂接在 mergeDebugResources 后面执行
mergeDebugResources.finalizedBy(myCustomTask)
}
  1. mustRunAfter配合dependsOn指定
    在2个Task之间插入自定义的Task
1
2
3
4
5
6
7
8
9
10
11
12
13
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}

afterEvaluate {
//在 mergeDebugResources 和 processDebugResources 之间插入 myCustomTask
def processDebugResources = tasks.findByName("processDebugResources")
def mergeDebugResources = tasks.findByName("mergeDebugResources")
myCustomTask.mustRunAfter(mergeDebugResources)
processDebugResources.dependsOn(myCustomTask)
}

上述Task依赖变化过程:

1
2
3
processDebugResources -> mergeDebugResources 
===>
processDebugResources -> myCustomTask -> mergeDebugResources

小结

本文主要介绍了

  • gradle生命周期
  • Project
  • Task
  • 自定义Task 挂载到构建流程的3种方式

感谢阅读希望能给你带来帮助~

参考