Gradle 自动化项目构建-Gradle 核心之 Project

news2024/11/24 15:57:55

一、前言


从明面上看,Gradle 是一款强大的构建工具,但 Gradle 不仅仅是一款强大的构建工具,它更像是一个编程框架。Gradle 的组成可以细分为如下三个方面:

  • groovy 核心语法:包括 groovy 基本语法、闭包、数据结构、面向对象等等。
  • Android DSL(build scrpit block):Android 插件在 Gradle 所特有的东西,我们可以在不同的 build scrpit block 中去做不同的事情。
  • Gradle API:包含 Project、Task、Setting 等等。

可以看到,Gradle 的语法是以 groovy 为基础的,而且它还有自己独有的 API,所以我们可以把 Gradle 认作是一款编程框架,利用 Gradle 我们可以在编程中去实现项目构建过程中的所有需求。想要随心所欲地使用 Gradle,我们必须提前掌握好 groovy。需要注意的是,Groovy 是一门语言,而 DSL 一种特定领域的配置文件,Gradle 是基于 Groovy 的一种框架工具,而 gradlew 则是 gradle 的一个兼容包装工具。

Gradle 有以下优势:

  1. 灵活性:相对于 Maven、Ant 等构建工具,Gradle 提供了一系列的 API 让我们有能力去修改或定制项目的构建过程。例如我们可以利用 Gradle 去动态修改生成的 APK 包名。
  2. 粒度性:使用 Maven、Ant 等构建工具时,我们的源代码和构建脚本是独立的,而且我们也不知道其内部的处理是怎样的。但是 Gradle 则不同,它从源代码的编译、资源的编译、到生成 APK 的过程中都是一个接一个来执行的。此外,Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的,在我们掌握了这一整套打包流程后,我们就可以通过修改它的 Task 去动态改变其执行流程。例如 Tinker 框架的实现过程中,它通过动态地修改 Gradle 的打包过程生成 APK 的同时,也生成了各种补丁文件。
  3. 扩展性:Gradle 支持插件机制,所以我们可以复用这些插件,就如同复用库一样简单方便。
  4. 兼容性:Gradle 不仅自身功能强大,而且它还能兼容所有的 Maven、Ant 功能,也就是说,Gradle 吸取了所有构建工具的长处。

可以看到,Gradle 相比于其它构建工具,其好处不言而喻,而其最核心的原因就是因为 Gradle 是一套编程框架。

二、Gradle 的生命周期


所谓 Gradle 的生命周期,即 gradle 的执行流程,也就是 Gradle 先执行什么后执行什么。我们看下它的流程图:

可以看到,gradle 的执行流程分了 初始化、配置、执行 三个阶段,上图中的 project、task 我们接下来几篇会详细介绍。下面我们看看这几个阶段。

 2.1、初始化阶段

初始化阶段会读取根工程中的 setting.gradle 中的 include 信息,确定有多少工程加入构建,然后会为每一个项目(build.gradle 脚本文件)创建一个个与之对应的 Project 实例,最终形成一个项目的层次结构。

与初始化阶段相关的脚本文件是 settings.gradle,而一个 settings.gradle 脚本对应一个 Settings 对象,我们最常用来声明项目的层次结构的 include 就是 Settings 对象下的一个方法,在 Gradle 初始化的时候会构造一个 Settings 实例对象,以执行各个 Project 的初始化配置。

此外,在 settings.gradle 文件中,我们可以指定其它 project 的位置,这样就可以将其它外部工程中的 moudle 导入到当前的工程之中了。示例代码如下所示:

if (useSpeechMoudle) {
    // 导入其它 App 的 speech 语音模块
    include "speech"
    project(":speech").projectDir = new File("../OtherApp/speech")
}

2.2、配置阶段

配置阶段的任务是执行各项目下的 build.gradle 脚本,完成 Project 的配置,与此同时,会构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task。而在配置阶段执行的代码通常来说都会包括以下三个部分的内容,如下所示:

  • 1)、build.gralde 中的各种语句。
  • 2)、闭包。
  • 3)、Task 中的配置段语句。

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

2.3、执行阶段

在配置阶段结束后,Gradle 会根据各个任务 Task 的依赖关系来创建一个有向无环图,我们可以通过 Gradle 对象的 getTaskGraph 方法来得到该有向无环图。并且当有向无环图构建完成之后,所有 Task 执行之前,我们可以通过 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 来接收相应的通知,其代码如下所示:

gradle.getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
    @Override
    void graphPopulated(TaskExecutionGraph graph) {
    }
})

然后,Gradle 构建系统会通过调用 gradle <任务名> 来执行相应的各个任务。

可以看到,整个 Gradle 生命周期的流程包含如下 四个部分:

  • 首先,解析 settings.gradle 来获取模块信息,这是初始化阶段。
  • 然后,配置每个模块,配置的时候并不会执行 task。
  • 接着,配置完了以后,有一个重要的回调 project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行 task 了。
  • 最后,执行指定的 task 及其依赖的 task。

在 Gradle 构建命令中,最为复杂的命令可以说是 gradle build 这个命令了,因为项目的构建过程中需要依赖很多其它的 task。这里,我们以 Java 项目的构建过程看看它所依赖的 tasks 及其组成的有向无环图,如下所示:

2.4、生命周期监听

上面我们学习了 Gradle 的执行生命流程,下面我们在它的监听回调中做一些输出。

首先在项目根目录的 build.gradle 中添加如下监听代码:

在根目录的 setting.gradle 中添加如下代码:

接下来我们执行一个简单的 gradle 命令:gradle clean


Gradle 核心之 Project

一、前言


Project 是 Gradle 构建整个应用程序的入口,所以它非常重要。我们看下面这张图:

上图是我创建的一个 Android 工程,并添加了一个 test module。我们在命令行中输入 gradle projects 命令看看有哪些 project:

可以看到输出了三个 project,其中 GradleTextProject 是根 project,而 app、test 是子 project。根 project 的作用是管理所有的 子 project。准确来说有 build.gradle 文件的目录即是 project。一个子 project 对应一个输出,具体输出什么由 build.gradle 配置去决定。

二、project 核心 api


 在 Project 中有很多的 API,但是根据它们的属性和用途我们可以将其分解为六大部分,如下图所示:

对于 Project 中各个部分的作用,我们可以先来大致了解下,以便为 Project 的 API 体系建立一个整体的感知能力,如下所示:

  1. Project 相关 API:让当前 Project 拥有了操作它的父 Project 以及管理它的子 Project 的能力。
  2. Task 相关 API:为当前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常重要,我们将在下一篇进行讲解。
  3. 属性相关的 Api:Gradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我们拥有了为 Project 添加额外属性的能力。
  4. File 相关 Api:Project File 相关的 API 主要用来操作我们当前 Project 下的一些文件处理。
  5. Gradle 生命周期 API:即我们在上一篇讲解过的 Gradle 核心之生命周期。
  6. 其它 API:添加依赖、添加配置、引入外部文件等等零散 API 的聚合。

2.1、Project 相关 API

通过 gradle 管理的工程都会有一个根工程 project,根工程用来管理子工程。下面我们来看看 Project 相关的 API。

2.1.1 getAllprojects()

getAllprojects 表示获取所有 project 的实例,示例代码如下所示:

我们调用了 getAllprojects 方法返回一个包含根 project 与其子 project 的 Set 集合,并链式调用了 eachWithIndex 遍历 Set 集合。接着,我们会判断当前的下标 index 是否是0,如果是则表明当前遍历的是 rootProject,则输出 rootProject 的名字,否则输出 child project 的名字。

然后我们在命令行执行 gradle clean,其运行结果可以看到,会先配置我们的 rootProject,并输出了对应的工程信息。接着便会执行子工程 app 的配置。rootProject 与其旗下的各个子工程组成了一个树形结构。

2.1.2 getSubprojects()

getSubprojects 表示获取当前工程下所有子工程的实例,示例代码如下所示:

同 getAllprojects 的用法一样,getSubprojects 方法返回了一个包含子 project 的 Set 集合。

2.1.3 getParent()

getParent() 是获取父 project 的方法。

 可以看到,执行 gradle clean 后输出了 test module 这个 project 的父 project,也就是 GradleTextProject。需要注意的是,如果在根目录的 build.gradle 中调用 getParent() ,由于根 project 没有父节点了,所有返回的是 null。

2.1.4 getRootProject()

getRootProject() 获取的是根节点 project。

形成的 project 树中肯定是有根节点的,所以在任意子节点 project 中调用 getRootProject 都返回的是根节点 project,所以肯定不会返回空。

2.1.5 project()

project 表示的是指定工程的实例,然后在闭包中对其进行操作。可以看到,在 project 方法中两个参数,一个是指定工程的路径,另一个是用来配置该工程的闭包。下面我们看看如何灵活地使用 project,示例代码如下所示: 

2.1.6 allprojects()

allprojects 表示用于配置当前 project 及其每一个子 project,在 allprojects 中我们一般用来配置一些通用的配置,比如最常见的全局仓库配置。如下所示:

当我们用熟练后,可以省略闭包的参数:

2.1.7 subprojects()

subprojects 用于统一配置当前 project 下的所有子 project, 给所有的子工程引 将 aar 文件上传置 Maven 服务器的配置脚本,示例代码如下所示:

 
  1. subprojects {

  2. if (project.plugins.hasPlugin("com.android.library")) {

  3. apply from: '../publishToMaven.gradle'

  4. }

  5. }

在上述示例代码中,我们会先判断当前 project 下的子 project 是不是库,如果是库才有必要引入 publishToMaven 脚本。需要注意与 allprojects() 的区别是,subprojects() 不包含当前 project。

2.2 属性相关API

Project 提供了默认的 7 个属性,我们先来看看这些属性:

第一个属性 DEFAULT_BUILD_FILE = "build.gradle" 表明默认读取的配置文件是 build.gradle,这也证明了上面说有 build.gradle 的文件夹就是一个 project 的结论。

第二个属性 PATH_SEPARATOR 表示的是分隔符。

第三个属性 DEFAULT_BUILD_DIR_NAME 表示默认的输出文件夹,每个工程都会有一个 build 文件夹存放 project 输出。

后面几个属性不常用到,就不详细说明了。这么少的属性显然无法满足我们各种各样的构建需求,gradle 为我们提供了一种去扩展 project 属性的方式。主要有两种扩展方式,下面我们来看看。

2.2.1 ext 扩展属性

我们可以使用 ext 扩展属性修改默认情况下 app 或其他 module 的 build.gradle 配置,如下所示:

project 中 ext 加闭包即定义扩展属性,我们可以在每个 project 的 build.gradle 文件中定义 ext 扩展属性,但当我们有多个 project 的时候这种写法也很麻烦。这时候我们可以把 ext 放到上一节我们学习的 allprojects()、subprojects() 中,然后在子 project 中用 this 关键字引用即可。

 另外,我们也可以去掉 subprojects(),在根目录中直接设置 ext,然后在子 project 中通过 this.rootProject 来引用。

另外也可以直接通过 this 来直接使用,因为子 project 是继承父 project 的,所以父 project 中定义的属性,子 project 可以直接使用。随着版本的迭代,演变出了最优方案:将扩展属性单独定义到一个新的 gradle 文件中,这样就可以减少根工程的配置代码,同时也更模块化了我们的变量。这里我们通常会将其命名为 config.gradle,如下:

可以看到,在 config.gradle 中分了各个区块,在每个区块中定义了一个 map,在 map 中定义各种 key、value。然后在我们的根目录下的 build.gradle 中引入这个 gradle:

引用完成后就可以在我们的子 project 的 build.gradle 中按区块引用即可:

2.2.2 gradle.properties 里定义扩展属性

除了使用 ext 扩展属性定义额外的属性之外,我们也可以在 gradle.properties 下定义扩展属性,其示例代码如下所示:

 
  1. // 在 gradle.properties 中

  2. mCompileVersion = 27

  3. // 在 app moudle 下的 build.gradle 中

  4. compileSdkVersion mCompileVersion.toInteger()

2.3 文件相关API

2.3.1 路径获取相关API

关于路径获取的 API 常用的有三种,其示例代码如下所示:

2.3.2 文件操作API

groovy 中的文件操作 API 可以用在 project 中,而本节讲解的 project 中文件操作 API 可以在 project 下去更方便的对文件进行操作。下面我们来看看有哪些操作。

文件定位:常用的文件定位 API 有下面两个方法:

 
  1. //定位单个文件

  2. File file(Object path);

  3. //定位多个文件

  4. ConfigurableFileCollection files(Object... paths);

使用如下所示:

文件拷贝:常用的文件拷贝 API 为 copy,不仅可以对文件进行拷贝,也可以对文件夹进行拷贝。其示例代码如下所示:

在实际项目中的使用一般如下:

 
  1. tasks.whenTaskAdded { task ->

  2. if (task.name.equalsIgnoreCase("assembleRelease")) {

  3. // 如果是assembleRelease任务,在最后执行导出apk以及mapping目录到指定目录

  4. task.doLast {

  5. outputReleaseFile()

  6. }

  7. }

  8. }

  9. void outputReleaseFile() {

  10. android.applicationVariants.all { variant ->

  11. // 如果是正式版打包

  12. if (variant.name.equalsIgnoreCase("release")) {

  13. File outputPath = new File("$rootDir" + File.separator + "release_app" + File.separator

  14. + android.defaultConfig.versionName)

  15. println(String.format('拷贝生成文件到指定目录[%s]', outputPath.getAbsolutePath()))

  16. // 拷贝apk文件

  17. copy {

  18. from variant.outputs[0].outputFile

  19. into outputPath

  20. // 重命名导出名称

  21. rename {

  22. 'account_system' + variant.name + '_' + android.defaultConfig.versionName + ".apk"

  23. }

  24. }

  25. // 拷贝mapping目录

  26. copy {

  27. from variant.mappingFile.getParentFile()

  28. into new File(outputPath, 'mapping')

  29. }

  30. }

  31. }

  32. }

变体其实就是我们的 apk,变体我们后面再介绍。

2.3.3 文件树的遍历

我们可以 使用 fileTree 将当前目录转换为文件数的形式,然后便可以获取到每一个树元素(节点)进行相应的操作,其示例代码如下所示:

2.4 其他API

其他API包含两部分:

2.4.1 依赖相关API

根项目下的 buildscript 用于配置项目核心的依赖,使用如下:

当我们熟练使用闭包后可以简写如下:

需要注意的是不同于根项目 buildscript 中的 dependencies 是用来配置我们 Gradle 工程的插件依赖的,而 app moudle 下的 dependencies 是用来为应用程序添加第三方依赖的。关于 app moudle 下的依赖使用这里我们需要注意下 exclude 与 transitive 的使用 即可,示例代码如下所示:

 
  1. implementation(rootProject.ext.dependencies.glide) {        

  2. // 排除依赖:一般用于解决资源、代码冲突相关的问题        

  3. exclude module: 'support-v4'         

  4. // 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B 中所使用的 C 中的依赖

  5. //默认都是不打开,即 false 禁止传递依赖     

  6. transitive false 

  7. }

 传递依赖文字描述有点抽象,我们来看下面这张图就可以明白了:

2.4.2 外部命令执行

如果 Gradle 的 API 能满足我们的需求时尽量使用 Gradle API,不行的化我们就可以考虑使用 Gradle 提供的 exec 来执行外部命令,下面我们就使用 exec 命令来 将当前工程下新生产的 APK 文件拷贝到电脑下的 Downloads 目录中,示例代码如下所示:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#调用OpenCvSharp实现图像的直方图均衡化

本文学习基于OpenCvSharp的直方图均衡化处理方式&#xff0c;并使用SkiaSharp绘制相关图形。直方图均衡化是一种图像处理方法&#xff0c;针对偏亮或偏暗的图像&#xff0c;通过调整图像的像素值来增强图像对比度&#xff0c;详细原理及介绍见参考文献1-4。   直方图均衡化第…

计算机组成原理笔记-第1章 计算机系统概论

第一章 计算机系统概论 笔记PDF版本已上传至Github个人仓库&#xff1a;CourseNotes&#xff0c;欢迎fork和star&#xff0c;拥抱开源&#xff0c;一起完善。 该笔记是最初是没打算发网上的&#xff0c;所以很多地方都为了自我阅读方便&#xff0c;我理解了的地方就少有解释&a…

如何恢复 Mac 数据?适用于 Mac 的免费磁盘恢复软件

对于大多数 Mac 电脑用户来说&#xff0c;丢失数据是他们最不想遇到的噩梦之一。然而&#xff0c;无论我们多么小心地使用 Mac&#xff0c;多么有条理地存储重要文件&#xff0c;我们仍然有可能丢失 Mac 上的数据。某些硬件故障更有可能导致您意外丢失文件。除此之外&#xff0…

在WordPress上添加亚马逊联盟链接的三种方法

在互联网快速发展的今天&#xff0c;很多人都希望通过网络来增加收入&#xff0c;而加入亚马逊联盟计划&#xff08;Amazon Associates&#xff09;无疑是一个不错的选择。如果你有一个WordPress网站&#xff0c;那么在文章中添加亚马逊联盟链接是个很好的变现方式。今天&#…

IDEA services模块无法启动springboot服务(添加了springboot但是为空白)

https://blog.csdn.net/m0_54042402/article/details/117918995 https://blog.csdn.net/qq_46550964/article/details/122235235 Alt8 显示services模块 发现有springboot启动模块&#xff0c;点一下springboot之后&#xff0c;这个模块就消失了 会自动在.idea文件夹下的work…

Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流

技术背景 VR头显需要更高的分辨率以提供更清晰的视觉体验、满足沉浸感的要求、适应透镜放大效应以及适应更广泛的可视角度&#xff0c;超高分辨率的优势如下&#xff1a; 提供更清晰的视觉体验&#xff1a;VR头显的分辨率直接决定了用户所看到的图像的清晰度。更高的分辨率意…

AI全栈之coze的logo生成

前言 前几日体验了国产的AI-Agents产品coze 它是一种能够自主执行任务、与环境进行交互并根据所获取的信息做出决策和采取行动的软件程序 并且可以自己去创建属于自己的AIBot&#xff0c;还是很有意思的&#xff0c;大家可以去体验体验 在体验过程中&#xff0c;我发现在创…

Zygote进程的理解

Zygote进程是安卓系统的一个重要进程&#xff0c;由init进程创建而来&#xff1b;另外系统里的重要进程&#xff08;system_server等&#xff09;都是由zygote进程fork的&#xff0c;所有的app进程也是由zygote进程fork的。 一、C 里的fork函数 fork是Linux里面创建子进程的函…

【Apache Doris】如何实现高并发点查?(原理+实践全析)

【Apache Doris】如何实现高并发点查&#xff1f;&#xff08;原理实践全析&#xff09; 一、背景说明二、原理介绍三、环境信息四、Jmeter初始化五、参数预调六、用例准备七、高并发实测八、影响因素九、总结 本文主要分享 Apache Doris 是如何实现高并发点查的&#xff0c;以…

Virtualbox主机和虚拟机之间文件夹共享及双向拷贝

在VirtualBox这样的虚拟化环境中&#xff0c;实现主机与虚拟机之间的文件夹共享与双向文件传输是一个常见的需求。下面&#xff0c;我们将详细讲解如何在VirtualBox中实现这一功能。 一、安装与准备 首先&#xff0c;确保你已经安装了VirtualBox&#xff0c;并在其上成功创建…

图像处理与视觉感知复习--三维重建基础

文章目录 完整的摄像机模型&#xff08;摄像机内外参数&#xff09;理解三个参考系齐次坐标系中的投影变换 摄像机标定单视图几何无穷远点、无穷远线、无穷远平面影消点、影消线 三维重建基础与极几何极几何、本质矩阵与基础矩阵 双目立体视觉视差或深度的推导过程 完整的摄像机…

微信小程序毕业设计-餐厅点餐系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

单机小游戏好上架的应用市场有哪些?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

长尾式差分放大电路调零

长尾式放大电路用了两个参数相同的三极管&#xff0c;但实际上并没有完全相同的三极管&#xff0c;所以为了提高差分放大电路的对称性(一边电流增加多少&#xff0c;另一边电流减小多少&#xff0c;即能在电阻Re上产生的压降不变(后面做虚地处理))&#xff0c;在下图中加入可调…

【CT】LeetCode手撕—143. 重排链表

目录 题目1- 思路2- 实现⭐143. 重排链表——题解思路 3- ACM 实现 题目 原题连接&#xff1a;143. 重排链表 1- 思路 模式识别&#xff1a;重排链表 ——> 逆向 ——> ① 找到中间节点 ——> ②逆置 mid.next 链表——> ③遍历 2- 实现 ⭐143. 重排链表——题解…

Vue72-路由传参1

一、需求 点击哪个消息&#xff0c;就展示哪个消息的详情 这是一个三级路由&#xff01; 给路由组件&#xff1a;detail.vue传递消息数据。 二、代码步骤 2-1、编写路由组件 从$route.query属性里面获取传参 2-2、编写路由规则 2-3、编写路由标签&#xff0c;传参 1、to的字…

做好海外ASO优化的7大核心要素你了解几个?

海外App进行ASO优化时&#xff0c;需要综合考虑多个方面以确保应用在应用商店中获得更高的曝光率和下载量。以下是一些关键的ASO优化步骤&#xff0c;结合参考文章中的相关信息进行详细阐述&#xff1a; 1.关键词优化 调研目标市场的用户行为和检索习惯&#xff0c;挖掘与应用…

让你的 Python 代码更快的小技巧

我们经常听到 “Python 太慢了”&#xff0c;“Python 性能不行”这样的观点。但是&#xff0c;只要掌握一些编程技巧&#xff0c;就能大幅提升 Python 的运行速度。 今天就让我们一起来看下让 Python 性能更高的 9 个小技巧 python学习资料分享&#xff08;无偿&#xff09;…

PXE自动平台 搭建 银河麒麟 UEFI x86_64 ARM64

1. PXE自动化 原理 要实现PXE自动安装需要以下组件&#xff1a; DHCP服务&#xff1a;服务器通过网络启动时自动分配IP地址。TFTP服务&#xff1a;提供服务器启动下载启动引导EFI。HTTP服务&#xff1a;操作系统镜像下载。 各组件工作原理如下[1]&#xff1a; 开PXE后&…

Android-app自动更新总结(已适配9-0)(1)

} //检查版本号&#xff0c;第一次请求(post)&#xff0c;&#xff0c;&#xff0c;UpdateAppBean根据服务器返回生成 private void requestAppUpdate(int version, final DataRequestListener listener) { OkGo.post(Const.HOST_URL Const.UPDATEAPP).params(“version”, v…