本文大部分内容来自 Gradle 官方文档,英文 OK 的同学可以直接看官方文档。 Gradle 系列文章的示例代码都放在了 zjxstar 的 GitHub 上了。
前言
本文假设大家都已经掌握了 Gradle 构建项目的目录结构。其中,脚本文件 settings.gradle 和 build.gradle 至关重要。Gradle 中的一系列构建操作都基于这两个文件。
本系列文章想和大家一起学习 Gradle 的基础知识,如:Project、Task 和 Transform 等。本文的主要内容是 Project ,其中会涉及到一些简单的 Task,这里不会详细介绍 Task,大家只需知道有 Task 这个概念即可。文中的示例代码都基于 Android Studio 创建的标准 Android 项目。
Project 的基本概念
在 Gradle 中,最重要的两个概念是 Project(项目) 和 Task(任务)。每一次构建都至少包含一个 Project,每一个 Project 又包含了一个或多个 Task。每个 build.gradle 文件都代表一个 Project,其实 Project 可以理解为在业务范围内,被抽象出来的一个个独立的模块。而 Task 一般被定义在 build.gradle 中,它表示一个操作,比如:复制文件、打个 jar 包、上传文件等。
以 Android 项目举例说明:
注:Demo 中使用的 Gradle 版本为 3.3.1 。
使用 Android Studio 创建项目后,会自动生成两个模块,一个是以项目名命名的根模块,另一个是 app 模块。两个模块中都有 build.gradle 文件,按照前文的说法,就是两个 Project。其中,项目 GradleSample 被称为 rootProject ,项目 app 被称为 subProject 。在根 build.gradle 文件中默认有一个名为 clean 的 Task,它的职责是删除根模块下 build 目录。
在初始化构建过程中,Gradle 会基于 build 脚本文件来组装 Project 和 Task。
Gradle 的构建生命周期
执行一个 Gradle 构建的最简单形式是执行一个 Task,而一些 Task 可能会依赖于其他 Task。Gradle 为了管理这些 Task 会在任何一个 Task 执行前构建一个 DAG 图(Directed Acyclic Graph,有向无环图)。这意味着所有的 Task 都会被一个接一个地执行,而且只执行一次。那些没有依赖的 Task 通常会被优先执行。
Gradle 的构建生命周期分为三个阶段:
- 初始化:Gradle 通过 settings.gradle 文件决定哪些项目参与构建,会创建 Project 实例。如果一个项目有多个模块,并且每个模块都有其对应的 build.gradle 文件,那么就会创建多个 Project 实例。
- 配置:这个阶段,构建脚本 build.gradle 会被执行,并为每个 Project 实例创建和配置 Task。DAG 依赖关系图就在此阶段生成。
- 执行:该阶段,Gradle 将决定哪个任务会被执行。哪些任务被执行取决于开始这次构建的参数配置以及该 Gradle 文件的所在目录。
Project 的生命周期
在 Gradle 构建的初始化阶段,Gradle 会给每个项目创建一个 Project 对象。那么 Project 又具有怎样的生命周期呢?
- 为构建创建一个 Settings 实例。
- 根据 Settings 对象评估 settings.gradle 脚本(如果存在)以进行配置。
- 使用配置后的 Settings 对象创建 Project 实例的层次结构。
- 最后,通过对项目执行 build.gradle文件(如果存在)来评估每个 Project。Project 是以广度优先顺序进行评估的,这样就可以使父 Project 在它的子 Project 之前完成评估。
Project 的属性
Gradle 针对 Project 实例执行项目的构建文件以配置项目。build 脚本中的任何属性和方法都会委托给关联的 Project 对象。这意味着,可以直接在脚本中使用 Project 接口上的任何属性和方法。
例如:
defaultTasks('some-task') // Delegates to Project.defaultTasks()
reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin
你也可以通过 Project 实例使用 Project 属性,比如:
project.name // 获取project的名字,而不是单独使用name属性
你可以在构建脚本中按照名称访问这些属性,也可以通过 Project 的
project.property(java.lang.String) // 例如:project.property('version')
方法来进行访问。
一个项目在搜索属性时会考虑 5 个属性范围:
- Project 对象自身。这个范围里的属性包含 Project 实现类中定义有 getters 和 setters 方法的所有属性。比如:
project.getRootProject()
方法就对应了rootProject
属性。至于这些属性的可读写性取决于它们是否定义 getters 或者 setters 方法了。 - Project 的额外属性 ( extra ) 。每个 Project 都会维护一个额外属性的映射,它可以包含任意的
名称 -> 值
对。定义后,此作用域的属性是可读写的。比如:project.ext.prop1 = 'foo' 。 - 通过插件被添加到 Project 中的扩展属性 ( extensions ) 。每个扩展名都可以作为只读属性使用,其名称与扩展名相同。比如:project.android.compileSdkVersion 。
- 通过插件添加到 Project 中的约定属性 ( convention ) 。插件可以通过 Project 的 Convention 对象向 Project 中添加属性和方法。此范围的属性的可读可写性取决于约束对象。
- Project 中 Tasks 。可以使用 Task 的名称作为属性名称来访问任务。此范围的属性是只读的。
读写属性时,Project 都是按照上述范围的顺序进行查找的,在某个范围找到属性后就会返回该属性。如果没有找到,会抛出异常。
常用的 Project 属性有:
属性名 | 作用 |
---|---|
allprojects | 当前项目及其所有子项目的集合 |
buildDir | 当前项目的编译目录(自动生成) 默认值 porjectDir/build |
defaultTasks | 当前项目的默认任务的名字集 当前构建没有提供任务名时会执行这些默认任务 |
group | 当前项目的组名 |
logger | 当前项目的日志器,可以用来在 build 文件中写日志 |
name | 当前项目的名字 |
parent | 当前项目的父项目 |
path | 当前项目的路径(绝对路径) |
project | 当前项目的实例 |
rootProject | 当前项目层次结构中的根项目 |
subprojects | 当前项目的子项目集 |
tasks | 当前项目的任务集 |
version | 当前项目的版本号,默认值:unspecified |
示例:使用 gradlew build 命令即可运行(基于第一小节图中的 Android 项目)。
// 访问Project的属性
// 在app模块的build.gradle中
println project.group // 打印组名:GradleSample
println version // 打印版本号,默认 unspecified
println project.name // 打印项目名:app
println rootProject.name // 打印根项目的名字:GradleSample
logger.quiet('Test project logger property') // 使用logger
for(String taskName : rootProject.defaultTasks) { // 获取默认任务
println "defaultTask: $taskName" // 打印:defaultTask: clean
}
println rootProject.ext.hello // 打印额外参数
// 在根build.gradle脚本中
defaultTasks 'clean' // 设置默认Task
ext { // 设置额外参数
hello = 'Welcome to Gradle'
}
Project 的方法
一个项目在搜索方法时,也会考虑 5 个范围:
- Project 对象自身。
- build.gradle 脚本文件。
- 通过插件添加到 Project 中的扩展 ( extensions ) 。每个扩展都可以当做参数是闭包或 Action 的方法。
- 插件添加到项目中的约定方法 ( convention ) 。插件可以通过项目的 Convention 对象向项目中添加属性和方法。
- 项目中的 Tasks 。每个 Task 都会添加一个方法,方法名是任务名,参数是单个闭包或者 Action 。该方法使用提供的闭包为相关任务调用
Task.configure( groovy.lang.Closure )
方法。
常用的 Project 方法有:
方法名(不列出参数,部分方法有重载) | 作用 |
---|---|
afterEvaluate | 可以添加一个闭包,它会在项目完成评估后立即执行。 当执行属于该项目的构建文件时,会通知此类监听器。 |
allprojects | 配置当前项目以及它的每个子项目 |
apply | 应用插件或脚本 |
beforeEvaluate | 添加一个闭包,她会在项目开始评估前立即执行 |
configure | 通过闭包配置对象集合 |
copy | 拷贝特定文件 |
file | 解析文件 |
findProperty | 找特定属性,返回它的值,如果没有,返回null |
hasProperty | 判断当前项目有没有指定属性 |
project | 获取指定项目的 project 对象 |
setProperty | 给属性设置值 |
subprojects | 配置当前项目的所有子项目 |
task | 定义一个任务 |
示例:使用 gradlew -q helloTask 命令运行即可。
// 根build.gradle
allprojects {
// 给所有项目都添加一个任务:helloTask
task helloTask {
doLast { task ->
println "I'm ${task.project.name}"
}
}
}
subprojects {
helloTask { // 给所有子项目中的helloTask任务增加一个doFirst回调
doFirst {
println "I'm the task in sub project, doFirst"
}
}
afterEvaluate { project ->
println "it's after evaluate..."
helloTask.configure {
doLast {
println "configure task after evaluate"
}
}
}
}
// 给所有的子项目(除了app项目)的helloTask配置doLast
configure(subprojects.findAll{ it.name != 'app' }) {
helloTask {
doLast {
println "i am not app module, i configure doLast"
}
}
}
输出结果:
it's after evaluate...
I'm GradleSample
I'm the task in sub project, doFirst
I'm app
configure task after evaluate
总结
本文的内容不多,详细介绍了 Gradle 中 Project 的生命周期、属性和方法。同时,概述了 Gradle 构建项目的三个时期,并提到了 Task 。其实,Gradle 主要还是围绕 Task 来运转的,后续文章我们将学习 Task 的一系列知识。
参考资料
- Gradle 官方文档
- Android 官网
- 《Android Gradle权威指南》
- 《Gradle for Android 中文版》