Gradle从0入门到实战系列【四】build.gradle之Task

简介: 任务是gradle的最小执行单元,一个build.gradle是由一系列的task组成,重要性不言而喻,在前面课程中我们或多或少做了一些了解,接下来我们真正认识task。

前置准备

参考Gradle从0入门到实战系列【三】build.gradle之Project的前置准备工作,创建好demo项目

定义任务

定义task有很多种方式,但我们记住常用的几种即可

  1. 定义task闭包

    //直接声明
    task taskOne{
      println "method1"
    }
    
    //参数定义名称
    task('taskTwo'){
      println "method2"
    }
    
    //使用tasks集合属性,引用了TaskContainer的一个实例对象。我们还可以使用Project.getTasks()
    tasks.create('taskThree'){
      println "method3"
    }
    
    //使用with
    tasks.with(){
      println "methodContainer"
    }
    
    //使用withType声明
    tasks.withType(Test){
      print "method5"
    }
    //使用register,register执行的是延迟创建。也就是说只有当task被需要使用的时候才会被创建。
    tasks.register('taskSix'){
      println "method6"
    }
    
    //这种写法gradle5.0就已经废弃
    task deprecatedTask << {
      println "execute deprecatedTask"
    }

    image.png

  2. TaskContainer解析
    tasks实际上就是TaskContainer的实例对象的映射属性,TaskContainer实现了TaskCollectionPolymorphicDomainObjectContainer,所以tasks具有以上2个类的所有行为,其构建如下

      public interface TaskContainer extends TaskCollection<Task>, PolymorphicDomainObjectContainer<Task>{
        //跟进去就会发现TaskCollection和PolymorphicDomainObjectContainer中有很多关于维护task的方法的定义
      }

    上述Container中可实现的重要方法有如下几个

    • 定位task

      1. findByPath:如果没找到会返回null

        task say
        println tasks.findByPath('say').path 
        println tasks.findByPath(':say').path
      2. getByPath:如果没找到会抛出UnknownTaskException

        task say
        println tasks.getByPath('say').path 
        println tasks.getByPath(':say').path
    • 创建task

      1. 直接创建,使用create,上述定义章节有使用
      2. 延迟创建使用register

          tasks.register('taskSix'){
            println "method6"
          }

        跟进源码定义如下

        TaskProvider<Task> register(String var1, Action<? super Task> var2) throws InvalidUserDataException;

        register返回了一个TaskProvider,和java多线程中的callable类似,当我们调用Provider.get()或TaskCollection.getByName(String)获取task的时候,才会去创建这个task。

    • 替换task(使用不多)
      replace的作用就是创建一个新的task,并且替换掉同样名字的老的task。
  3. 给task配置group和描述

    //group可以将任务分组,在右侧的task列表中能够清晰直观的看到group
    task taskDev1{
      group "dev"
      description "项目描述"
      println "method1"
    }
    task('taskDev2'){
      group "dev"
      println "method2"
    }
    
    task taskTest{
      group "test"
      println "method1"
    }

    image.png

  4. doFirst/doLast体验
    一个Task包含若干Action。Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的Action。Action就是一个闭包。

    //在task闭包体内声明
    task taskDev1{
        //doFirst:task执行时,最开始的操作
        doFirst{
            println "first exec"
        }
        //doLast:task执行时,最后的操作
        doLast{
            println "last exec"
        }
        group "dev"
        println "method1"
    }
    
    //也可以这样写
    task myTask {
        println "config myTask"
    }
    myTask.doFirst {
        println "before execute myTask"
    }
    myTask.doLast {
        println "after execute myTask"
    }
    

任务详细使用

定义Task的时候是可以指定很多参数的,如下所示:

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

带参任务

// 定义一个名字为paramTask的task,属于it235分组,并且依赖myTask1和myTask2两个task。
task myTask1{
}
task myTask2{
}

project.task('paramTask', group: "it235", description: "我自己的Task", dependsOn: ["myTask1", "myTask2"] ).doLast {
    println "execute paramTask"
}

这些参数也可以写在task的闭包体内

task myTask1{
}
task myTask2{
}
task it235Task {
    group "it235"
    description "我自己的Task"
    //任务依赖,先忽略接下来会讲
    dependsOn myTask1, myTask2
    doLast {
        println "execute it235"
    }
}

指定Type

Task创建的时候可以通过 type: SomeType 指定Type,Type其实就是告诉Gradle,这个新建的Task对象会从哪个基类Task派生。比如,Gradle本身提供了一些通用的Task,最常见的有Copy 任务。Copy是Gradle中的一个类。当我们:task myTask(type:Copy)的时候,创建的Task就是一个Copy Task。Gradle本身还提供了其他如:Delete、Sync等供我们使用。

  1. Copy的使用

    实现了CopySpec接口,使用CopySpec.from()方法可以指定源文件,CopySpec.into()方法可以指定目标目录。

    task myTask(type: Copy) {
        configure closure 
    }
  2. 拷贝文件例子

    task copyDocs(type: Copy) {
        from 'src/main/doc'
        into 'build/target/doc'
    }
    
    //这是个Ant filter
    import org.apache.tools.ant.filters.ReplaceTokens
    
    //这是一个闭包
    def dataContent = copySpec {
        from 'src/data'
        include '*.data'
    }
    
    task initConfig(type: Copy) {
        from('src/main/config') {
            include '**/*.properties'
            include '**/*.xml'
            filter(ReplaceTokens, tokens: [version: '2.3.1'])
        }
        from('src/main/config') {
            exclude '**/*.properties', '**/*.xml'
        }
        from('src/main/languages') {
            rename 'EN_US_(.*)', '$1'
        }
        into 'build/target/config'
        exclude '**/*.bak'
        includeEmptyDirs = false
        
        with dataContent
    }
    
    task chVer(type: Copy) {
        from "src/main/manifest/AndroidManifestCopy.xml"  // 复制src/main/manifest/目录下的AndroidManifest.xml
        into 'src/main'  // 复制到指定目标目录
        rename { String fileName -> //在复制时重命名文件
            fileName = "AndroidManifest.xml" // 重命名
        }
    
    }
  3. Zip

    创建ZIP归档文件,默认压缩文件类型为zip。

    task zip(type: Zip) {
        from 'src/dist'
        into('libs') 
    }
  4. 其他任务

    Task Class Description
    Copy 将文件复制到目标目录
    Delete 删除文件和目录
    Exec 执行命令行程序
    GradleBuild 执行Gradle构建
    JavaExec 在一个子进程中执行一个Java程序
    SourceTask 对源文件执行一些操作
    Sync 同步文件或者目录
    Upload 将配置的组件上传到执行的仓库
    WorkResults Helps access trivial WorkResult objects.
  5. 更多示例请戳

通过TaskAction指定一个任务

class GreetingTask extends DefaultTask {
    @Input
    String message = 'This is GreetingTask'
    // @TaskAction 表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行
    @TaskAction
    def hello(){
        println "Hello world. $message"
    }
}
// hello使用了默认的message值
task hello(type:GreetingTask)
//与register相同
tasks.register('hello', GreetingTask)


// 重新设置了message的值
task hello1(type:GreetingTask){
    message ="I am an java developer"
}

任务依赖

task之间的依赖关系是通过task name来决定的。我们可以在同一个项目中做task之间的依赖:

task hello { 
    doLast { println 'Hello www.it235.com!' } 
} 
task intro { 
    dependsOn hello 
    doLast { println "I'm 君哥" } 
} 

也可以跨项目进行task的依赖,如果是跨项目的task依赖的话,需要制定task的路径:

project('it235-service') { 
    task service{ 
        dependsOn ':it235-dao:dao' 
        doLast { println 'service' } 
    } 
} 

project('it235-dao') { 
    task dao{ 
        doLast { println 'dao' } 
    } 
} 

或者我们可以在定义好task之后,再处理task之间的依赖关系:

执行顺序

有时候我们的task之间是有执行顺序的,我们称之为对task的排序ordering。

先看一下ordering和dependency有什么区别。dependency表示的是一种强依赖关系,如果taskA依赖于taskB,那么执行taskA的时候一定要先执行taskB。

而ordering则是一种并不太强列的顺序关系。表示taskA需要在taskB之后执行,但是taskB不执行也可以。

在gradle中有两种order:分别是must run after和should run after。

taskA.mustRunAfter(taskB)表示必须遵守的顺序关系,而taskA.shouldRunAfter(taskB)则不是必须的,在下面两种情况下可以忽略这样的顺序关系: 第一种情况是如果shouldRunAfter引入了order循环的时候。

第二种情况是如果在并行执行的情况下,task所有的依赖关系都已经满足了,那么也会忽略这个顺序。

我们看下怎么使用:

task taskX { 
    doLast { println 'www.it235.com' } 
} 
task taskY { 
    doLast { println 'hello' } 
} 
taskY.mustRunAfter taskX 
//taskY.shouldRunAfter taskX 

条件执行

我们可以给task一些描述信息,这样我们在执行gradle tasks的时候,就可以查看到:

有时候我们需要根据build文件中的某些属性来判断是否执行特定的task,我们可以使用onlyIf :

task hello { 
    doLast { println 'www.it235.com' } 
} 
hello.onlyIf { !project.hasProperty('skipHello') } 

或者我们可以抛出StopExecutionException异常,如果遇到这个异常,那么task后面的任务将不会被执行:

task eat { 
    doLast { println 'We're having dinner.' } 
}

eat.doFirst { 
    if (true) {
        throw new StopExecutionException()
    } 
} 

task myTask { 
    dependsOn('eat') 
    doLast { 
        println 'I am not affected' 
    }
} 

我们还可以启动和禁用task:

myTask.enabled = false

最后我们还可以让task超时,当超时的时候,执行task的线程将会被中断,并且task将会被标记为failed。

如果我们想继续执行,那么可以使用 --continue。

注意, 只有能够响应中断的task,timeout才有用。
import java.time.Duration
task hangingTask() { 
    doLast { 
        Thread.sleep(100000)
    } 
    //设定的超时时间,需要导Duration包
    timeout = Duration.ofMillis(500) 
} 

Gradle命令是支持连在一起拼写的,比如gradle hello hello1,那么gradle将会先执行hello,然后再执行hello1。
Gradle命令是可以缩写的,前提是这个缩写能够唯一地限定一个task。比如rygTask,那么执行的时候就可以缩写为gradle rTask。

task rule

如果我们想要给某些task定义一些规则,那么可以使用tasks.addRule:

tasks.addRule("Pattern: ping<ID>") { 
    String taskName -> 
        if (taskName.startsWith("ping")) { 
            task(taskName) { 
                doLast { println "Pinging: " + (taskName - 'ping') } 
            } 
        } 
} 

上我们定义了一个rule,如果taskName是以ping开头的话,那么将会输出对应的内容。

运行结果:

> gradle -q pingServer1
Pinging: Server1 

我还可以将这些rules作为依赖项引入:

task groupPing { dependsOn pingServer1, pingServer2 } 

Finalizer tasks

和java中的finally一样,task也可以指定对应的finalize task:

task execute { 
    doLast { 
        println '执行程序' 
    } 
} 
task release { 
    doLast { println '释放资源' } 
} 
execute.finalizedBy release 

> gradle -q execute
执行程序
释放资源

finalize task一定会被执行,即使上面的execute中抛出了异常,release也会被成功执行。
image.png

总结

本篇给大家讲解了task在gradle中的作用以及其内部原理,结合前面的project的文章,我们已经初步掌握了build.gradle的基本使用,接下来我们继续学习gradle中另外一个重要的概念:插件

关注君哥继续肝起来

相关文章
|
4月前
|
XML Java 测试技术
Gradle安装部署与基础入门详解
Gradle安装部署与基础入门详解
457 0
|
4月前
|
存储 Java 测试技术
Gradle笔记 四 Gradle的核心 TASK(二)
Gradle笔记 四 Gradle的核心 TASK
42 0
|
4月前
|
Java Maven
Gradle笔记 四 Gradle的核心 TASK(一)
Gradle笔记 四 Gradle的核心 TASK
46 0
|
9月前
|
XML Java Apache
Gradle极简入门
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,而不是传统的XML。Gradle构建脚本是用Groovy编写的,也可以使用Kotlin编写,两者都是基于JVM的语言。
238 0
|
Java 编译器 API
Gradle筑基篇(五)-Gradle自定义插件实战
前面几篇文章笔者对Gradle的一些基础认知,groovy基础语法,以及Gradle 项目中常用的一些api进行了讲解。今天笔者再来讲解一些关于`Gradle插件`的使用
|
前端开发 Java Maven
Gradle build.gradle 文件
Gradle build.gradle 文件
Gradle build.gradle 文件
|
存储 Java 测试技术
|
XML 存储 缓存
|
Java API 容器
【Deprecated】Gradle | 进阶篇(Project & Task & 构建生命周期)
【Deprecated】Gradle | 进阶篇(Project & Task & 构建生命周期)
404 0
【Deprecated】Gradle | 进阶篇(Project & Task & 构建生命周期)