Gradle学习系列(二):Gradle核心探索

5,293 阅读9分钟

概述

又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记

Gradle系列(一):Groovy学习

Gradle学习系列(二):Gradle核心解密

理解Gradle

Gradle是一个可以构建工具,他可以app的编译打包工作,但是我们学习Gradle不能把它当做一个工具来学,当我们把他当做工具来学的话,我们的目标就是,会写,会配置脚本就就OK了,但是真实的工作中的需求是复杂且多变的,我们除了会用,还要了解为什么这么用,所以我们需要把他当成编程框架来看,这样对于复杂的需求会更加的得心应手

回忆一下我们在项目中使用Okhttp的时候是怎么使用的

  • 首先Okhttp 使用java语言写的,所以你必须要懂java语法
  • 其次Okhttp定义了很多API用于请求网络,所以我要学习Okhttp的各种API,以满足我们不同的需求

一样的思路用到Gradle上

  • 首先Gradle是用Groovy写的,所以我们懂Groovy的语法
  • 其次Gradle定义了自己的API,所以我们要学习Gradle的API
  • 最后Gradle中有插件的概念,不同的插件完成不同的任务,比如我们的Android的打包编译用到的Android的插件,所以我们要学习Android 插件定义的各种API

最终我们需要掌握基本语言Groovy,然后掌握Gradle的API还有他的生命周期,最后掌握Android的插件API,这样就可以去写Gradle了

这里是Gradle API文档,回头想想原来写Gradle脚本原来就是玩转Gradle的API,这样一想好像也还好,并没有想象中那么难,毕竟都是一个个框架学过来的

Gradle基本组件

下面我们就来认识一下Gradle框架的基本组件

先来看下这张图,就是一个普通的Android工程,这个工程包括三个Module

Gradle中每一个带编译的工程都是一个Project(例如上图的app,mylibrary和myLibary2)每一个Project在构建的时候都要包含多个Task,比如一个Android APK的编译可能包括,Java编译的Task,JNI编译的Task,打包生成APK的Task等

而一个Project到底包含多少个Task,是由插件决定的,插件就是来定义Task然后执行Task的,一个插件可以包含多个Task

Gradle是一个框架,他负责定义流程和规则,而具体的工作都是通过插件实现的(类似于淘宝和淘宝商家,淘宝负责制定规则和流程,淘宝商家负责真正的卖东西),比如:编译Java的插件,编译Groovy的插件,编译Android APP的插件

现在我们知道了,一个带编译的工程是一个Project,而一个Protect在构建的时候是由一个个Task定义和执行

那么现在请问上图的图片中有多少个Project?

答案是3个

每一个Libary和每一个App都是单独的Project,每一个Project根目录下都要有一个build.gradle,build.gradle就是Project的编译脚本

所以此gradle工程,包含3个Project,我们可以选择独立编译每一个Project

  • cd进入该Project的目录
  • 然后执行gradle任务(比如:gradle assemble)

但是如果有100Project 难道就要单独编译100次吗,有没有可以直接在根目录直接全部编译这100个Project?

肯定是有的,只要在gradle工程的根目录添加build.gradle和setting.gradle

  • 根目录的build.gradle ,主要是配置其他子Project,比如为其他子project添加属性
  • 根目录setting.gradle,他用来告诉Gradle这个工程一共包括多少个Project,如下图

Gradle的命令简介

gradle projects 查看工程信息

执行这个命令可以查看这个工程到底包含多少个子Project

gradle tasks查看任务信息

gradle project-path:tasks查看某个project下的Tasks,project-path是目录名,后面需要跟冒号,在根目录你需要指定你想看那个project的路径

gradle task-name 执行任务

上面我们查看了所有的Task,现在我们看执行其中一个

最后 Task和Task之间是有依赖关系的,比如assemble task就是依赖其他Task,如果执行这个Task,那就需要他所依赖的其他Task先执行assemble才能最终输出

由于Task之间是有依赖关系的,所以我们可以自定义Task让他依赖于assemble Task,那么当assemble Task执行的时候就会先执行我们自定义的Task

Gradle 的工作流程

Gralde 工作流程包括三个阶段

初始化阶段

Initiliazation初始化阶段,主要任务是创建项目的层次结构,为每一个项目创建一个Project对象,对应就是执行setting.gradle,一个setting.gradle对应一个setting对象,在setting.gradle中可以直接调用其中的方法,Settings API文档

配置阶段

下一个阶段就是Configration配置阶段,它主要是配置每个Project中的build.gradle,在初始化阶段和配置阶段之间,我们可以加入Hook,这是通过API添加的

Configration阶段完成之后,整个build的project以及内部的Task关系都确定了,我们可以通过gradlegetTaskGraph方法访问,对应的类为TaskExecutionGraph,TaskExecutionGraph API文档

我们知道每个Project都由多个Task组成,每个Task之间都有依赖关系,Configuration阶段会建立一个有向图来描述Task之间的依赖关系,这里也可以添加一个Hook,当有向图建立完成之后进行一些操作

每个build.gradle对应一个Project对象,在初始化阶段创建,这里是Project API文档

执行阶段

最后一个阶段就是执行阶段,这一阶段的主要是执行Task,这里也可以加Hook,当任务执行完之后做一些事情

添加Gradle构建过程的监听

在setting.gradle中加入这段代码

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println'buildStarted'
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println 'settings 评估完成(settings.gradle 中代码执行完毕)'
        println '这里project 还没有初始化完成'

    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println '项目结构加载完成(初始化阶段结束)'
        println '初始化结束,这里已经完成project的初始化,可访问根项目:' + gradle.rootProject
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println '所有项目评估完成(配置阶段结束)'

    }

    @Override
    void buildFinished(BuildResult result) {
        println '构建结束 '
    }
})

执行gradle assemble任务

L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew assemble
setting----1518204878
setting --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
setting --- userhomedir/Users/renxiaohui/.gradle
setting -----paraentnull
"hahah"
settings 评估完成(settings.gradle 中代码执行完毕)
这里project 还没有初始化完成
项目结构加载完成(初始化阶段结束)
初始化结束,这里已经完成project的初始化,可访问根项目:root project 'gradle'

> Configure project :app
gradle----1518204878
gradle --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
gradle --- userhomedir/Users/renxiaohui/.gradle
gradle -----paraentnull
所有项目评估完成(配置阶段结束)

> Task :app:assemble
构建结束 

BUILD SUCCESSFUL in 5s

Hook点

这里借用Gradle基础 构建生命周期和Hook技术 文章中的图片,来展示整个生命周期何时进行Hook

Gradle在各个阶段都提供了回调,在添加监听器的时候需要注意一点,监听器要在回调的声明周期之前添加,一般情况加在setting.gradle中

beforeProjectbeforeEvaluate这俩个方法的调用时机是一样的,只不过beforeProject调用应用于所有项目,beforeEvaluate之应用于调用的Project

afterProjectafterEvaluated也是一样的道理

获取构建的耗时时间

在setting.gradle中加入如下代码,gradle.taskGraph.beforeTask这个方法会在Task调用之前回调

long beginOfSetting = System.currentTimeMillis()

gradle.projectsLoaded {
    println '初始化阶段,耗时:' + (System.currentTimeMillis() - beginOfSetting) + 'ms'
}

def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
gradle.beforeProject { project ->
    if (!configHasBegin) {
        configHasBegin = true
        beginOfConfig = System.currentTimeMillis()
    }
    beginOfProjectConfig.put(project, System.currentTimeMillis())
}
gradle.afterProject { project ->
    def begin = beginOfProjectConfig.get(project)
    println '配置阶段,' + project + '耗时:' + (System.currentTimeMillis() - begin) + 'ms'
}
def beginOfProjectExcute
gradle.taskGraph.whenReady {
    println '配置阶段,总共耗时:' + (System.currentTimeMillis() - beginOfConfig) + 'ms'
    beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
    task.doFirst {
        task.ext.beginOfTask = System.currentTimeMillis()
    }
    task.doLast {
        println '执行阶段,' + task + '耗时:' + (System.currentTimeMillis() - task.beginOfTask) + 'ms'
    }
}
gradle.buildFinished {
    println '执行阶段,耗时:' + (System.currentTimeMillis() - beginOfProjectExcute) + 'ms'
}

运行gradle assemble

L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew assemble
初始化阶段,耗时:25ms

> Configure project :
配置阶段,root project 'gradle'耗时:78ms

> Configure project :app
配置阶段,project ':app'耗时:19ms

> Configure project :mylibrary
配置阶段,project ':mylibrary'耗时:7ms

> Configure project :mylibrary2
配置阶段,project ':mylibrary2'耗时:10ms
配置阶段,总共耗时:391ms

> Task :app:preBuild
执行阶段,task ':app:preBuild'耗时:0ms

> Task :app:preDebugBuild
执行阶段,task ':app:preDebugBuild'耗时:0ms
....

当Gradle执行脚本的时候会生成对应的实例,Gradle主要有三种对象,每种对象对于对应一种脚本

  • Gradle对象:在项目初始化时构建,全局单例存在,只有这一个对象
  • Project对象:每一个build.gradle都会转换成一个Project对象
  • Settings对象:Seeting.gradle 会转变成一个Seetings对象

官方文档Gradle 介绍

Gradle对象 API介绍

Gradle对象 API文档 具体的API看上面的文档,下面介绍一些可能用到的API

gradle.afterProject/gradle.beforeProject

这俩个方法是在每个Project执行完毕之后,或者开始执行之前调用的

在seeting.gradle写入代码


gradle.afterProject {
    println 'gradle.afterProject 调用'
}

gradle.beforeProject {
    println 'gradle.beforeProject 调用'
}

执行./gradlew clean

L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew clean
setting.gradle 开始执行
初始化阶段,耗时:3ms

> Configure project :
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,root project 'gradle'耗时:43ms

> Configure project :app
gradle.beforeProject 调用
com.aliyun.gradle
gradle----2006449733
gradle --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
gradle --- userhomedir/Users/renxiaohui/.gradle
gradle -----paraentnull
com.aliyun.gradle
gradle.afterProject 调用
配置阶段,project ':app'耗时:19ms

> Configure project :mylibrary
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,project ':mylibrary'耗时:5ms

> Configure project :mylibrary2
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,project ':mylibrary2'耗时:5ms
配置阶段,总共耗时:115ms

> Task :clean
执行阶段,task ':clean'耗时:0ms

> Task :app:clean
执行阶段,task ':app:clean'耗时:0ms

> Task :mylibrary:clean
执行阶段,task ':mylibrary:clean'耗时:0ms

> Task :mylibrary2:clean
执行阶段,task ':mylibrary2:clean'耗时:0ms
执行阶段,耗时:8ms

Gradle对象 其他API

API描述
TaskExecutionGraph getTaskGraph()获取Project中Task的关系图
buildStarted当构建开始回调
settingsEvaluated当setting.gradle评估完成回调
projectsLoaded在初始化阶段中projects创建完成回调
projectsEvaluated配置阶段完成回调
buildFinished构建完毕回调
addBuildListener添加构建监听

TaskExecutionGraph API介绍

TaskExecutionGraph API文档

API描述
addTaskExecutionGraphListener给任务执行图添加监听
addTaskExecutionListener给任务的执行添加监听
whenReady当任务执行图填充完毕被调用
beforeTask当一个任务执行之前被调用
afterTask当一个任务执行完毕被调用
hasTask查询是否有这个task
getAllTasks获取所有的Task
Set getDependencies(Task task)返回参数Task的依赖关系

Project 介绍

Project API 文档

介绍一些可能用到API,具体的自己看文档

API描述
getRootProject()获取根Project
getRootDir返回根目录文件夹
getBuildDir返回构建目录,所有的build生成物都放入到这个里面
setBuildDir(File path)设置构建文件夹
getParent()获取此Project的父Project
getChildProjects获取此Project的直系子Project
setProperty(String name, @Nullable Object value)给此Project设置属性
getProject()但会当前Project对象,可用于访问当前Project的属性和方法
getAllprojects返回包括当前Project,以及子Project的集合
allprojects(Closure configureClosure)返回包括当前Project,以及子Project的集合到闭包中
getSubprojects返回当前Project下的所有子Project
subprojects(Closure configureClosure)返回当前Project下的所有子Project到闭包中
Task task(String name)创建一个Task,添加到此Priject
getAllTasks(boolean recursive)如果recursive为true那么返回当前Project和子Project的全部Task,如果为false只返回当前Project的所有task
getTasksByName(String name, boolean recursive)根据名字返回Task,如果recursive为true那么返回当前Project和子Project的Task,如果为false只返回当前Project的task
beforeEvaluate(Closure closure)在Project评估之前调用
afterEvaluate(Closure closure);在项目评估之后调用
hasProperty(String propertyName)查看是否存在此属性
getProperties()获取所有属性
findProperty(String propertyName);返回属性的value
dependencies(Closure configureClosure)为Project配置依赖项
buildscript(Closure configureClosure)为Project配置build脚本
project(String path, Closure configureClosure)根据路径获取Project实例,并在闭包中配置Project
getTasks()返回此Project中所有的tasks

属性

在我们写java代码的时候,当遇到很多类同时用的一些方法,我们会把这个共用的方法,抽取到Utils作为公共方法使用,而在Gradle中也会存在相同的问题,那么在Gradle中改如何抽取公共方法呢?

在Gradle中提供了extra property的方法,extra property是额外属性的意思,第一次定义需要使用ext前缀表示额外属性,定义好了之后,再次存取就不需要ext前缀了,ext额外属性支持Gradle和Project对象

在local.properties中加入一个新属性

在setting.gradle 中取出sdk.api属性,并且设置给Gradle对象,然后打印Gradle对象刚刚赋值的属性

def text(){
    Properties properties = new Properties()
    File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
    properties.load(propertyFile.newDataInputStream())

    gradle.ext.api = properties.getProperty('sdk.api')

    println(gradle.api)
}

text()

输出

setting.gradle 开始执行
"hahah"

接下来我们定义一个utils.gradle作为一个公共的类,为其他的build.gradle提供公共的方法

//这个方法取出AndroidManifest.xml中的包名
def getxmlpackage(boolean x){
// 注释1 此处的 project 是指的那个project?
    def file=new File(project.getProjectDir().getPath()+"/src/main/AndroidManifest.xml");
    def paser = new XmlParser().parse(file)
    return paser.@package
}
//注释2 此处的ext 是谁的ext?
ext{
	//除了这种赋值ext.xxx=xxx,还有这种闭包形式的赋值
    getpackage = this.&getxmlpackage
}

app模块 mylibrary模块mylibrary2模块的build.gradle 引入 utils.gradle

apply  from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"

...

println(getpackage(true))

输出

> Configure project :app

取出的包名为= com.renxh.gradle

配置阶段,project ':app'耗时:18ms

> Configure project :mylibrary

取出的包名为= com.renxh.mylibrary

配置阶段,project ':mylibrary'耗时:73ms

> Configure project :mylibrary2

取出的包名为= com.renxh.mylibrary2

配置阶段,project ':mylibrary2'耗时:59ms
配置阶段,总共耗时:271ms

看到了输出,那就应该知道上面注释1注释2 的答案了吧

  • 注释1处的project,指的是 谁加载utils.gradle 就是谁的 project
  • 注释2 同样也是谁加载utils.gradle就是为谁的Project加载属性

通过这种方式,我们就可以把一些常用的函数放在utils.gradle中,然后为他加载的project设置一些ext属性

有关文件操作的API

定位文件

this.getText("utils.gradle")

def getText(String path) {
    try {
        // 不同与 new file 的需要传入 绝对路径 的方式,
        // file 从相对于当前的 project 工程开始查找
        File mFile = file(path)
        println mFile.text
    } catch (GradleException e) {
        println e.toString()
        return null
    }
}

拷贝文件

assemble任务完成之后,把生成的的app-debug.apk改名为renxhui.apk,然后拷贝到项目根目录,不拷贝release相关文件文件

tasks.getByName("assemble") {
      it.doLast {
            copy {
                  // 既可以拷贝文件,也可以拷贝文件夹
                  // 这里是将 app moudle 下生成的 apk 目录拷贝到
                  // 根工程下的 build 目录
                  from file("build/outputs/apk")
                  into getRootDir().path+ "/apk/"

                  rename('app-debug.apk', 'renxhui.apk')
                  exclude { details ->
                          details.file.name.contains('release') }
            }

      }
}

文件树

遍历build/outputs/apk文件夹,打印出每个文件的文件名

 fileTree("build/outputs/apk"){ freeTree ->
                freeTree.visit{fileTreeElement->
                      println "遍历,文件名为="+"$fileTreeElement.file.name"
                }
           }

输出

遍历,文件名为=release
遍历,文件名为=app-release-unsigned.apk
遍历,文件名为=output.json
遍历,文件名为=debug
遍历,文件名为=output.json
遍历,文件名为=app-debug.apk

dependencies依赖相关

 dependencies {
   implementation('org.hibernate:hibernate:3.1') {
     //在版本冲突的情况下优先使用3.1版本
     force = true

     //排除特定的依赖
     exclude module: 'cglib' //by artifact name
     exclude group: 'org.jmock' //by group
     exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group

     //禁用依赖传递
     // 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
     // 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B 
     // 中所使用的 C 中的依赖,默认都是打开,即 true
     transitive = false
   }
 }

Task

Task是Gradle中的一种数据类型,他代表要执行的工作,不同的插件可以添加不同的Task,每一个Task都要和Project关联,Task是Gradle构建的原子执行单元,Gradle将一个个Task串联起来完成一个具体的构建任务

Task API文档

Task的创建

由于Task是和Project相关联的,所以我们使用Project中的task(String name) 方法来创建

task myTask <==myTask 是新建 Task 的名字
task myTask { configure closure }
task myTask(type: SomeType) 
task myTask(type: SomeType) { configure closure }
task myTask(dependsOn:SomeTask){configure closure}

task aa{
    println "ccc"
    doFirst{
       println "aaa"
    }

    doLast{
        println"bbb"
    }
}
  • 一个Task包含多个Action,Task有俩个函数,doLast和doFristdoLast:指的是任务执行完之后在进行操作,doFrist:指的是任务执行之前进行操作,Action 就是一个闭包

  • Task创建的时候可以指定Type,通过Type:name告诉Gradle新建的Task对象会从那个Task基类派生,比如task myTask(type:Copy)创建的Task继承于Copy,是一个Copy Task

  • 当我们使用task myTask { configure closure },还括号是一个闭包,这样会导致Gradle创建这个Task之后,返回用户之前,会先执行闭包中内容,括号中的代码只是配置代码,在配置阶段会被执行,他并不是Action,Action只有执行Task的时候才会执行,比如doLast和doFrist就是俩个Action

  • 创建Task的时候可以使用dependsOn属性,表示当前Task依赖 xx Task,当前Task执行的时候需要先执行xx Task

  • doLast有一种等价操作叫做leftShift,leftShift可以缩写为 << ,这只是一个语法糖,下面几种写法一个意思,不要被这个迷惑了

myTask1.doLast {
    println "task1 doLast"
}

myTask1 << {
    println "task1 doLast<<"
}

myTask1.leftShift {
    println "task1 doLast leftShift"
}

创建Task的几种方法

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"
}

创建Task一些参数的介绍

参数名描述默认值
nametask的名字必须指定不能为空
typetask的父类默认值为org.gradle.api.DefaultTask
overwrite是否替换同名的task默认false
grouptask所属的分组名null
descriptiontask的描述null
dependsOntask依赖的task集合
constructorArgs构造函数参数
task myTask3(description: "这是task3的描述", group: "myTaskGroup", dependsOn: [myTask1, myTask2], overwrite: true) << {
    println "doLast in task3, this is new task"
}
task aa{
    doFirst{
       println "aaa"
    }

    doLast{
        println"aa"
    }
}

task bb(dependsOn:'aa') {
    doFirst{
        println "bb"
    }

    doLast{
        println 'bb'
    }
}


自定义Task

class MyTask extends DefaultTask{

    String msg = "mmm";
    int age = 18;

    //构造函数必须用@javax.inject.Inject注解标识
    @javax.inject.Inject
    MyTask(){
    }

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

}
  Task hello = project.task("hello2", type: MyTask)

        //配置task
        hello.configure {
            println("configure")

            msg = "123"
        }

执行Task

> Task :app:hello2
Hello 123 ! age is 18
执行阶段,task ':app:hello2'耗时:0ms

Task类图

image.png

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
}

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创建时
        project.getTasks().whenTaskAdded { Task task ->
            println "The task ${task.getName()} is added to the TaskContainer"
        }

        //采用create(String var1)创建
        project.getTasks().create("task1")

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

        //采用create(String var1, Closure var2)创建
        project.getTasks().create("task3", {
            group "MyGroup"
            setDependsOn(["task1", "task2"])
            setDescription "这是task3描述"
        })

执行

> Configure project :app
gradle.beforeProject 调用
app before --
configure
The task task1 is added to the TaskContainer
The task task2 is added to the TaskContainer
The task task3 is added to the TaskContainer

Task的增量构建

Gradle 支持一种up-to-date 的检查功能,Gradle会把每次的运行结果保存下来,下次运行会检查结果有没有变化,如果没有变就跳过运行,这样可以提高Gradle的构建速度

image.png

通常一个Task会有,输入(intput)输出(output),Task的输入会影响输出结果

图中表示java编译的task,他的输入有俩种,一个是JDK版本号,一个是源文件,输出结果为class文件,只有当版本号和源文件改动,最终编译出的class文件是不同的,当我们执行过一次Task后,再次执行,如果他的输入没有任何变化,结果可以直接从缓存中取出,这样Gradle会标识UP-TO-DATE,从而跳过Task的执行

如何实现增量构建

一个增量构建必须指定一个输入一个输出,从前面个的类图可以看到,Task.getInputs()的类型为TaskInputs,Task.getOutputs() 类型为TaskOuputs,从中也可以看到inputs、outputs支持哪些类型

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

    doLast {
        println "exec task task1"
    }
}

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

第一次执行

> Task :app:test1
exec task task1
执行阶段,task ':app:test1'耗时:0ms

> Task :app:test2
exec task task2
执行阶段,task ':app:test2'耗时:0ms

第二次执行,test1 已经不需要重复执行了,标记为up-to-date,这就是典型的增量构建

> Task :app:test2
exec task task2
执行阶段,task ':app:test2'耗时:0ms

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

taskInputs、taskOutputs注解

在自定义task的时候,也可听过注解来实现增量构建

注解名属性类型描述
@Input任意Serializable类型一个简单的输入值
@InputFileFile一个输入文件,不是目录
@InputDirectoryFile一个输入目录,不是文件
@InputFilesIterableFile列表,包含文件和目录
@OutputFileFile一个输出文件,不是目录
@OutputDirectoryFile一个输出目录,不是文件
@OutputFilesMap<String, File>或Iterable输出文件列表
@OutputDirectoriesMap<String, File>或Iterable输出目录列表
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 文件目录,当你再次执行该Task,会执行增量构建,如果你改动了属性值,或者删除了文件,则会重新执行

参考

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

深入理解Android之Gradle

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)