Gradle之Project,Task

gradle基本概念

百度百科:Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置。

gradle是一个构建工具,也是一个编程框架

gradle组成:

  • groovy核心语法
  • build script block
  • gradle api

gradle的优势:

  • 相比于maven,ant构建更加灵活
  • 可以服用各种插件
  • 兼容上,兼容maven等所有功能
  • 细粒度,可以自定义编译过程

gradle的生命周期

gradle的生命周期主要分为3个阶段:Initialization初始化阶段,Configuration配置阶段,Execution执行阶段

  1. Initialization:解析真个工程中的所有Project,构建所有Project赌赢的project对象
  2. Configuration:解析project中的所有的task,构建成一个有向无环图
  3. Execution:执行具体的task以及其依赖的task

setting.gradle:这个文件是在初始化阶段执行,一个gradle项目中必须由setting.gradle这个文件,因为它决定了那些项目参与构建。在gradle的构建中这是最先执行的一个文件。

如何监听gradle的生命周期呢

在setting.gradle文件中可以监听初始化阶段和配置之前的阶段

gradle.settingsEvaluated {
    println('初始化阶段开始执行...')
}
gradle.beforeProject {
    println('配置之前调用...')
}

在build.gradle文件中,可以监听配置之后、task的执行阶段和执行完成的阶段

this.afterEvaluate {
    println('配置之后....')
}
gradle.taskGraph.beforeTask {
    println "执行阶段 before task"
}
gradle.taskGraph.afterTask {
    println "执行阶段 afterTask "
}
this.gradle.buildFinished {
    println("执行阶段完毕,构建完毕...")
}

gradle Project

官方文档:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project

在一个gradle项目中,怎么算是一个Project呢,以一个Android项目为例,跟工程算是一个Project,其内部的子module也是一个Project。准确的说,只要该文件夹下有build.gradle文件,它就是一个Project。每一个module在编译期间都会生成一个对应的project对象,我们在build.gradle中编写的代码,其实都是在一个project对象内部编写。

获取一个项目所有的project

this.getAllprojects().eachWithIndex { Project entry, int index ->
    println(entry.name)
}

获取Project中的目录

println(this.project.getRootDir().absolutePath)
println(this.project.getBuildDir().absolutePath)

gradle中拷贝文件,非常简单,比如把app目录下的proguard-rules.pro文件拷贝到根目录下的build文件夹下面

copy {
    from(file('proguard-rules.pro'))
    into(getRootProject().getBuildDir())
}

file(’’)函数可以定位当前project中的某个文件。

Project的依赖:

在一个Android项目的根目录中的build.gradle中,我们都会看到下面的一段配置:

buildscript {
    repositories {
         google()
         jcenter()
          maven{
            url 'http://localhost:8081/xxxx'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
        
    }
    allprojects {
       repositories {
        google()
        jcenter()
    }
}

buildscript 就是依赖配置的核心部分,它接受一个闭包,内部可以设置很多参数,最重要的就是前面的两个repositories和dependencies。

  • repositories :配置工程的仓库地址,按照顺序从不同的仓库中查找依赖。这里也可以添加本地的maven仓库地址
  • dependencies :配置工程的插件依赖地址(gradle程序所依赖的第三方库)。比如com.android.tools.build:gradle:3.5.1就是谷歌开发的Android相关的插件还有平时开发经常用到的tinker相关插件,butterknife相关插件都在这里。
  • allprojects 子项目的仓库

在Project中执行外部指令,比如下面的一个复制的任务:

task('copytask'){
    //保证是在执行阶段
    doLast{
        def fromPath = this.buildDir.path + '\\outputs\\apk'

        def toPath = 'D:\\data'
        //外部命令脚本
        def command = "xcopy ${fromPath} /s ${toPath}"
        println(command)
        //执行代码块
        exec {
            try {
                //执行 dir命令, /c 表示 cmd窗口执行完 dir 命令后立即关掉,
                // 至于 cmd 后可带哪些参数,可在终端下 cmd /? 查看
                commandLine 'cmd', '/c', command
                println('copytask is success')
            }catch(GradleException e){
                println('copytask is failed')
            }
        }
    }
}

上面的代码:定义一个资源路径、一个目标路径和一个复制的指令。这里使用的是windows中的复制目录的指令xcopy,在mac和linux中需要使用对应系统的指令。然后使用commandLine来执行指令。

gradle Task

官方文档 :https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task

task是构建过程中的基本单元,每一个task都属于一个project,每一个task都有一个自己的名字和一个唯一的路径,该路径在所有project和所有的task中都是唯一的。

Task的创建方式:

第一种直接创建:

task('helloTask'){
    println('hello task')
}

第二种通过TaskContainer容器创建

this.tasks.create('helloTask2'){
    println('hello task2')
}

这两种方式创建的task没有区别,TaskContainer可以更好的管理task,它里面有创建task和查找task等方法。

Task的配置方式

同样,Task的配置也有两种方式:比如下面的两种配置分组和描述信息。

//第一种
task helloTask(group:'hello',description:'hello task des'){
    println('hello task')
}
//第二种
this.tasks.create(name:'helloTask2'){
    setGroup('hello')
    setDescription('hello create task')
    println('hello task2')
}

不同的task设置相同的分组,方便查找,比如把我们自己自定义的task都设置到一个同样的分组中,跟系统的区分开。

自定义Task的执行时机

当我们在studio的命令行执行前面的代码的时候,比如执行gradlew helloTask,我们只执行第一个task,执行结果会看到helloTask和helloTask2都会输出。这是因为前面的写法都是在gradle生命周期的配置阶段执行。

那如何让一个自定义的task在gradle的执行阶段执行,可以使用doFirst和doLast这两个闭包函数。因为系统中会有很多默认的task,比如Android中build和clean等。doFirst就是在系统task执行之前执行,doLast就是在系统的task执行之后执行。

task helloTask(group:'hello',description:'hello task des'){
    //配置阶段执行
    println('hello task')
    //系统task执行之前
    doFirst{
        println('hello task do First')
    }
    //系统task执行之后
    doLast{
        println('hello task do Last')
    }
}

通过doFirst和doLast,我们可以做一些事情,比如统计build执行阶段的时间

def buildStartTime
def buildEndTime
this.afterEvaluate { Project project ->
    //afterEvaluate配置阶段完成后执行
    def preTask = project.tasks.getByName('preBuild')
    preTask.doFirst{
        buildStartTime = System.currentTimeMillis()
    }
    def endTask = project.tasks.getByName('build')
    endTask.doLast{
        buildEndTime = System.currentTimeMillis()
        println("build 执行时间:${buildEndTime - buildStartTime}")
    }
}

//输出
> Task :app:build
build 执行时间:28018

Task的依赖

一个task可能会依赖一个或者多个别的task,那么在执行的时候,它所依赖的task会先执行

静态添加依赖,当我们明确知道需要依赖那几个task的时候

task taskA{
    doLast{
        println('i am taskA')
    }
}
task taskC{
    doLast{
        println('i am taskC')
    }
}
task taskB(dependsOn:[taskA,taskC]){
    doLast{
        println('i am taskB')
    }
}

taskB依赖了taskA和taskC,运行可以看到,taskA和taskC在taskB之前执行。

动态添加依赖,比如下面的taskB依赖所有name以lib开头的task

task lib1 {
    doLast{
        println('i am lib1')
    }
}
task lib2  {
    doLast{
        println('i am lib2')
    }
}
task lib3 {
    doLast{
        println('i am lib3')
    }
}

task taskB {
    dependsOn project.tasks.findAll {
        task -> 
            return task.name.startsWith('lib')
    }
    doLast{
        println('i am taskB')
    }
}

控制task的执行顺序可以通过前面的依赖的方式,也可以通过mustRunAfter这个关键字来指定某个task的执行必须在谁的后面。

task taskC{
    doLast{
        println('i am taskC')
    }
}
task taskB {
    mustRunAfter taskC
    doLast{
        println('i am taskB')
    }
}
task taskA{
    mustRunAfter taskB
    doLast{
        println('i am taskA')
    }
}

前面我们知道,通过this.afterEvaluate {}这个闭包我们可以在配置完成之后来执行我们自己的一些操作,那如何将我们自己的task插入到系统构建的中间部位执行呢?

办法就是:通过mustRunAfter关键字,让我们的task必须执行在一个系统的task之后,然后通过dependsOn来让另一个一个系统的task依赖我们自己的task。这样就把我们自己的task插入到了这两个系统的task之间了。

Task的输入输出

一个Task的inputs和outputs可以是一个或多个文件,可以是文件夹,还可以是Project的某个Property,甚至可以是某个闭包所定义的条件。

从proguard-rules.pro中读取数据,写入到destination.txt文件中

task combineFileContent {
    def source = file('proguard-rules.pro')
    def destination = file('destination.txt')

    //inputs 属性应该被赋值为 一个目录、一个或多个文件、或是一个任意属性
    inputs.file source        // 将sources声明为该Task的inputs
    //outputs 应该被赋值为一个或多个目录或者一个或多个文件
    outputs.file destination  // 将destination声明为outputs

    doLast {
        source.withReader {
            reader ->
                def  lines = reader.readLines()
                destination.withWriter { writer ->
                    lines.each { line ->
                        writer.append(line+'\r\n')
                    }
                }
        }
    }
}

上面的代码,如果我们不写inputs和outputs也是可以完成复制的,那有什么区别呢,区别就是如果写了,只有当源文件改变的时候,才会重新执行此task,如果没写,那么每次都会执行此task。如此看来还是写这个执行效率会高点。在gradle中这个称为增量构建。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章