从Gradle生命周期到自定义Task挂接到Build构建流程全解

我们知道Gradle构建工具非常的灵活,它提供了一系列的Api让我们有能力去修改或定制项目的构建过程,在项目的编译过程中,插入我们自己的 Task 并执行相关的操作,如:多渠道打包,ASM代码织入和资源的检测等。

要想实现这些功能,首先就需要明白Gradle的构建流程,知道Gradle在每个阶段都做了什么,加上自己需要在哪个阶段做什么事件,就可以通过Gradle提供的Api,插入我们想要执行的代码。因此理解Gradle的生命周期和Hook点,有助于我们梳理、扩展项目的构建流程。

Gradle的构建过程有着固定的生命周期,理解Gradle的生命周期和Hook点,有助于帮你梳理、扩展项目的构建流程。

一、Gradle 构建生命周期

Gradle的构建过程有着固定的生命周期,分别是:

  1. 初始化阶段
  2. 配置阶段
  3. 执行阶段

下面就详细介绍一下这三个阶段都做了什么事情。

1、初始化阶段

初始化阶段的主要任务:就是创建项目的层次结构,并为每一个项目创建一个Project 实例对象。
在初始化阶段中,会执行 settings.gradle 脚本,并读取 settings.gradle 中的 include 信息,进而为每一个工程(即build.gradle 脚本文件)创建一个与之对应的 Project 对象,最终形成一个项目的层次结构。 一个 settings.gradle 脚本对应一个 Settings 对象(在Gradle 初始化的时候创建),而我们最常用来声明项目的层次结构的 include标签 就是 Settings 类下的一个方法,Settings 类还有如下方法,都可以直接在 settings.gradle 文件中直接访问:

1-1、应用其它工程的模块

比如:通过 include 和 project 方法,可以实现引用任何位置下的工程模块:

include ':myjson' // 需要参与构建的模块名称
project(':myjson').projectDir = file('/Users/WorkSpace/AndroidDemo/MyJson/myjson')
  • include方法 :指定参与构建的模块的名称,模块名前需要加冒号(:),模块名称可以任取。
  • project方法:加载指定的模块,并为该模块设置一个工程路径,参数必须与include参数一致 。

这样本工程就可以引用到其它位置的模块了,如果引用的是一个 library 模块,那么在本工程的模块中,就可以依赖这个 library 模块了,如:

implementation project(":myjson")

1-2、监听初始化过程

在上面的 Settings 类的方法列表中,有一个 getGradle() 方法,返回一个 Gradle 对象,通过这个 Gradle 对象,我们可以监听 Gradle 构建过程中 各个生命周期方法的回调,如在 settings.gradle 文件中,增加如下监听:

gradle.addBuildListener(new BuildListener() {void buildStarted(Gradle var1) {println 'buildStarted()->开始构建' }void settingsEvaluated(Settings var1) {println 'settingsEvaluated()->settings评估完成(settins.gradle中代码执行完毕)'// var1.gradle.rootProject 这里访问Project对象时会报错,还未完成Project的初始化}void projectsLoaded(Gradle var1) {println 'projectsLoaded()->项目结构加载完成(初始化阶段结束)'println 'projectsLoaded()->初始化结束,可访问根项目:' + var1.gradle.rootProject}void projectsEvaluated(Gradle var1) {println 'projectsEvaluated()->所有项目评估完成(配置阶段结束)'}void buildFinished(BuildResult var1) {println 'buildFinished()->构建结束 '}
})

在 build.gradle 文件中,也可以拿到 Gradle 对象,但如果在 build.gradle 文件中添加上面的监听事件的话,buildStarted,settingsEvaluated 和 projectsLoaded 方法是不会回调的,因为这三个方法是在初始化阶段 执行 settings.gradle 文件的时候执行的,但另外两个方法是会回调的。

在根工程的 build.gradle 文件中,增加如下代码,以便更好的观察上面添加的监听事件的回调时机:

allprojects {afterEvaluate {println "${name}:配置完成"}
}

打印信息如下:

settingsEvaluated()->settings评估完成(settins.gradle中代码执行完毕)
projectsLoaded()->项目结构加载完成(初始化阶段结束)
projectsLoaded()->初始化结束,可访问根项目:root project 'KotlinLearning'Configure project :
KotlinLearning:配置完成Configure project :app
app配置完成Configure project :kotlinlearning
kotlinlearning配置完成projectsEvaluated()->所有项目评估完成(配置阶段结束)
buildFinished()->构建结束 

2、配置阶段

配置阶段的任务:是执行各项目下的build.gradle脚本,完成 Project 的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task。

2-1、配置阶段执行的代码

配置阶段也是我们最常接触到的构建阶段,比如应用外部构建插件apply plugin: 'com.android.application',配置插件的属性android{ compileSdkVersion 25 ...}等。

每个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 '我是顺序执行的'

打印信息如下:

build.gradle的配置阶段
dependencies中执行的代码
Task中的配置代码 Task中的配置代码2 我是顺序执行的

从上面的打印信息可以看出,在执行了dependencies的闭包后,直接执行的是任务test中的配置段代码(Task中除了Action外的代码段都在配置阶段执行)

需要注意的是,执行任何 Gradle 命令,在初始化阶段和配置阶段的代码都会被执行。

2-2、Task 依赖关系配置完成监听

配置阶段另外一个重要的任务就是 构建 Task 依赖关系的有向无环图,说简单一点,就是给 所有的 Task 排一个执行顺序,在执行阶段的时候,就按照这个顺序去执行所有的 Task 任务。

我们可以通过 Gradle 对象的 getTaskGraph 方法来得到该有向无环图对象: TaskExecutionGraph,通过TaskExecutionGraph类的相关方法可以监听 Task 依赖关系构建完成的通知,如:

  • void whenReady(Closure var1)
  • void addTaskExecutionGraphListener(TaskExecutionGraphListener var1)

在 build.gradle 文件中,增加如下代码:

gradle.getTaskGraph().whenReady {println "whenReady Task依赖关系构建完成,size=${it.allTasks.size()}"it.allTasks.forEach { task ->println "${task.name}"}
}gradle.getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {@Overridevoid graphPopulated(TaskExecutionGraph graph) {println "graphPopulated Task依赖关系构建完成 size=${graph.allTasks.size()}"graph.allTasks.forEach { task ->println "${task.name}"}}
})

点击运行按钮,运行app的时候,就可以打印出 Task 任务列表:

>whenReady Task依赖关系构建完成,size=40 preBuild preDebugBuild compileDebugAidl compileDebugRenderscript generateDebugBuildConfig checkDebugAarMetadata generateDebugResValues generateDebugResources mergeDebugResources createDebugCompatibleScreenManifests extractDeepLinksDebug processDebugMainManifest processDebugManifest processDebugManifestForPackage processDebugResources compileDebugKotlin javaPreCompileDebug compileDebugJavaWithJavac compileDebugSources mergeDebugNativeDebugMetadata mergeDebugShaders compileDebugShaders generateDebugAssets mergeDebugAssets compressDebugAssets processDebugJavaRes mergeDebugJavaResource checkDebugDuplicateClasses dexBuilderDebug desugarDebugFileDependencies mergeExtDexDebug mergeDexDebug mergeDebugJniLibFolders mergeDebugNativeLibs stripDebugDebugSymbols validateSigningDebug writeDebugAppMetadata writeDebugSigningConfigVersions packageDebug assembleDebug graphPopulated Task依赖关系构建完成 size=40 preBuild 。。。。。 assembleDebug

3、执行阶段

执行阶段就是根据配置阶段构建的 Task 依赖关系去执行相关的 Task。

当我们运行项目的时候,Gradle 就会根据 Task的依赖关系依次去执行相关的Task。还可以通过 gradle 命令去执行指定的 Task,如在控制台中输入如下命令,就可以执行 build 任务:

./gradlew build

build 是任务名称。

二、Gradle Hook点

Gradle 提供了非常多的接口回调以便我们修改构建过程中的行为,整体流程如下图所示:

方法说明:

  • Gradle#settingsEvaluated 方法:与BuildListener的settingsEvaluated的执行时机一样,需要在 settings.gradle 文件中添加,否则无效。
  • Gradle#projectsLoaded 方法:与BuildListener的projectsLoaded的执行时机一样,需要在 settings.gradle 文件中添加,否则无效。
  • Gradle#beforeProject方法,在每个工程 配置之前执行,
  • Project#beforeEvaluate方法,在调用该方法的 Project 配置之前执行,如果在 module 的 build.gradle 文件中调用该方法,是不会执行的,因为执行到build.gradle的时候,已经过了beforeEvaluate执行的时间节点了。
  • Gradle#afterProject方法,在每个工程配置完成之后,执行该方法,哪怕构造过程出错,也会调用,截止到出错的文件。
  • Project#afterEvaluate方法,在调用该方法的Project 配置执行完成之后调用,在这个时机,该工程就配置完成了,如在这个方法回调中就可以获取到所有的Task任务了。
  • Gradle#projectsEvaluate方法,当所有的工程都配置完成之后,执行该方法,与BuildListener的projectsEvaluated方法的执行时机是一样的。

三、指定 Task 执行顺序

在Gradle中,有三种方式指定task的执行顺序:

  • dependsOn 强依赖方式
  • 通过Task输入输出
  • 通过API指定执行顺序

1、通过dependsOn强依赖方式指定

dependsOn 强依赖的方式可以细分为 静态依赖 动态依赖

  • 静态指定依赖:在创建task的时候,就明确的知道定义的 Task需要依赖的task是什么,直接通过 dependsOn 参数或者 dependsOn 方法指定所依赖的Task。
    task提供了dependsOn,finalizedBy方法来管理task之间的依赖关系,依赖关系表达的是执行这个task时所需要依赖的其他task,也就是说这个task不能被单独执行,执行这个 Task 之前或之后需要执行另外的task。
  • 动态指定依赖:在创建 Task的时候,不知道需要依赖哪些 Task,通过 dependsOn 方法动态依赖符合条件的 Task。

示例如下所示:

静态指定依赖

在定义 Task 的时候,直接为 Task 指定 dependsOn 参数,值为 另一个被依赖的 Task 的名字:

task taskX {doLast{println 'taskX'}
}task taskY {doLast{println 'taskY'}
}task taskZ(dependsOn:taskX) { // 多依赖方式:dependsOn:[taskX,taskY]doLast{println 'taskZ'}
}// 当我们执行taskZ的时候,由于依赖了taskX,则taskX会先执行,然后才会执行:taskZ

除了在 Task 定义的时候指定 Task 的依赖之外,还可以通过 Task 的 dependsOn 方法,为 Task 指定依赖:

task taskZ {// 定义Task的时候不指定依赖doLast{println 'taskZ'}
}// 通过task的dependsOn方法,也可以指定task的依赖task。
taskZ.dependsOn(taskX,taskY)

当一个task依赖多个task的时候,被依赖的task之间,如果没有依赖关系的话,那么他们的执行顺序是随机的,并无影响。

动态添加依赖

当 Task 在定义的时候,不知道所依赖的 Task 是什么,在配置阶段,通过条件找出符合条件的 Task ,并进行依赖。

task lib1 {doLask{println 'lib1'}
}
task lib2 {doLask{println 'lib2'}
}
task lib3 {doLask{println 'lib3'}
}// 动态指定taskX依赖所有以lib开头的task
task taskX{// 动态指定依赖dependsOn this.tasks.findAll{ task->return task.name.startsWidth('lib')}doLast {println 'taskZ'}
}

2、通过Task输入输出指定

当一个参数作为TaskA的输出参数,同时又作为TaskB的输入参数。那么当执行TaskB的时候先要执行TaskA。即输出的Task先于输入的Task执行。 如:

ext {testFile = file("${this.buildDir}/test.txt")
}// 生产者 Task
task producer {outputs.file testFiledoLast {outputs.getFiles().singleFile.withWriter { writer ->writer.append("我爱中国")}println "producer Task 执行结束"}
}// 消费者 Task
task consumer {inputs.file testFiledoLast {println "读取文件内容:${inputs.files.singleFile.text}"println "consumer Task 执行结束"}
}task testTask(dependsOn: [producer, consumer]) {doLast {println "测试Task执行结束"}
}
文件 testFile 是 producer 的输出参数,是 consumer 的输入参数,所以 producer 优先于 consumer 执行。

3、通过API指定执行顺序

可以指定Task执行顺序的方法还有:

  • mustRunAfter:指定必须在哪个Task执行完成之后在执行,如 taskA.mustRunAfter(taskB),表示 taskA 必须在 taskB 之后执行。
  • shouldRunAfter:跟mustRunAfter类似,区别在于不强制。不常用。
  • finalizedBy :在任务结束之后执行指定的 Task。如:taskA.finalizedBy(taskB),表示在 taskA 执行完成之后,再执行 taskB 任务。

示例代码:

通过mustRunAfter 指定task执行顺序:

task taskA {doLast {println "taskA 执行"}
}task taskB {mustRunAfter(taskA)doLast {println "taskB 执行"}
}task testAB(dependsOn: [taskA, taskB]) {doLast {println "testAB 执行"}
}

执行 任务:testAB,输出信息为:

shouldRunAfter与mustRunAfter类似,这就不在测试了。

通过 finalizedBy 指定 Task 的执行顺序:

task taskA {doLast {println "taskA 执行"}
}task taskB {finalizedBy(taskA)doLast {println "taskB 执行"}
}task testAB(dependsOn: [taskA, taskB]) {doLast {println "testAB 执行"}
}

同样的代码,就把 mustRunAfter 替换成了 finalizedBy 方法,按照finalizedBy 方法的含义,就是:taskB 执行结束后,再执行 taskA。运行 testAB 任务,输出信息为:

这与我们想象的结果一致。

四、自定义 Task 挂接到构建流程

1、打印 Task 依赖关系

如果对一个构建流程的任务依赖关系不熟悉的话,可以使用第三方插件来查看,在根项目的build.gradle中添加如下代码:

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 <任务名> taskTree --no-repeat,即可看到指定Task的依赖关系,比如在Android构建中查看 app:assembleDebug任务的依赖关系:

2、自定义 Task 挂机到构建流程

我们知道,Gradle 的构建流程是通过执行一系列的 Task 任务来完成的,每一个Task完成自己独特的工作之后,就根据Task的依赖关系,执行下一个 Task 任务。如:preBuild(开始构建之前执行的Task任务)->mergeDebugResources(合并资源文件的Task任务)->assembleDebug(生成debug包的任务)。

如果想把自己写的Task也插入到构建流程中,在运行的时候自动执行我们的Task任务,又该如何做呢?这个时候就可以通过指定Task的执行顺序,把我们的 Task 加入到构建流程中,具体来说就是:明确自己的任务需要插入到哪个任务之后或者之前,接着找到这个任务,并把自己的任务插入到这个任务的前面或者后面

可以通过如下方式,把自定义的 Task 插入到指定的任务

  • 通过 dependsOn 或者 finalizedBy 方法指定
  • 单独使用dependsOn 方法,最好是让编译流程的 Task 依赖于自己的 Task,否则不生效
  • finalizedBy :可以单独使用,指定在某个 Task 执行结束之后,执行自己的 Task
  • 通过 mustRunAfter 结合 dependsOn 一起指定

举例说明:

2-1、在 某个 Task 之前执行 自定义 Task:dependsOn

afterEvaluate {// 1\. 找到需要依赖自己 Task的构建流程的Taskdef mergeResourcesTask = tasks.findByName("mergeDebugResources")println "mergeResourcesTask=$mergeResourcesTask"// 2\. 通过dependsOn 方法,插入到指定Task之前mergeResourcesTask.dependsOn(checkBigImage)
}

插入自定义 Task 之前的 Task 依赖关系图:

 插入自定义 Task 之后的 Task 依赖关系图:

从Task的依赖关系图可以看出,mergeDebugResources 任务确实是依赖于checkBigImage任务了,这样在运行编译app的时候,就会在执行 mergeDebugResources 任务的时候,先去执行 checkBigImage 任务了。

2-2、在某个 Task 之后,执行自定义的Task:finizedBy

afterEvaluate {// 1\. 找到需要依赖自己 Task的构建流程的Taskdef mergeResourcesTask = tasks.findByName("mergeDebugResources")println "mergeResourcesTask=$mergeResourcesTask"// 2\. 通过 finalizedBy 方法,插入到指定Task之后mergeResourcesTask.finalizedBy(checkBigImage)
}

2-3、在两个 Task 之间,插入自定义的 Task :mustRunAfter 结合 dependsOn

afterEvaluate {// 1\. 找到需要挂接的Taskdef mergeResourcesTask = tasks.findByName("mergeDebugResources")def processDebugResourcesTask = tasks.findByName("processDebugResources")// 2.让自定义的 Task 在 mergeDebugResources 任务之后且在 processDebugResources 之前执行checkBigImage.mustRunAfter(mergeResourcesTask)processDebugResourcesTask.dependsOn(checkBigImage)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/80924.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GAN:对抗生成网络,前向传播和后巷传播的区别

目录 GAN&#xff1a;对抗生成网络 损失函数 判别器开始波动很大&#xff0c;先调整判别器 生成样本和真实样本的统一&#xff1a;真假难辨​编辑 文字专图片​编辑 头像转表情包​编辑 头像转3D​编辑 后向传播 1. 前向传播&#xff08;forward&#xff09; 2. 反向传播&…

LVS - DR

LVS-DR 数据流向 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路层来传…

Docker容器监控之 CAdvisor+InfluxDB+Granfana

1. 原命令 通过docker stats命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据&#xff0c;一般小公司够用了。。。。 但是&#xff0c;docker stats统计结果只能是当前宿主机的全部容器&#xff0c;数据资料是实时的&#xff0c;没有地方存储、没有健康指…

游戏服务端性能测试实战总结

导语&#xff1a;近期经历了一系列的性能测试&#xff0c;涵盖了Web服务器和游戏服务器的领域。在这篇文章中&#xff0c;我将会对游戏服务端所做的测试进行详细整理和记录。需要注意的是&#xff0c;本文着重于记录&#xff0c;而并非深入的编程讨论。在这里&#xff0c;我将与…

Ompl初探

在/ompl-1.x.0/build/Release/bin下有很多生成的demo可执行文件 在终端执行 ./demo_Point2DPlanning 测试程序 #include <ompl/base/SpaceInformation.h> #include <ompl/base/spaces/SE3StateSpace.h> #include <ompl/base/StateSpace.h> #include <o…

跨平台图表:ChartDirector for .NET 7.1 Crack

什么是新的 ChartDirector for .NET 7.0 支持跨平台使用&#xff0c;但仅限于 .NET 6。这是因为在 .NET 7 中&#xff0c;Microsoft 停止了用于非 Windows 使用的 .NET 图形库 System.Drawing.Common。由于 ChartDirector for .NET 7.0 依赖于该库&#xff0c;因此它不再支持 .…

运放的分类、运放的参数

一、运放的分类 运放按功能分为通用运放与专用运放&#xff08;高速运放、精密运放、低IB运放等&#xff09;。 1.1通用运放 除廉价外&#xff0c;没有任何最优指标的运放。 例&#xff1a;uA741&#xff0c;LM324&#xff0c;TL06X&#xff0c;TL07X、TL08X等 国外知名运放…

【Django】Task4 序列化及其高级使用、ModelViewSet

【Django】Task4 序列化及其高级使用、ModelViewSet Task4主要了解序列化及掌握其高级使用&#xff0c;了解ModelViewSet的作用&#xff0c;ModelViewSet 是 Django REST framework&#xff08;DRF&#xff09;中的一个视图集类&#xff0c;用于快速创建处理模型数据的 API 视…

如何从用户视角搭建可观测体系?阿里云ECS业务团队的设计思路

一分钟精华速览 互联网平台以业务为中心&#xff0c;以用户为中心&#xff0c;平台的功能服务、质量和用户体验等是关键的目标&#xff0c;仅仅关注后台系统的可用性是不够的&#xff0c;以传统运维的视角来解决故障、做监控会比较被动。 本文以阿里云 ECS 业务为例&#xff…

某多多商品平台数据采集

某多多商品平台数据采集 声明逆向目标寻找加密位置代码分析补环境补充内容声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者 无关,若有侵权,请私信我立即删除! 逆向目标 Anti-Content参数 寻找加密位置 先在控制台全局搜…

CCF HPC China2023 | 盛大开幕,邀您关注澎峰科技

2023年8月24日&#xff0c;以“算力互联智领未来”为主题的第十九届全国高性能计算学术年会&#xff08;CCF HPC China 2023&#xff09;在青岛红岛国际会议展览中心拉开帷幕。特邀嘉宾涵盖行业大咖&#xff0c;主持阵容同样是“重量级”——来自国家并行计算机工程技术研究中心…

设计模式之工厂模式

文章目录 一、介绍二、基本组件三、案例应用1. 代码演示2. 优缺点 四、静态工厂1. 应用 五、总结 一、介绍 工厂模式(Factory Pattern)是最常使用的设计模式之一&#xff0c;属于创建型设计模式。在该设计模式中&#xff0c;我们不再使用new来实例化对象&#xff0c;而是通过工…