Android Gradle学习(三):Task进阶学习

前面通过很多范例讲了在 build.gradle 中怎么创建 Task,但是 Task 到底是个什么东西,它里面有些什么,我们并不清楚,本文试图揭开 Task 神秘的面纱。

1. Task类图

Gradle 所说的 Task 是 org.gradle.api.Task 接口,默认实现是 org.gradle.api.DefaultTask 类,其类图大概如下(只节选了比较重要的部分):


Task类图

我们只需关注 Task 接口即可,从这里基本可以了解到 Task 有哪些特性。

2. Task接口解析

为了了解每个接口方法的含义,我们直接用代码来测试验证下:

class SayHelloTask extends DefaultTask {
    
    String msg = "default name";
    int age = 20

    @TaskAction
    void sayHello() {
        println "Hello $msg ! Age is ${age}"
    }

}

task test1 << {
    println "task test1 exec..."
}
task test2 << {
    println "task test2 exec..."
}
task test3 << {
    println "task test3 exec..."
}
task hello(type: SayHelloTask, group: "MyGroup")

//对task进行配置,
hello.configure {
    println "hello task configure"
    msg = "hjy"
}

//获取task的名称
println "task name is ${hello.getName()}"
//获取task的组名
println "task group is ${hello.getGroup()}"

//设置task里的属性值,设置 age = 70
hello.setProperty("age", 70)
//获取task里的某个属性值
println "task msg is ${hello.property('msg')}"

//设置依赖的task,只有test1 task执行完后才会执行hello task
hello.dependsOn(test1)
//设置终结者任务,执行完hello task之后会执行test2 task,通常可以用该方法做一些清理操作
hello.finalizedBy(test2)

//如果同时执行hello、test3这2个task,会确保test3执行完之后才执行hello这个task,用这个来保证执行顺序
hello.setMustRunAfter([test3])

//设置满足某个条件后才执行该task
hello.setOnlyIf {
    //只有当 age = 70 时,才会执行task,否则不会执行
    return hello.property("age") == 70
}

我们执行任务gradle hello test3,结果如下:

> Configure project :
hello task configure
task name is hello
task group is MyGroup
task msg is hjy

> Task :test3
task test3 exec...

> Task :test1
task test1 exec...

> Task :hello
Hello hjy ! Age is 70

> Task :test2
task test2 exec...

3. TaskContainer接口解析

TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。

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 的方法:

//当有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描述"
})

执行命令gradle -q tasks --all,查看是否创建成功,结果如下:

MyGroup tasks
-------------
task2 - 这是task2描述
task3 - 这是task3描述

Other tasks
-----------
task1

我们再来试试查找 task 的方法:

//通过名字查找指定的task
def task3 = getTasks().findByName("task3")
println "findByName() return task is " + task3

def taskList = getTasks().withType(DefaultTask)
def count = 0
//遍历所有的task,打印出其名字
taskList.all { Task t ->
    println "${count++} task name is ${t.name}"
}

4. Task增量构建

Gradle 支持一种叫做 up-to-date 检查的功能,也就是常说的增量构建。Gradle 的 Task 会把每次运行的结果缓存下来,当下次运行时,会检查输出结果有没有变更,如果没有变更则跳过运行,这样可以提高 Gradle 的构建速度。

通常,一个 task 会有一些输入(inputs)和一些输出(outputs),task 的输入会影响其输出结果,以官网中的一张图为例:

Example task inputs and outpus

图中表示一个 java 编译的 task,它的输入有2种,一是 JDK 版本号,一是源文件,它的输出结果为 class 文件,只要 JDK 版本号与源文件有任何变动,最终编译出的 class 文件肯定是不同的。当我们执行过一次编译任务后,再次运行该 task ,如果发现它的输入没有任何改变,那么它编译后的结果肯定也是不会变化的,可以直接从缓存里获取输出,这样 Gradle 会标识该 task 为 UP-TO-DATE,进而跳过该 task 的执行。

4.1 TaskInputs、TaskOutputs

那么怎么实现一个增量构建呢?一个增量构建必须至少指定一个输入、一个输出,从前面 Task 的类图中可以看到,Task.getInputs() 对象类型为 TaskInputs,Task.getOutputs() 对象类型为 TaskOuputs,从中也可以看到inputs、outputs都支持哪些数据类型。同样以一个简单例子来说明:

task test1 {
    //设置inputs
    inputs.property("name", "hjy")
    inputs.property("age", 20)
    //设置outputs
    outputs.file("$buildDir/test.txt")

    doLast {
        println "exec task task1"
    }
}

task test2 {
    doLast {
        println "exec task task2"
    }
}

连续2次运行task,执行命令gradle test1 test2,结果如下:

//第一次的运行结果
> Task :test1
exec task task1

> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

//第二次的运行结果
> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date

从结果中可以看到,第2次运行时,test1 task 并没有运行,而是被标记为 up-to-date,而 test2 task 则每次都会运行,这就是典型的增量构建。

4.2 taskInputs、taskOutputs注解

当你自定义 task class 时,可以通过注解来实现增量构建,这是一种更加灵活方便的方式。我们常用的注解包括:

注解名 属性类型 描述
@Input 任意Serializable类型 一个简单的输入值
@InputFile File 一个输入文件,不是目录
@InputDirectory File 一个输入目录,不是文件
@InputFiles Iterable<File> File列表,包含文件和目录
@OutputFile File 一个输出文件,不是目录
@OutputDirectory File 一个输出目录,不是文件
@OutputFiles Map<String, File>或Iterable<File> 输出文件列表
@OutputDirectories Map<String, File>或Iterable<File> 输出目录列表

我们通过自定义一个 task class 来演示一下用法:

class SayHelloTask extends DefaultTask {
    
    //定义输入
    @Input
    String username;
    @Input
    int age

    //定义输出
    @OutputDirectory
    File destDir;

    @TaskAction
    void sayHello() {
        println "Hello $username ! age is $age"
    }

}

task test(type: SayHelloTask) {
    age = 18
    username = "hjy"
    destDir = file("$buildDir/test")
}

执行该 task 之后,会自动生成一个 $buildDir/test 文件目录,当你再次运行时就实施增量构建。但是当你修改 age、username 的值,或者删除磁盘上 $buildDir/test 目录,再次运行该 task ,该 task 就会重新运行。

系列文章

Android Gradle学习(一):Gradle基础入门
Android Gradle学习(二):如何创建Task
Android Gradle学习(三):Task进阶学习
Android Gradle学习(四):Project详解
Android Gradle学习(五):Extension详解
Android Gradle学习(六):NamedDomainObjectContainer详解
Android Gradle学习(七):Gradle构建生命周期
Android Gradle学习(八):统计Task执行时长
Android Gradle学习(九):一些有用的小技巧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342