一、Gradle构建流程
1. Gradel构建生命周期
任何Gradle的构建过程都分为三部分:初始化阶段、配置阶段和执行阶段。
初始化阶段
初始化阶段的任务是创建项目的层次结构,并且为每一个项目创建一个Project
实例。 与初始化阶段相关的脚本文件是settings.gradle
(包括<USER_HOME>/.gradle/init.d
目录下的所有.gradle脚本文件,这些文件作用于本机的所有构建过程)。一个settings.gradle
脚本对应一个Settings
对象,我们最常用来声明项目的层次结构的include
就是Settings
类下的一个方法,在Gradle初始化的时候会构造一个Settings
实例对象,它包含了下图中的方法,这些方法都可以直接在settings.gradle
中直接访问。
比如可以通过如下代码向Gradle的构建过程添加监听:
gradle.addBuildListener(new BuildListener() {
void buildStarted(Gradle var1) {
println '开始构建'
}
void settingsEvaluated(Settings var1) {
println 'settings评估完成(settins.gradle中代码执行完毕)'
// var1.gradle.rootProject 这里访问Project对象时会报错,还未完成Project的初始化
}
void projectsLoaded(Gradle var1) {
println '项目结构加载完成(初始化阶段结束)'
println '初始化结束,可访问根项目:' + var1.gradle.rootProject
}
void projectsEvaluated(Gradle var1) {
println '所有项目评估完成(配置阶段结束)'
}
void buildFinished(BuildResult var1) {
println '构建结束 '
}
})
编写完相应的 Gradle 生命周期监听代码之后,我们就可以在 Build 输出界面看到如下信息:
settings 评估完成(settings.gradle 中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项目:root project 'GradleDemo'
所有项目评估完成(配置阶段结束)
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
构建结束
include 扩展
我们可以使用 include + project 方法引用任何位置下的工程,如下:
include ':test'
project(':test').projectDir = file('当前工程的绝对路径')
通常我会使用这种方式引用自己写的库进行调试,非常的方便
但有的时候会遇到同时引入了 AAR 和源码的情况,我们可以使用 include + project,结合一些其他的配置,来实现 AAR 和源码的快速切换,具体步骤如下:
//步骤一:在 settings.gradle 中引入源码工程
include ':test'
project(':test').projectDir = file('当前工程的绝对路径')
//步骤二:在根 build.gradle 下进行如下配置
allprojects {
configurations.all {
resolutionStrategy {
dependencySubstitution {
substitute module("com.dream:test") with project(':test')
}
}
}
}
结合官网提供的文档传送门去查看,效果杠杠的
配置阶段
配置阶段的任务是执行各项目下的build.gradle
脚本,完成Project的配置,并且构造Task
任务依赖关系图以便在执行阶段按照依赖关系执行Task
。 该阶段也是我们最常接触到的构建阶段,比如应用外部构建插件apply plugin: 'com.android.application'
,配置插件的属性android{ compileSdkVersion 30 ...}
等。每个build.gralde
脚本文件对应一个Project
对象,在初始化阶段创建,Project
的接口文档。 配置阶段执行的代码包括build.gralde
中的各种语句、闭包以及Task
中的配置段语句,在根目录的build.gradle
中添加如下代码:
println 'build.gradle的配置阶段'
// 调用Project的dependencies(Closure c)声明项目依赖
dependencies {
// 闭包中执行的代码
println 'dependencies中执行的代码'
}
// 创建一个Task
task test() {
println 'Task中的配置代码'
// 定义一个闭包
def a = {
println 'Task中的配置代码2'
}
// 执行闭包
a()
doFirst {
println '这段代码配置阶段不执行'
}
}
println '我是顺序执行的'
调用gradle build
,得到如下结果:
settings 评估完成(settings.gradle 中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项目:root project 'GradleDemo'
> Configure project :
build.gradle的配置阶段
dependencies中执行的代码
Task中的配置代码
Task中的配置代码2
我是顺序执行的
所有项目评估完成(配置阶段结束)
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
构建结束
一定要注意,配置阶段不仅执行build.gradle
中的语句,还包括了Task
中的配置语句。从上面执行结果中可以看到,在执行了dependencies的闭包后,直接执行的是任务test中的配置段代码(Task
中除了Action外的代码段都在配置阶段执行)。 另外一点,无论执行Gradle的任何命令,初始化阶段和配置阶段的代码都会被执行。同样是上面那段Gradle脚本,我们执行帮助任务gradle help
,任然会打印出上面的执行结果。
执行阶段
在配置阶段结束后,Gradle会根据任务Task的依赖关系创建一个有向无环图,可以通过Gradle
对象的getTaskGraph
方法访问,对应的类为TaskExecutionGraph,并且,当有向无环图构建完成之后,所有 Task 执行之前,我们可以通过 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 来接收相应的通知,其代码如下所示:
gradle.getTaskGraph().addTaskExecutionGraphListener(new
TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
}
})
然后通过调用gradle <任务名>
执行对应任务。
2. Hook点
Gradle提供了非常多的钩子供开发人员修改构建过程中的行为,为了方便说明,先看下面这张图。 注意:Gradle 执行脚本文件的时候会生成对应的实例,主要有如下三种对象:
1、Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象
2、Project 对象:每一个 build.gradle 都会转换成一个 Project 对象
3、Settings 对象:Seetings.gradle 会转变成一个 Seetings 对象 Gradle 在各个阶段都提供了生命周期回调,在添加监听器的时候需要注意:监听器要在生命周期回调之前添加,否则会导致有些回调收不到
1.Gradle 初始化阶段
- 在 settings.gradle 执行完后,会回调 Gradle 对象的 settingsEvaluated 方法
- 在构建所有工程 build.gradle 对应的 Project 对象后,也就是初始化阶段完毕,会回调 Gradle 对象的 projectsLoaded 方法
2.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 执行前后会收到这两个方法的回调
3.执行阶段
-
Gradle 会循环执行 Task 及其依赖的 Task
-
在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法
-
在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法
- 当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法 了解了 Gradle 生命周期后,我们就可以根据自己的需求添加 Hook。例如:我们可以打印 Gradle 构建过程中,各个阶段及各个 Task 的耗时
获取构建各阶段耗时情况
在 settings.gradle 添加如下代码:
//初始化阶段开始时间
long beginOfSetting = System.currentTimeMillis()
//配置阶段开始时间
def beginOfConfig
//配置阶段是否开始了,只执行一次
def configHasBegin = false
//存放每个 build.gradle 执行之前的时间
def beginOfProjectConfig = new HashMap()
//执行阶段开始时间
def beginOfTaskExecute
//初始化阶段执行完毕
gradle.projectsLoaded {
println "初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms"
}
//build.gradle 执行前
gradle.beforeProject {Project project ->
if(!configHasBegin){
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project,System.currentTimeMillis())
}
//build.gradle 执行后
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}"
}
//执行 Gradle 命令
./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
二、Project
每一个 build.gradle 都有一个与之对应的 Project 实例,而在 build.gradle 中,我们通常都会配置一系列的项目依赖,如下面这个依赖:
implementation 'com.github.bumptech.glide:glide:4.8.0'
类似于 implementation、api 这种依赖关键字,在本质上它就是一个方法调用,在上面,我们使用 implementation() 方法传入了一个 map 参数,参数里面有三对 key-value,完整写法如下所示:
implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0'
当我们使用 implementation、api 依赖对应的 aar 文件时,Gradle 会在 repository 仓库 里面找到与之对应的依赖文件,你的仓库中可能包含 jcenter、maven 等一系列仓库,而每一个仓库其实就是很多依赖文件的集合服务器, 而他们就是通过上述的 group、name、version 来进行归类存储的。
1、Project 核心 API 分解
在 Project 中有很多的 API,但是根据它们的 属性和用途 我们可以将其分解为 六大部分,如下图所示: 对于 Project 中各个部分的作用,我们可以先来大致了解下,以便为 Project 的 API 体系建立一个整体的感知能力,如下所示:
-
1)、
Project API
:让当前的 Project 拥有了操作它的父 Project 以及管理它的子 Project 的能力。 -
2)、
Task 相关 API
:为当前 Project 提供了新增 Task 以及管理已有 Task 的能力。 -
3)、
Project 属性相关的 Api
:Gradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我们拥有了为 Project 添加额外属性的能力。 -
4)、
File 相关 Api
:Project File 相关的 API 主要用来操作我们当前 Project 下的一些文件处理。 -
5)、
Gradle 生命周期 API
:即我们在上面讲解过的生命周期 API。 -
6)、
其它 API
:添加依赖、添加配置、引入外部文件等等零散 API 的聚合。
注意:API 的演示如果没做特殊说明,则是在 app 的 build.gradle 文件下操作的
2、Project API
Project API 文档,我们主要介绍一些常用的 API
1、getRootProject 方法
获取根 Project 对象
println getRootProject()
//运行结果
> Configure project :app
root project 'GradleDemo'
2、getRootDir 方法
获取根目录文件夹路径
println getRootDir()
//运行结果
> Configure project :app
/Users/ex-shenkai001/AndroidStudioProjects/Demo
3、getBuildDir 方法
获取当前 Project 的 build 文件夹路径
println getBuildDir()
//运行结果
> Configure project :app
/Users/ex-shenkai001/AndroidStudioProjects/Demo/app/build
4、getParent 方法
获取当前父 Project 对象
println getParent()
//运行结果
> Configure project :app
root project 'GradleDemo'
5、getAllprojects 方法
获取当前 Project 及其子 Project 对象,返回值是一个 Set 集合
//当前在根工程的 build.gradle 文件下
println getAllprojects()
//打印结果
> Configure project :
[root project 'GradleDemo', project ':app']
我们还可以使用其闭包的形式
allprojects {
println it
}
//运行结果
> Configure project :
root project 'GradleDemo'
project ':app'
//我们通常会使用闭包的语法在根 build.gradle 下进行相关的配置,如下:
allprojects {
repositories {
google()
jcenter()
}
}
注意:根 Project 与其子 Project 组成了一个树形结构,但这颗树的高度也仅仅被限定为了两层
6、getSubprojects 方法
获取当前 Project 下的所有子 Project 对象,返回值是一个 Set 集合
//当前在根工程的 build.gradle 文件下
println getSubprojects()
//运行结果
> Configure project :
[project ':app']
同样我们也可以使用其闭包的形式
subprojects {
println it
}
//运行结果
> Configure project :
project ':app'
7、apply 系列方法
引用插件
//引用第三方插件
apply plugin: 'com.android.application'
//引用脚本文件插件
apply from: 'config.gradle'
8、configurations 闭包
编写 Project 一些相关的配置,如全局移除某个依赖
configurations {
all*.exclude group: '组名', module: '模块名'
}
9、project 系列方法
指定工程实例,然后在闭包中对其进行相关的配置
project("app") {
apply plugin: 'com.android.application'
}
3、扩展属性
扩展属性作用:方便我们全局的一个使用。类似 Java 中,在工具类里面定义静态方法
1、扩展属性定义
我们可以通过以下两种方式来定义扩展属性:
1、通过 ext 关键字定义扩展属性
2、在 gradle.properties 下定义扩展属性
1、通过 ext 关键字定义扩展属性
通过 ext 定义扩展属性的语法有两种:
//当前在根 build.gradle 下
//方式1:ext.属性名
ext.test = 'erdai666'
//方式2:ext 后面接上一个闭包
ext{
test1 = 'erdai777'
}
2、在 gradle.properties 下定义扩展属性
通过 gradle.properties 定义扩展属性,直接使用 key=value 的形式即可:
// 在 gradle.properties 中
mCompileVersion = 27
// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()
2、扩展属性调用
1、ext 定义的扩展属性调用的时候可以去掉 ext 前缀直接调用
2、ext 定义的扩展属性也可以通过 当前定义扩展属性的 Project 对象.ext.属性名 进行调用
3、gradle.properties 定义的扩展属性直接通过属性名调用即可 下面我们在 app 的 build.gradle 下进行演示:
/**
* 下面这种写法之所以能这么写
* 1、ext 定义的扩展属性调用的时候可以去掉 ext 前缀直接调用
* 2、子 Project 能拿到根 Project 中的属性和方法
*/
println test
println test1
println test2
//2、ext 定义的扩展属性也可以通过 当前定义扩展属性的 Project 对象.ext.属性名 调用
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 长啥样
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 ,用来存放扩展属性
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 进行引用
apply from: 'config.gradle'
步骤3: 在 app 的 build.gradle 里面进行扩展属性的调用
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
// 在 rootProject 下的 build.gradle 中
//============================== 1、file 方法应用============================ //通过 file 方法传入一个相对路径,返回值是一个 file 对象
println file("config.gradle").text
//通过 new File 方式传入一个绝对路径
def file = new File("/Users/ex-shenkai001/AndroidStudioProjects/Demo/config.gradle")
println file.text
//上述两者打印结果相同,如下截图
//============================== 2、files 方法应用============================ //通过 files 方法传入多个相对路径,返回值是一个 ConfigurableFileCollection 即文件集合
files("config.gradle", "build.gradle").each {
println it.name
}
//运行结果
> Configure project :
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、传入路径
copy {
from getRootDir().path + "/config.gradle"
into getProjectDir().path
}
//2、传入文件
copy {
from file('../config.gradle')
into getProjectDir()
}
//最终结果是这两种方式都能拷贝成功
2、文件夹拷贝
例如我们实现:将根目录下的 gradle 文件夹下的所有文件和文件夹拷贝到 app 目录下的 gradle 文件夹
copy {
// 既可以拷贝文件,也可以拷贝文件夹
// 这里是将 app moudle 下生成的 apk 目录拷贝到
// 根工程下的 build 目录
from file("build/outputs/apk")
into getRootProject().getBuildDir().path + "/apk/"
exclude {
// 排除不需要拷贝的文件
}
rename {
// 对拷贝过来的文件进行重命名
}
}
3、fileTree 文件树映射
Project 对象提供了 fileTree 方法,方便我们将一个目录转换为文件树,然后对文件树进行相关的逻辑处理,它接收的参数和 file/files 类似,也是一个相对路径
例如我们实现:遍历根目录下的 gradle 文件夹,并打印文件及文件夹的名称
fileTree('../gradle/'){ FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
println fileTreeElement.name
}
}
//运行结果
> Configure project :app
wrapper
gradle-wrapper.jar
gradle-wrapper.properties
我们通常会在 app 的 build.gradle 下看到这么一个配置语句:
implementation fileTree(include: ['*.jar'], dir: 'libs')
他实际上是调用了 fileTree 接收 Map 参数的重载方法:
ConfigurableFileTree fileTree(Map<String, ?> var1);
这句配置语句的意思就是:引入当前 project 目录下的 libs 文件夹下的所有 jar 包
4)、其他API
1.buildscript解读
我们通常在新建一个 Android 项目的时候可以看到根 build.gradle 有这么一段配置:
buildscript {
//插件仓库地址
repositories {
google()
mavenCentral()
}
//插件依赖
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
}
}
它的作用是:引入 Gradle 构建过程中的一些插件 实际上上面这段代码的完整写法如下:
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 进去看下源码,这里就不对源码进行分析了
2.app moudle 下的 dependencies
不同于 根项目 buildscript 中的 dependencies 是用来配置我们 Gradle 工程的插件依赖的,而 app moudle 下的 dependencies 是用来为应用程序添加第三方依赖的。关于 app moudle 下的依赖使用这里我们 需要注意下 exclude 与 transitive 的使用 即可,示例代码如下所示:
implementation(rootProject.ext.dependencies.glide) {
// 排除依赖:一般用于解决资源、代码冲突相关的问题
exclude module: 'support-v4'
// 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
// 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B
// 中所使用的 C 中的依赖,默认都是不打开,即 false
transitive false
}
5)、exec外部命令执行
Project 对象提供了 exec 方法,方便我们执行外部的命令
我们可以在 linux 下通过如下命令去移动一个文件夹:
mv -f 源文件路径 目标文件路径
现在我们在 Gradle 下去进行这一操作
例如我们实现:使用外部命令,将我们存放的 apk 目录移动到项目的根目录 ,如下:
task taskMove() {
doLast {
// 在 gradle 的执行阶段去执行
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 中的一系列 Action)。
1. doFirst、doLast 介绍
首先我们要搞懂 Action 这个概念,Action 本质上是一个执行动作,它只有在我们执行当前 Task 时才会被执行,Gradle 执行阶段本质上就是在执行每个 Task 中的一系列 Action
doFirst,doLast 是 Task 给我们提供的两个 Action
doFirst 表示:Task 执行最开始时被调用的 Action
doLast 表示: task 执行完时被调用的 Action
值的注意的是:doFirst 和 doLast 可被多次添加执行.
task shenkai{
println 'task start...'
doFirst {
println 'doFirst1'
}
doLast {
println 'doLast1'
}
doLast {
println 'doLast2'
}
println 'task end...'
}
//执行当前 task
./gradlew shenkai
//打印结果如下
> Configure project :app
task start...
task end...
> Task :app:shenkai
doFirst1
doLast1
doLast2
从上述打印结果我们可以发现
1、println 'task start...'
, println 'task end...'
这两句的代码在 Gradle 配置阶段就被执行了
2、doFirst,doLast 中的代码是在 Gradle 执行阶段,执行 erdai 这个 task 时被执行的
因此也验证了一开始我说的那个结论: Gradle 配置阶段,除 Task 的 Action 中编写的代码都会被执行
2、Task 的定义及配置
因为 Task 和 Project 是相互关联的,Project 中提供了一系列创建 Task 的方法,下面介绍一些常用的:
//1、创建一个名为 task1 的 Task
task task1
//2、创建一个名为 task2 的 Task,并通过闭包进行相应的配置
task task2{
//指定 task 的分组
group 'erdai666'
doFirst{
}
}
//3、创建一个名为 task3 的 Task,该 Task 继承自 Copy 这个 Task,依赖 task2
task task3(type: Copy){
dependsOn "task2"
doLast{
}
}
//4、创建一个名为 task4 的 Task 并指定了分组和描述
task task4(group: "erdai666", description: "task4") {
doFirst {
}
doLast {
}
}
//5、通过 Project 对象的 TaskContainer 创建名为 task5 的 Task
tasks.create("task5"){
}
//6、通过 Project 对象的 TaskContainer 创建名为 task6 的 Task
//相对于 5 ,只是调用了不同的重载方法而已
tasks.create(name: "task6"){
}
Task 的属性
需要注意的是,不管是哪一种 task 的定义方式,在 "()" 内我们都可以配置它的一系列属性,如下:
project.task('JsonChao3', group: "JsonChao", description: "my tasks",
dependsOn: ["JsonChao1", "JsonChao2"] ).doLast {
println "execute JsonChao3 Task"
}
目前 官方所支持的属性 可以总结为如下表格:
选型 | 描述 | 默认值 |
---|---|---|
"name" | task 名字 | 无,必须指定 |
"type" | 需要创建的 task Class | DefaultTask |
"action" | 当 task 执行的时候,需要执行的闭包 closure 或 行为 Action | null |
"overwrite" | 替换一个已存在的 task | false |
"dependsOn" | 该 task 所依赖的 task 集合 | [] |
"group" | 该 task 所属组 | null |
"description" | task 的描述信息 | null |
"constructorArgs" | 传递到 task Class 构造器中的参数 | null |
使用 ext 给 task 自定义需要的属性
当然,除了使用已有的属性之外,我们也可以 使用 ext 给 task 自定义需要的属性,代码如下所示:
task Gradle_First() {
ext.good = true
}
task Gradle_Last() {
doFirst {
println Gradle_First.good
}
doLast {
println "I am not $Gradle_First.name"
}
}
Task 类型介绍
一般我们创建的 Task 默认是继承 DefaultTask,我们可以通过 type 属性让他继承其他的类,也可以通过 extends 关键字直接指定,Gradle 自带的有 Copy、Delete 等等,如下:
// 1、继承 Delete 这个类,删除根目录下的 build 文件
task deleteTask(type: Delete) {
delete rootProject.buildDir
}
//通过 extends 关键字指定
class DeleteTask extends Delete{
}
DeleteTask deleteTask = tasks.create("deleteTask",DeleteTask)
deleteTask.delete(rootProject.buildDir)
// 2、继承 Copy 这个类
task copyTask(type: Copy) {
//...
}
//通过 extends 关键字指定
class CopyTask extends Copy{
//...
}
TaskContainer 介绍
TaskContainer 你可以理解为一个 Task 容器,Project 对象就是通过 TaskContainer 来管理 Task,因此我们可以通过 TaskContainer,对 Task 进行相关的操作,一些常用的 API 如下:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
//创建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)
3. Task 的执行详解
Task 通常使用 doFirst 与 doLast 两个方式用于在执行期间进行操作。其示例代码如下所示:
// 使用 Task 在执行阶段进行操作
task myTask3(group: "MyTask", description: "task3") {
println "This is myTask3"
doFirst {
// 老二
println "This group is 2"
}
doLast {
// 老三
println "This description is 3"
}
}
// 也可以使用 taskName.doxxx 的方式添加执行任务
myTask3.doFirst {
// 这种方式的最先执行 => 老大
println "This group is 1"
}
Task 执行实战
接下来,我们就使用 doFirst 与 doLast 来进行一下实战,来实现 计算 build 执行期间的耗时,其完整代码如下所示:
// Task 执行实战:计算 build 执行期间的耗时
def startBuildTime, endBuildTime
// 1、在 Gradle 配置阶段完成之后进行操作,
// 以此保证要执行的 task 配置完毕
this.afterEvaluate { Project project ->
// 2、找到当前 project 下第一个执行的 task,即 preBuild task
def preBuildTask = project.tasks.getByName("preBuild")
preBuildTask.doFirst {
// 3、获取第一个 task 开始执行时刻的时间戳
startBuildTime = System.currentTimeMillis()
}
// 4、找到当前 project 下最后一个执行的 task,即 build task
def buildTask = project.tasks.getByName("build")
buildTask.doLast {
// 5、获取最后一个 task 执行完成前一瞬间的时间戳
endBuildTime = System.currentTimeMillis()
// 6、输出 build 执行期间的耗时
println "Current project execute time is ${endBuildTime - startBuildTime}"
}
}
//执行 build 任务
./gradlew build
//打印结果
Current project execute time is 21052
Task 的依赖和执行顺序
在 Gradle 中,有三种方式可以指定 Task 执行顺序:
1、dependsOn 强依赖方式
2、通过 Task 输入输出
3、通过 API 指定执行顺序
1、dependsOn 强依赖方式
dependsOn 强依赖方式可细分为静态依赖和动态依赖
- 静态依赖:在创建 Task 的时候,直接通过 dependsOn 指定需要依赖的 Task
- 动态依赖:在创建 Task 的时候,不知道需要依赖哪些 Task,需通过 dependsOn 动态依赖符合条件的 Task
//=================================静态依赖=============================
task taskA{
doLast {
println 'taskA'
}
}
task taskB{
doLast {
println 'taskB'
}
}
task taskC(dependsOn: taskA){//多依赖方式 dependsOn:[taskA,taskB]
doLast {
println 'taskC'
}
}
//执行 taskC
./gradlew taskC
//打印结果
> Task :app:taskA
taskA
> Task :app:taskC
taskC
上述代码,当我们执行 taskC 的时候,因为依赖了 taskA,因此 taskA 会先执行,在执行 taskC
注意:当一个 Task 依赖多个 Task 的时候,被依赖的 Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响,如下:
task taskC(dependsOn:[taskA,taskB]){
doLast {
println 'taskC'
}
}
taskA 和 taskB 的执行顺序是随机的。
//=================================动态依赖=============================
// Task 动态依赖方式
task lib1 {
doLast{
println 'lib1'
}
}
task lib2 {
doLast{
println 'lib2'
}
}
task lib3 {
doLast{
println 'lib3'
}
}
// 动态指定taskX依赖所有以lib开头的task
task taskDynamic{
// 动态指定依赖
dependsOn tasks.findAll{ Task task ->
return task.name.startsWith('lib')
}
doLast {
println 'taskDynamic'
}
}
//执行 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,如下:
ext {
testFile = file("${projectDir.path}/test.txt")
if(testFile != null || !testFile.exists()){
testFile.createNewFile()
}
}
//输出 Task
task outputTask {
outputs.file testFile
doLast {
outputs.getFiles().singleFile.withWriter { writer ->
writer.append("erdai666")
}
println "outputTask 执行结束"
}
}
//输入 Task
task inputTask {
inputs.file testFile
doLast {
println "读取文件内容:${inputs.files.singleFile.text}"
println "inputTask 执行结束"
}
}
//测试 Task
task testTask(dependsOn: [outputTask, inputTask]) {
doLast {
println "testTask1 执行结束"
}
}
//执行 testTask
./gradlew testTask
//理论上会先执行 outputTask,在执行 inputTask,最后执行 testTask
//但实际打印结果
> Task :app:inputTask
读取文件内容:
inputTask 执行结束
> Task :app:outputTask
outputTask 执行结束
> Task :app:testTask
testTask1 执行结束
最终我对 inputTask 指定了具体依赖才达到了预期效果:
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
下面我们通过代码来演示一下:
//======================================= mustRunAfter ===========================
task taskX{
doLast {
println 'taskX'
}
}
task taskY{
mustRunAfter taskX
doLast {
println 'taskY'
}
}
task taskXY(dependsOn: [taskX,taskY]){
doLast {
println 'taskXY'
}
}
//执行 taskXY
./gradlew taskXY
//打印结果
> Task :app:taskX
taskX
> Task :app:taskY
taskY
> Task :app:taskXY
taskXY
//======================================= finalizeBy ===========================
task taskI{
doLast {
println 'taskI'
}
}
task taskJ{
finalizedBy taskI
doLast {
println 'taskJ'
}
}
task taskIJ(dependsOn: [taskI,taskJ]){
doLast {
println 'taskIJ'
}
}
//执行 taskIJ
./gradlew taskIJ
//打印结果
> Task :app:taskJ
taskJ
> Task :app:taskI
taskI
> Task :app:taskIJ
taskIJ
四、自定义 Task 挂接到 Android 应用构建流程
1)、Task 依赖关系插件介绍
我们可以引入如下插件来查看 Task 的一个依赖关系
//1、在根 build.gradle 添加如下代码
buildscript {
repositories {
//...
maven{
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
//..
classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.5"
}
}
// 2、在 app 的 build.gradle 中应用插件
apply plugin: com.dorongold.gradle.tasktree.TaskTreePlugin
/**
* 3、在命令行中执行:./gradlew <任务名> taskTree --no-repeat 命令即可查看
* 这里以执行 build 这个 task 为例
*/
./gradlew build taskTree --no-repeat
经过上面 3 步,我们看下依赖关系图,仅截取部分:
2)、自定义 Task 挂接到 Android 构建流程
我们知道,Gradle 在执行阶段就是执行 Task 及其依赖的 Task,就比如上面截图的 build Task 的关系依赖图,它会按照这个依赖图有条不紊的去执行。
那么如果我想把自己自定义的 Task 挂接到这个构建流程,该怎么做呢?
1、通过 dependsOn 指定
注意: 单独使用 dependsOn ,必须让构建流程中的 Task 依赖我们自定义的 Task,否则我们的 Task 不会生效
如下代码演示一下:
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}
afterEvaluate {
//1、找到需要的构建流程 Task
def mergeDebugResources = tasks.findByName("mergeDebugResources")
//2、通过 dependsOn 指定
mergeDebugResources.dependsOn(myCustomTask)
//如果换成下面这种写法则自定义 Task 不会生效
//myCustomTask.dependsOn(mergeDebugResources)
}
接下来我们验证一下
首先看一眼 Task 依赖关系图: 我们自定义的 Task 挂接到了 mergeDebugResources 上
执行下 build 这个 Task,可以发现我们的 Task 被执行了:
2、通过 finalizedBy 指定
在某个 Task 执行完成后,指定需要执行的 Task
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}
afterEvaluate {
def mergeDebugResources = tasks.findByName("mergeDebugResources")
//将 myCustomTask 挂接在 mergeDebugResources 后面执行
mergeDebugResources.finalizedBy(myCustomTask)
}
3、通过 mustRunAfter 配合 dependsOn 指定
在两个 Task 之间,插入自定义的 Task
task myCustomTask{
doLast {
println 'This is myCustomTask'
}
}
afterEvaluate {
//在 mergeDebugResources 和 processDebugResources 之间插入 myCustomTask
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 对象
./gradlew project
2)、查看 module 下所有的 task
./gradlew $moduleName:tasks
//演示
//查看 app 下的所有 Task
./gradlew app:tasks
//查看根 Project 的所有 Task
./gradlew tasks
3)、执行一个 Task
./gradlew $taskName
//执行 build Task
./gradlew build
4)、查看 module 下的第三方库依赖关系
./gradlew $moduleName:dependencies
//查看 app 下的第三方库依赖关系
./gradlew app:dependencies
参考
- Gradle 系列 (二)、Gradle 技术探索(sweetying参考了jsonchao和renxhui)
- 深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)
- Gradle学习系列(二):Gradle核心探索
- Gradle基础 构建生命周期和Hook技术