4

Gradle之Project,Task

 3 years ago
source link: https://blog.csdn.net/mingyunxiaohai/article/details/103201213
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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中这个称为增量构建。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK