概述
又开始了一个新的系列,这个系列学习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关系都确定了,我们可以通过gradle的getTaskGraph方法访问,对应的类为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中
beforeProject和beforeEvaluate这俩个方法的调用时机是一样的,只不过beforeProject调用应用于所有项目,beforeEvaluate之应用于调用的Project
afterProject和afterEvaluated也是一样的道理
获取构建的耗时时间
在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介绍
| API | 描述 |
|---|---|
| addTaskExecutionGraphListener | 给任务执行图添加监听 |
| addTaskExecutionListener | 给任务的执行添加监听 |
| whenReady | 当任务执行图填充完毕被调用 |
| beforeTask | 当一个任务执行之前被调用 |
| afterTask | 当一个任务执行完毕被调用 |
| hasTask | 查询是否有这个task |
| getAllTasks | 获取所有的Task |
| Set getDependencies(Task task) | 返回参数Task的依赖关系 |
Project 介绍
介绍一些可能用到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的创建
由于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一些参数的介绍
| 参数名 | 描述 | 默认值 |
|---|---|---|
| name | task的名字 | 必须指定不能为空 |
| type | task的父类 | 默认值为org.gradle.api.DefaultTask |
| overwrite | 是否替换同名的task | 默认false |
| group | task所属的分组名 | null |
| description | task的描述 | null |
| dependsOn | task依赖的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类图
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的构建速度
通常一个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类型 | 一个简单的输入值 |
| @InputFile | File | 一个输入文件,不是目录 |
| @InputDirectory | File | 一个输入目录,不是文件 |
| @InputFiles | Iterable | File列表,包含文件和目录 |
| @OutputFile | File | 一个输出文件,不是目录 |
| @OutputDirectory | File | 一个输出目录,不是文件 |
| @OutputFiles | Map<String, File>或Iterable | 输出文件列表 |
| @OutputDirectories | Map<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,会执行增量构建,如果你改动了属性值,或者删除了文件,则会重新执行