前言
很高兴遇见你~
这又是一个新的系列,关于 Gradle 学习,我所理解的流程如下图:
在本系列的上一篇文章中,我们对 Gradle 的一些基础概念及 Groovy 语法进行了讲解,完成了第一个环节。还没有看过上一篇文章的朋友,建议先去阅读 Gradle 系列 (一)、Gradle相关概念理解,Groovy基础。
今天我们主要介绍环节二:熟悉 Gradle 常用 API,了解 Settings,Project,Task 等等。
Github Demo 地址 , 大家可以结合 demo 一起看,效果杠杠滴🍺
下面就正式进入 Gradle 的学习
一、Gradle 构建流程
1)、Gradle 构建阶段
Gradle 构建流程主要分为三个阶段:
1、初始化阶段
2、配置阶段
3、执行阶段
1、初始化阶段
Gradle 初始化阶段主要就是执行 settings.gradle 脚本,构建 Project 对象
我们使用 AndroidStudio 新建一个 Android 项目的时候会自动生成 settings.gradle 文件,内容如下:
1 2
| rootProject.name = "GradleDemo" include ':app'
|
1、指定项目根 Project 的名称
2、使用 include 导入 app 工程
实际上 settings.gradle 对应一个 Settings 对象,include 就是 Settings 对象下的一个方法,它的作用就是引用哪些工程需要加入构建。然后 Gradle 会为每个带有 build.gradle 脚本文件的工程构建一个与之对应的 Project 对象
1、include 扩展
我们可以使用 include + project 方法引用任何位置下的工程,如下:
1 2
| include ':test' project(':test').projectDir = file('当前工程的绝对路径')
|
通常我会使用这种方式引用自己写的库进行调试,非常的方便
但有的时候会遇到同时引入了 AAR 和源码的情况,我们可以使用 include + project,结合一些其他的配置,来实现 AAR 和源码的快速切换,具体步骤如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| include ':test' project(':test').projectDir = file('当前工程的绝对路径')
allprojects { configurations.all { resolutionStrategy { dependencySubstitution { substitute module("com.dream:test") with project(':test') } } } }
|
2、Settings
关于 Settings 的所有属性和方法,如下图:
结合官网提供的文档 传送门 去查看,效果杠杠的😄
2、配置阶段
Gradle 配置阶段主要就是解析 Project 对象(build.gradle 脚本文件),构建 Task 有向无环图
配置阶段会执行的代码:除 Task 的 Action 中编写的代码都会被执行,不懂 Action 的继续往下看,后面会讲到。如:
1、build.gradle 中的各种语句
2、Task 配置段语句
配置阶段完成后,整个工程的 Task 依赖关系都确定了,我们可以通过 Gradle 对象的 getTaskGraph 方法访问 Task ,对应的类为 TaskExecutionGraph ,关于 TaskExecutionGraph API 文档 传送门
注意: 执行任何 Gradle 命令,在初始化阶段和配置阶段的代码都会被执行
3、执行阶段
Gradle 执行阶段主要就是执行 Task 及其依赖的 Task
2)、Gradle 生命周期 Hook 点
引用 joe_H 一张完整的 Gradle 生命周期图,如下:
上图对 Gradle 生命周期总结的很到位,我们解析一波:
注意:Gradle 执行脚本文件的时候会生成对应的实例,主要有如下三种对象:
1、Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象
2、Project 对象:每一个 build.gradle 都会转换成一个 Project 对象
3、Settings 对象:Seetings.gradle 会转变成一个 Seetings 对象
1、Gradle 在各个阶段都提供了生命周期回调,在添加监听器的时候需要注意:监听器要在生命周期回调之前添加,否则会导致有些回调收不到
2、Gradle 初始化阶段
- 在 settings.gradle 执行完后,会回调 Gradle 对象的 settingsEvaluated 方法
- 在构建所有工程 build.gradle 对应的 Project 对象后,也就是初始化阶段完毕,会回调 Gradle 对象的 projectsLoaded 方法
3、Gradle 配置阶段:
- Gradle 会循环执行每个工程的 build.gradle 脚本文件
- 在执行当前工程 build.gradle 前,会回调 Gradle 对象的 beforeProject 方法和当前 Project 对象的 beforeEvaluate 方法
- 在执行当前工程 build.gradle 后,会回调 Gradle 对象的 afterProject 方法和当前 Project 对象的 afterEvaluate 方法
- 在所有工程的 build.gradle 执行完毕后,会回调 Gradle 对象的 projectsEvaluated 方法
- 在构建 Task 依赖有向无环图后,也就是配置阶段完毕,会回调 TaskExecutionGraph 对象的 whenReady 方法
注意: Gradle 对象的 beforeProject,afterProject 方法和 Project 对象的 beforeEvaluate ,afterEvaluate 方法回调时机是一致的,区别在于:
1、Gradle 对象的 beforeProject,afterProject 方法针对项目下的所有工程,即每个工程的 build.gradle 执行前后都会收到这两个方法的回调
2、 Project 对象的 beforeEvaluate ,afterEvaluate 方法针对当前工程,即当前工程的 build.gradle 执行前后会收到这两个方法的回调
4、执行阶段:
- Gradle 会循环执行 Task 及其依赖的 Task
- 在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法
- 在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法
5、当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法
了解了 Gradle 生命周期后,我们就可以根据自己的需求添加 Hook。例如:我们可以打印 Gradle 构建过程中,各个阶段及各个 Task 的耗时
3)、打印 Gradle 构建各个阶段及各个任务的耗时
在 settings.gradle 添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| long beginOfSetting = System.currentTimeMillis()
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
def beginOfTaskExecute
gradle.projectsLoaded { println "初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms" }
gradle.beforeProject {Project project -> if(!configHasBegin){ configHasBegin = true beginOfConfig = System.currentTimeMillis() } beginOfProjectConfig.put(project,System.currentTimeMillis()) }
gradle.afterProject {Project project -> def begin = beginOfProjectConfig.get(project) println "配置阶段,$project 耗时:${System.currentTimeMillis() - begin} ms" }
gradle.taskGraph.whenReady { println "配置阶段总耗时:${System.currentTimeMillis() - beginOfConfig} ms" beginOfTaskExecute = System.currentTimeMillis() }
gradle.taskGraph.beforeTask {Task task -> task.doFirst { task.ext.beginOfTask = System.currentTimeMillis() }
task.doLast { println "执行阶段,$task 耗时:${System.currentTimeMillis() - task.ext.beginOfTask} ms" } }
gradle.buildFinished { println "执行阶段总耗时:${System.currentTimeMillis() - beginOfTaskExecute}" }
./gradlew clean
初始化总耗时 140 ms
> Configure project : 配置阶段,root project 'GradleDemo' 耗时:1181 ms
> Configure project :app 配置阶段,project ':app' 耗时:1122 ms 配置阶段总耗时:2735 ms
> Task :clean 执行阶段,task ':clean' 耗时:0 ms
> Task :app:clean 执行阶段,task ':app:clean' 耗时:1 ms 执行阶段总耗时:325
|
了解了 Gradle 的三个阶段及生命周期,接下来我们就学习 Gradle 的一些核心 API
二、Project 介绍
对于一个 Android 项目,build.gradle 脚本文件是我们经常操作的文件之一,而每个 build.gradle 就对应了一个 Project 对象,因此学习好 Project 对应的 API 能帮助我们更好的去操作 build.gradle 脚本文件, 同时也能看懂大佬们所写的一些配置语句。
首先看一眼我的项目结构,后续就是基于它来做演示
注意:
1、下面所演示的 API 都是一些常用的 API,对 API 使用有疑问的可以去查询官方文档
2、API 的演示如果没做特殊说明,则是在 app 的 build.gradle 文件下操作的
1)、Project API
Project API 文档,我们主要介绍一些常用的 API
1、getRootProject 方法
获取根 Project 对象
1 2 3 4 5
| println getRootProject()
> Configure project :app root project 'GradleDemo'
|
2、getRootDir 方法
获取根目录文件夹路径
1 2 3 4 5
| println getRootDir()
> Configure project :app /Users/zhouying/learning/GradleDemo
|
3、getBuildDir 方法
获取当前 Project 的 build 文件夹路径
1 2 3 4 5
| println getBuildDir()
> Configure project :app /Users/zhouying/learning/GradleDemo/app/build
|
4、getParent 方法
获取当前父 Project 对象
1 2 3 4 5
| println getParent()
> Configure project :app root project 'GradleDemo'
|
5、getAllprojects 方法
获取当前 Project 及其子 Project 对象,返回值是一个 Set 集合
1 2 3 4 5 6
| println getAllprojects()
> Configure project : [root project 'GradleDemo', project ':app']
|
我们还可以使用其闭包的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| allprojects { println it }
> Configure project : root project 'GradleDemo' project ':app'
allprojects { repositories { google() mavenCentral() } }
|
注意:根 Project 与其子 Project 组成了一个树形结构,但这颗树的高度也仅仅被限定为了两层
6、getSubprojects 方法
获取当前 Project 下的所有子 Project 对象,返回值是一个 Set 集合
1 2 3 4 5 6
| println getSubprojects()
> Configure project : [project ':app']
|
同样我们也可以使用其闭包的形式
1 2 3 4 5 6 7
| subprojects { println it }
> Configure project : project ':app'
|
7、apply 系列方法
引用插件
1 2 3 4 5
| apply plugin: 'com.android.application'
apply from: 'config.gradle'
|
8、configurations 闭包
编写 Project 一些相关的配置,如全局移除某个依赖
1 2 3
| configurations { all*.exclude group: '组名', module: '模块名' }
|
9、project 系列方法
指定工程实例,然后在闭包中对其进行相关的配置
1 2 3
| project("app") { apply plugin: 'com.android.application' }
|
2)、扩展属性
扩展属性作用:方便我们全局的一个使用。类似 Java 中,在工具类里面定义静态方法
1、扩展属性定义
我们可以通过以下两种方式来定义扩展属性:
1、通过 ext 关键字定义扩展属性
2、在 gradle.properties 下定义扩展属性
1、通过 ext 关键字定义扩展属性
通过 ext 定义扩展属性的语法有两种:
1 2 3 4 5 6 7 8
|
ext.test = 'erdai666'
ext{ test1 = 'erdai777' }
|
2、在 gradle.properties 下定义扩展属性
通过 gradle.properties 定义扩展属性,直接使用 key=value 的形式即可:
2、扩展属性调用
1、ext 定义的扩展属性调用的时候可以去掉 ext 前缀直接调用
2、ext 定义的扩展属性也可以通过 当前定义扩展属性的 Project 对象.ext.属性名 进行调用
3、gradle.properties 定义的扩展属性直接通过属性名调用即可
下面我们在 app 的 build.gradle 下进行演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
println test println test1 println test2
println rootProject.ext.test println rootProject.ext.test1 println test2
> Configure project :app erdai666 erdai777 erdai888
|
注意: 子 Project 和根 Project 存在继承关系,因此根 Project 中定义的属性和方法子 Project 能获取到
3、扩展属性应用
通常我们会使用扩展属性来优化 build.gradle 脚本文件,例如我们以优化 app 下的 build.gradle 为例:
首先看一眼优化之前 app 的 build.gradle 长啥样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| apply plugin: 'com.android.application'
android { compileSdkVersion 30
defaultConfig { applicationId "com.dream.gradledemo" minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
|
下面我们就来进行改造
步骤1: 在根目录下创建一个脚本文件 config.gradle ,用来存放扩展属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ext{
androidConfig = [ compileSdkVersion : 30, applicationId : 'com.dream.gradledemo', minSdkVersion : 19, targetSdkVersion : 30, versionCode : 1, versionName : '1.0' ]
implementationLib = [ appcompat : 'androidx.appcompat:appcompat:1.3.0', material : 'com.google.android.material:material:1.4.0', constraintlayout : 'androidx.constraintlayout:constraintlayout:2.0.4' ]
testImplementationLib = [ junit : 'junit:junit:4.13.2' ]
androidTestImplementationLib = [ junit : 'androidx.test.ext:junit:1.1.3', 'espresso-core' : 'androidx.test.espresso:espresso-core:3.4.0' ] }
|
步骤2: 在根 build.gradle 对 config.gradle 进行引用
1
| apply from: 'config.gradle'
|
注意: 在根 build.gradle 进行引用的好处就是所有的子 build.gradle 都能够获取到这些扩展属性
步骤3: 在 app 的 build.gradle 里面进行扩展属性的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| apply plugin: 'com.android.application'
android { compileSdkVersion androidConfig.compileSdkVersion
defaultConfig { applicationId androidConfig.applicationId minSdkVersion androidConfig.minSdkVersion targetSdkVersion androidConfig.targetSdkVersion versionCode androidConfig.versionCode versionName androidConfig.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
def implementationLibMap = implementationLib def testImplementationLibMap = testImplementationLib def androidTestImplementationLibMap = androidTestImplementationLib
dependencies { implementationLibMap.each{k,v -> implementation v }
testImplementationLibMap.each{k,v -> testImplementation v }
androidTestImplementationLibMap.each{k,v -> androidTestImplementation v } }
|
3)、文件操作 API
1、file/files 系列文件定位
Project 对象提供的 file/files 系列方法主要用来定位一个或者多个文件,值的注意的是:它们接收的参数是一个相对路径,从当前 project 工程开始查找,而我们通过 new File 的方式需要传入一个绝对路径,下面通过代码演示感受一下他们的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
println file('../config.gradle').text
def file = new File('/Users/zhouying/learning/GradleDemo/config.gradle') println file.text
files('../config.gradle','../build.gradle').each { println it.name }
> Configure project :app config.gradle build.gradle
|
2、copy 文件拷贝
1、Project 对象提供了 copy 方法,它使得我们拷贝一个文件或文件夹变得十分简单
2、copy 方法能够接收一个闭包,闭包的参数 CopySpec ,CopySpec 提供了很多文件操作的 API,具体可以查看文档 传送门
下面会使用到 CopySpec 的 from 和 into 方法
注意: from 和 into 接收的参数是 Object 类型的,因此我们可以传入一个路径或文件
1、文件拷贝
例如我们实现:将根目录下的 config.gradle 文件拷贝拷贝到 app 目录下。 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| copy { from getRootDir().path + "/config.gradle" into getProjectDir().path }
copy { from file('../config.gradle') into getProjectDir() }
|
2、文件夹拷贝
例如我们实现:将根目录下的 gradle 文件夹下的所有文件和文件夹拷贝到 app 目录下的 gradle 文件夹
1 2 3 4 5 6
| copy { from file('../gradle/') into getProjectDir().path + "/gradle/" }
|
此时如果 app 目录下没有 gradle 文件夹,那么 copy 方法会给我们自动创建,非常的方便
3、fileTree 文件树映射
Project 对象提供了 fileTree 方法,方便我们将一个目录转换为文件树,然后对文件树进行相关的逻辑处理,它接收的参数和 file/files 类似,也是一个相对路径
例如我们实现:遍历根目录下的 gradle 文件夹,并打印文件及文件夹的名称
1 2 3 4 5 6 7 8 9 10 11
| fileTree('../gradle/'){ FileTree fileTree -> fileTree.visit { FileTreeElement fileTreeElement -> println fileTreeElement.name } }
> Configure project :app wrapper gradle-wrapper.jar gradle-wrapper.properties
|
我们通常会在 app 的 build.gradle 下看到这么一个配置语句:
1
| implementation fileTree(include: ['*.jar'], dir: 'libs')
|
他实际上是调用了 fileTree 接收 Map 参数的重载方法:
1
| ConfigurableFileTree fileTree(Map<String, ?> var1);
|
这句配置语句的意思就是:引入当前 project 目录下的 libs 文件夹下的所有 jar 包
4)、buildscript 解读
我们通常在新建一个 Android 项目的时候可以看到根 build.gradle 有这么一段配置:
1 2 3 4 5 6 7 8 9 10 11 12
| buildscript { repositories { google() mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:4.2.1" } }
|
它的作用是:引入 Gradle 构建过程中的一些插件
实际上上面这段代码的完整写法如下:
1 2 3 4 5 6 7 8 9 10
| buildscript { ScriptHandler scriptHandler -> scriptHandler.repositories { RepositoryHandler repositoryHandler -> repositoryHandler.google() repositoryHandler.mavenCentral() } scriptHandler.dependencies { DependencyHandler dependencyHandler -> dependencyHandler.classpath "com.android.tools.build:gradle:4.2.1" } }
|
你是否会有这么一个疑问:为啥这些参数都能够去掉,简化成上面那样?🤔️
要明白上面这个问题,首先我们得对闭包有一定的了解:
1、首先闭包中有 owenr this delegate 三个对象,这三个对象拥有的属性和方法我们都可以调用,并且无需写出来
2、这三个对象调用的先后顺序取决于闭包的委托策略,一般我们会对 delegate 进行操作并修改它的委托策略
实际上,Gradle 对上面的这些闭包的 delegate 修改为了传入闭包的参数,并把委托策略设置为了 DELEGATE_FIRST ,因此我们调用的时候才能把这些参数给去掉,感兴趣的可以点击 buildscript 进去看下源码,这里就不对源码进行分析了
5)、exec 外部命令执行
Project 对象提供了 exec 方法,方便我们执行外部的命令
我们可以在 linux 下通过如下命令去移动一个文件夹:
现在我们在 Gradle 下去进行这一操作
例如我们实现:使用外部命令,将我们存放的 apk 目录移动到项目的根目录 ,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| task taskMove() { doLast { def sourcePath = buildDir.path + "/outputs/apk" def destinationPath = getRootDir().path def command = "mv -f $sourcePath $destinationPath" exec { try { executable "bash" args "-c", command println "The command execute is success" } catch (GradleException e) { e.printStackTrace() println "The command execute is failed" } } } }
|
三、Task 介绍
Task 中文翻译即任务,它是 Gradle 中的一个接口,代表了要执行的任务,不同的插件可以添加不同的 Task,每一个 Task 都要和 Project关联。众所周知,线程是 cpu 执行的最小单元。同理,Task 是 Gradle 执行的最小单元,Gradle 将一个个 Task 串联起来,完成一个具体的构建任务
1)、doFirst、doLast 介绍
首先我们要搞懂 Action 这个概念,Action 本质上是一个执行动作,它只有在我们执行当前 Task 时才会被执行,Gradle 执行阶段本质上就是在执行每个 Task 中的一系列 Action
doFirst,doLast 是 Task 给我们提供的两个 Action
doFirst 表示:Task 执行最开始时被调用的 Action
doLast 表示: task 执行完时被调用的 Action
值的注意的是:doFirst 和 doLast 可被多次添加执行 ,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| task erdai{ println 'task start...'
doFirst { println 'doFirst1' }
doLast { println 'doLast1' }
doLast { println 'doLast2' }
println 'task end...' }
./gradlew erdai
> Configure project :app task start... task end...
> Task :app:erdai doFirst1 doLast1 doLast2
|
从上述打印结果我们可以发现
1、println 'task start...'
, println 'task end...'
这两句的代码在 Gradle 配置阶段就被执行了
2、doFirst,doLast 中的代码是在 Gradle 执行阶段,执行 erdai 这个 task 时被执行的
因此也验证了一开始我说的那个结论: Gradle 配置阶段,除 Task 的 Action 中编写的代码都会被执行
2)、Task 属性介绍
属性 |
描述 |
默认值 |
name |
task 名字 |
无,必须指定 |
type |
Task 的父类 |
DefaultTask |
action |
当 Task 执行的时候,需要执行的闭包或 Action |
null |
overwrite |
替换一个已存在的 Task |
false |
dependsOn |
该 Task 所依赖的 Task 集合 |
[] |
group |
该 task 所属分组 |
null |
description |
该 Task 的描述信息 |
null |
constructorArgs |
传递到 Task Class 构造器中的参数 |
null |
3)、Task 类型介绍
一般我们创建的 Task 默认是继承 DefaultTask,我们可以通过 type 属性让他继承其他的类,也可以通过 extends 关键字直接指定,Gradle 自带的有 Copy、Delete 等等,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| task deleteTask(type: Delete) { delete rootProject.buildDir }
class DeleteTask extends Delete{
} DeleteTask deleteTask = tasks.create("deleteTask",DeleteTask) deleteTask.delete(rootProject.buildDir)
task copyTask(type: Copy) { }
class CopyTask extends Copy{ }
|
4)、TaskContainer 介绍
TaskContainer 你可以理解为一个 Task 容器,Project 对象就是通过 TaskContainer 来管理 Task,因此我们可以通过 TaskContainer,对 Task 进行相关的操作,一些常用的 API 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| findByPath(path: String): Task getByPath(path: String): Task getByName(name: String): 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
whenTaskAdded(action: Closure)
|
5)、Task 定义及配置
因为 Task 和 Project 是相互关联的,Project 中提供了一系列创建 Task 的方法,下面介绍一些常用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| task task1
task task2{ group 'erdai666' doFirst{ } }
task task3(type: Copy){ dependsOn "task2" doLast{ } }
task task4(group: "erdai666", description: "task4") { doFirst { } doLast { } }
tasks.create("task5"){
}
tasks.create(name: "task6"){
}
|
6)、Task 执行实战
通常我们会使用 doFirst 与 doLast 在 Task 执行期间进行相关操作,下面我们就来实现 build 任务执行期间耗时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| def startBuildTime, endBuildTime
this.afterEvaluate { Project project -> def preBuildTask = project.tasks.getByName("preBuild") preBuildTask.doFirst { startBuildTime = System.currentTimeMillis() } def buildTask = project.tasks.getByName("build") buildTask.doLast { endBuildTime = System.currentTimeMillis() println "Current project execute time is ${endBuildTime - startBuildTime}" } }
./gradlew build
Current project execute time is 21052
|
7)、指定 Task 执行顺序
在 Gradle 中,有三种方式可以指定 Task 执行顺序:
1、dependsOn 强依赖方式
2、通过 Task 输入输出
3、通过 API 指定执行顺序
1、dependsOn 强依赖方式
dependsOn 强依赖方式可细分为静态依赖和动态依赖
- 静态依赖:在创建 Task 的时候,直接通过 dependsOn 指定需要依赖的 Task
- 动态依赖:在创建 Task 的时候,不知道需要依赖哪些 Task,需通过 dependsOn 动态依赖符合条件的 Task
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| task taskA{ doLast { println 'taskA' } }
task taskB{ doLast { println 'taskB' } }
task taskC(dependsOn: taskA){ doLast { println 'taskC' } }
./gradlew taskC
> Task :app:taskA taskA
> Task :app:taskC taskC
|
上述代码,当我们执行 taskC 的时候,因为依赖了 taskA,因此 taskA 会先执行,在执行 taskC
注意:当一个 Task 依赖多个 Task 的时候,被依赖的 Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响,如下:
1 2 3 4 5
| task taskC(dependsOn:[taskA,taskB]){ doLast { println 'taskC' } }
|
taskA 和 taskB 的执行顺序是随机的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
task lib1 { doLast{ println 'lib1' } } task lib2 { doLast{ println 'lib2' } } task lib3 { doLast{ println 'lib3' } }
task taskDynamic{ dependsOn tasks.findAll{ Task task -> return task.name.startsWith('lib') }
doLast { println 'taskDynamic' } }
./gradlew taskDynamic
> Task :app:lib1 lib1
> Task :app:lib2 lib2
> Task :app:lib3 lib3
> Task :app:taskDynamic taskDynamic
|
2、通过 Task 输入输出指定执行顺序
当一个参数,作为 TaskA 的输入参数,同时又作为 TaskB 的输出参数,那么 TaskA 执行的时候先要执行 TaskB。即输出的 Task 先于输入的 Task 执行
但是我在实际测试过程中发现:输入的 Task 会先执行,然后在执行输出的 Task,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| ext { testFile = file("${projectDir.path}/test.txt") if(testFile != null || !testFile.exists()){ testFile.createNewFile() } }
task outputTask { outputs.file testFile doLast { outputs.getFiles().singleFile.withWriter { writer -> writer.append("erdai666") } println "outputTask 执行结束" } }
task inputTask { inputs.file testFile doLast { println "读取文件内容:${inputs.files.singleFile.text}" println "inputTask 执行结束" } }
task testTask(dependsOn: [outputTask, inputTask]) { doLast { println "testTask1 执行结束" } }
./gradlew testTask
> Task :app:inputTask 读取文件内容: inputTask 执行结束
> Task :app:outputTask outputTask 执行结束
> Task :app:testTask testTask1 执行结束
|
最终我对 inputTask 指定了具体依赖才达到了预期效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| task inputTask(dependsOn: outputTask) { inputs.file testFile doLast { println "读取文件内容:${inputs.files.singleFile.text}" println "inputTask 执行结束" } }
> Task :app:outputTask outputTask 执行结束
> Task :app:inputTask 读取文件内容:erdai666 inputTask 执行结束
> Task :app:testTask testTask1 执行结束
|
3、通过 API 指定执行顺序
可以指定 Task 执行顺序的 API 有:
mustRunAfter:指定必须在哪个 Task 执行完成之后执行
shouldRunAfter:跟 mustRunAfter 类似,区别在于不强制,不常用
finalizeBy:在当前 Task 执行完成之后,指定执行的 Task
下面我们通过代码来演示一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| task taskX{ doLast { println 'taskX' } }
task taskY{ mustRunAfter taskX doLast { println 'taskY' } }
task taskXY(dependsOn: [taskX,taskY]){ doLast { println 'taskXY' } }
./gradlew taskXY
> Task :app:taskX taskX
> Task :app:taskY taskY
> Task :app:taskXY taskXY
task taskI{ doLast { println 'taskI' } }
task taskJ{ finalizedBy taskI doLast { println 'taskJ' } }
task taskIJ(dependsOn: [taskI,taskJ]){ doLast { println 'taskIJ' } }
./gradlew taskIJ
> Task :app:taskJ taskJ
> Task :app:taskI taskI
> Task :app:taskIJ taskIJ
|
四、自定义 Task 挂接到 Android 应用构建流程
1)、Task 依赖关系插件介绍
我们可以引入如下插件来查看 Task 的一个依赖关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| buildscript { repositories { maven{ url "https://plugins.gradle.org/m2/" } } dependencies { classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.5" } }
apply plugin: com.dorongold.gradle.tasktree.TaskTreePlugin
./gradlew build taskTree --no-repeat
|
经过上面 3 步,我们看下依赖关系图,仅截取部分:
2)、自定义 Task 挂接到 Android 构建流程
我们知道,Gradle 在执行阶段就是执行 Task 及其依赖的 Task,就比如上面截图的 build Task 的关系依赖图,它会按照这个依赖图有条不紊的去执行。
那么如果我想把自己自定义的 Task 挂接到这个构建流程,该怎么做呢?
1、通过 dependsOn 指定
注意: 单独使用 dependsOn ,必须让构建流程中的 Task 依赖我们自定义的 Task,否则我们的 Task 不会生效
如下代码演示一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| task myCustomTask{ doLast { println 'This is myCustomTask' } }
afterEvaluate { def mergeDebugResources = tasks.findByName("mergeDebugResources") mergeDebugResources.dependsOn(myCustomTask) }
|
接下来我们验证一下
首先看一眼 Task 依赖关系图:
我们自定义的 Task 挂接到了 mergeDebugResources 上
执行下 build 这个 Task,可以发现我们的 Task 被执行了:
2、通过 finalizedBy 指定
在某个 Task 执行完成后,指定需要执行的 Task
1 2 3 4 5 6 7 8 9 10 11
| task myCustomTask{ doLast { println 'This is myCustomTask' } }
afterEvaluate { def mergeDebugResources = tasks.findByName("mergeDebugResources") mergeDebugResources.finalizedBy(myCustomTask) }
|
3、通过 mustRunAfter 配合 dependsOn 指定
在两个 Task 之间,插入自定义的 Task
1 2 3 4 5 6 7 8 9 10 11 12 13
| task myCustomTask{ doLast { println 'This is myCustomTask' } }
afterEvaluate { def processDebugResources = tasks.findByName("processDebugResources") def mergeDebugResources = tasks.findByName("mergeDebugResources") myCustomTask.mustRunAfter(mergeDebugResources) processDebugResources.dependsOn(myCustomTask) }
|
上述 Task 依赖变化过程:
processDebugResources -> mergeDebugResources ===> processDebugResources -> myCustomTask -> mergeDebugResources
五、Gradle 相关命令介绍
1)、查看项目所有的 Project 对象
2)、查看 module 下所有的 task
1 2 3 4 5 6 7 8
| ./gradlew $moduleName:tasks
./gradlew app:tasks
./gradlew tasks
|
3)、执行一个 Task
1 2 3 4
| ./gradlew $taskName
./gradlew build
|
4)、查看 module 下的第三方库依赖关系
1 2 3 4
| ./gradlew $moduleName:dependencies
./gradlew app:dependencies
|
六、总结
本篇文章讲的一些重点内容:
1、Gradle 三个阶段及生命周期 Hook 点
2、Project 对象常用 API 介绍,扩展属性的应用与实战
3、Task 常用配置介绍,其中通过 Task 输入输出指定执行顺序遇到了坑:会先执行输入的 Task。最终还是通过使用 dependsOn 指定具体依赖才达到预期效果
4、自定义 Task 挂接到 Android 应用构建流程的三种方式:
1、单独使用 dependsOn (注意必须使用构建流程中的 Task 依赖我们自定义的 Task)
2、使用 finalizedBy
3、mustRunAfter 配合 dependsOn
5、Gradle 一些常用的命令介绍
好了,本篇文章到这里就结束了,希望能给你带来帮助 🤝
感谢你阅读这篇文章
下篇预告
下篇文章我会讲如何自定义第三方插件,敬请期待吧😄
参考和推荐
补齐Android技能树 - 玩转Gradle
Gradle学习系列(二):Gradle核心探索
深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)
从Gradle生命周期到自定义Task挂接到Build构建流程全解
7个你应该知道的Gradle实用技巧
你的点赞,评论,是对我巨大的鼓励!
欢迎关注我的公众号: sweetying ,文章更新可第一时间收到
如果有问题,公众号内有加我微信的入口,在技术学习、个人成长的道路上,我们一起前进!