前言
Gradle 作为官网主推的构建零碎,目前曾经深度利用于 Android 的多个技术体系中,例如组件化开发、产物构建、单元测试等。可见,要成为 Android 高级工程师 Gradle 是必须把握的知识点。在这篇文章里,我将带你由浅入深建设 Gradle 的基本概念,波及 Gradle 生命周期、Project、Task 等知识点,这些内容也是 Gradle 在面试八股文中容易遇见的问题。
从这篇文章开始,我将带你全面把握 Gradle 构建零碎,系列文章:
- 1、Gradle 根底
- 2、Gradle 插件
- 3、Gradle 依赖治理
- 4、APG Transform
请点赞,你的点赞对我意义重大,满足下我的虚荣心。
🔥 Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,欢送跟着我一起成长。(联系方式在 GitHub)
1. 意识 Gradle
Gradle 并不仅仅是一个语言,而是一套构建工具。在晚期,软件构建只有编译和打包等简略需要,但软件开发的倒退,当初的构建变得更加简单。而构建工具就是在这一背景下衍生进去的工具链,它可能帮忙开发者可反复、自动化地生成指标产物。例如 Ant、Maven 和 ivy 也是历史演化过程中诞生的构建工具。
1.1 Gradle 的优缺点
相比于晚期呈现的构建工具,Gradle 可能怀才不遇次要是以下长处:
- 表白性的 DSL: Gradle 构建脚本采纳基于 Groovy 的 DSL 畛域特定语言,而不是采纳传统的 XML 文件,相比 Maven 等构建零碎更加简洁;
- 基于 Java 虚拟机: Groovy 语言基于 Java 虚拟机,这使得 Gradle 反对用 Java / Kotlin 代码编写构建脚本,咱们齐全能够只学习一小部分 Groovy 语法就能上手 Gradle 脚本,升高了 Gradle 的学习强度;
- 约定优先于配置: Gradle 具备约定优先于配置的准则,即为属性提供默认值,相比 Ant 等构建零碎更容易上手。咱们在开发 Gradle 插件时也须要遵循这一准则。
Gradle 也有显著的毛病,例如:
- 较弱的向后兼容性: Gradle 是一个疾速倒退的工具,新版本常常会突破向后兼容性,有教训的同学就晓得,一个工程在低版本 Gradle 能够编译,但换了新版本 Gradle 可能就编译不通过了。
1.2 Gradle 工程的根本构造
在 Android Studio 中创立新我的项目时,会主动生成以下与 Gradle 相干文件。这些大家都很相熟了,简略梳理下各个文件的作用:
.
├── a-subproject
│ └── build.gradle
├── build.gradle
├── settings.gradle
├── gradle.properties
├── local.properties
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
- settings.gradle 文件: 用于确定哪些模块参加构建;
- 我的项目级 build.gradle 文件: 用于定义所有子模块公共的配置参数;
- 模块级 build.gradle 文件: 用于定义子模块的配置参数,它能够笼罩我的项目级 build.gradle 文件中定义的配置;
- gradle/warpper: 负责主动下载安装我的项目所需的 Gradle 环境的脚本;
- gradle.properties: 用作我的项目级 Gradle 配置项,会笼罩全局的配置项;
- local.properties: 用作我的项目的公有属性配置,例如 SDK 装置目录,个别不把 local.properties 退出版本控制。
1.3 Gradle 中的重要概念
- Gradle: 提供外围构建流程,但不提供具体构建逻辑;
- Gradle 插件: Gradle 提供的是一套外围的构建机制,而 Gradle 插件正是运行在这套机制上的一些具体构建逻辑,实质上和 .gradle 文件没有区别。例如,咱们相熟的 Android 构建流程就是由 Android Gradle Plugin 引入的构建逻辑;
- Gradle Daemon: 用于晋升构建速度的后盾过程;
- Gradle Wrapper: 对 Gradle 的封装,减少了主动下载安装 Gradle 环境的能力;
- 环境变量 GRADLE: 用于定义 Gradle 的装置目录;
- 环境变量 GRADLE_USER_HOME: 用于定义 Gradle 运行过程的文件存储目录,例如 Gradle Wrapper 主动装置的 Gradle 环境、构建缓存等;
1.4 Gradle Daemon
Gradle Daemon 是 Gradle 3.0 引入的构建优化策略,通过躲避反复创立 JVM 和内存缓存的伎俩晋升了构建速度。 Daemon 过程才是执行构建的过程,当构建完结后,Daemon 过程并不会立刻销毁,而是保留在内存中期待承接下一次构建。依据官网文档阐明,Gradle Daemon 可能升高 15-75% 的构建工夫。
Daemon 的优化成果次要体现在 3 方面:
- 1、缩短 JVM 虚拟机启动工夫: 不须要反复创立;
- 2、JIT 编译: Daemon 过程会执行 JIT 编译,有助于晋升后续构建的字节码执行效率;
- 3、构建缓存: 构建过程中加载的类、资源或者 Task 的输出和输入会保留在内存中,能够被后续构建复用。
相干的 Gradle 命令:
- gradle —status: 查看存活的 Daemon 过程信息;
- gradle —stop: 进行所有 Daemon 过程。
提醒: 并不是所有的构建都会复用同一个 Daemon 过程,如果已存活的 Daemon 过程无奈满足新构建的需要,则 Gradle 会新建一个新的 Daemon 过程。影响因素:
- Gradle 版本:不同 Gradle 版本的构建不会关联到同一个 Daemon 过程;
- Gradle 虚拟机参数:不满足的虚拟机参数不会关联到同一个 Daemon 过程。
1.5 Gradle Wrapper
Gradle Wrapper 实质是对 Gradle 的一层包装,会在执行 Gradle 构建之前主动下载安装 Gradle 环境。 在开始执行 Gradle 构建时,如果以后设施中还未装置所需版本的 Gradle 环境,Gradle Wrapper 会先帮你下载安装下来,未来其余须要这个 Gradle 版本的工程也能够间接复用。
Android Studio 默认应用 Gradle Wrapper 执行构建,你能够在设置中批改这一行为:
命令行也有辨别:
gradle
:应用零碎环境变量定义的 Gradle 环境进行构建;gradlew
:应用 Gradle Wrapper 执行构建。
为什么 Gradle 官网从晚期就专门推出一个主动装置环境工具呢,我认为起因有 2 个:
- 确保 Gradle 版本正确性: 鉴于 Gradle 有较弱向后兼容性的特点,Gradle Wrapper 可能从我的项目工程级别固化我的项目所须要的 Gradle 版本,从而确保同一个工程移植到其余电脑后可能正确地、可反复地构建;
- 缩小了手动装置 Gradle 环境的工作量: 单单从 Gradle 4 到 Gradle 7 就有大大小小十几个版本,而且每个工程所须要的 Gradle 版本不尽相同,应用 Gradle Wrapper 可能缩小手动装置环境的工作量;
简略说下 Gradle Wrapper 相干的文件,次要有 4 个:
- gradlew & gradlew.bat: 在 Linux 或 Mac 上可用的 Shell 脚本,以及在 Window 上可用的 Batch 脚本,用于以 Gradle Wrapper 的形式执行构建。也就是说,在命令行应用
gradlew
才是基于 Gradle Wrapper 执行的,而应用gradle
命令是间接基于零碎装置的 Gradle 环境执行编译; - gradle-wrapper.jar: 负责下载安装 Gradle 环境的脚本;
-
gradle-wrapper.properties: Gradle Wrapper 的配置文件,次要作用是决定 Gradle 版本和装置目录:
- distributionBase + distributionPath:指定 Gradle 环境装置门路;
- zipStoreBase + zipStorePath:指定 Gradle 安装包的存储门路;
- distributionUrl:指定版本 Gradle 的下载地址,通过这个参数能够配置我的项目工程所须要的 Gradle 版本。
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
提醒: GRADLE_USER_HOME 的默认值是
用户目录 /.gradle
,能够通过零碎环境变量 GRADLE_USER_HOME 批改。
1.6 gradle.properties 构建环境配置
Gradle 是运行在 Java 虚拟机的,gradle.properties 文件能够配置 Gradle 构建的运行环境,并且会笼罩 Android Studio 设置中的全局配置,残缺构建环境配置见官网文档:Build Enviroment。罕用的配置项举例:
# Gradle Daemon 开关,默认 ture
org.gradle.daemon=true
# 虚拟机参数
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# 多模块工程并行编译多个模块,会耗费更多内存
org.gradle.parallel=true
除了构建环境配置,其余配置也能够用相似的键值对形式放在 gradle.properties 中,并间接在 .gradle 文件中援用。
2. Groovy 必知必会
Groovy 是从 Java 虚拟机衍生进去的语言,因为咱们都具备肯定的 Java 根底,所以咱们没有必要齐全从零开始学习 Groovy。梳理 Groovy 与 Java 之间有差别的中央,或者是更高效的学习形式:
2.1 一些小差别
- 分号: 语句容许不以分号 ; 结尾;
- public: 默认的拜访修饰符为 public;
- getter / setter: Groovy 会为每个 field 创立对应的 getter / setter 办法,在拜访 obj.field / obj.field=””时,实际上是在拜访 getField() 和 setField(””);
- 反对动态类型和动静类型: Groovy 既反对 Java 的动态类型,也反对通过
def
关键字申明动静类型(动态类型和动静类型的要害区别在于”类型查看是否偏向于在编译时执行“。例如 Java 是动态类型语言,意味着类型查看次要由编译器在编译时实现); -
字符串: Groovy 反对三种格局定义字符串 —— 单引号、双引号和三引号
- 单引号:纯正的字符串,与 Java 的双引号字符串相似;
- 双引号:反对在引号内通过 $ 关键字间接援用变量值;
- 三引号:反对换行。
2.2 函数
- 函数定义: Groovy 反对通过返回类型或 def 关键字定义函数。def 关键字定义的函数如果没有 return 关键字返回值,则默认会返回 null。例如:
// 应用 def 关键字
def methodName() {// Method Code}
String methodName() {// Method Code}
- 参数名: Groovy 反对不指定参数类型。例如:
// 省略参数类型
def methodName(param1, param2) {// Method Code}
def methodName(String param1, String param2) {// Method Code}
- 默认参数: Groovy 反对指定函数参数默认值,默认参数必须放在参数列表开端。例如:
def methodName(param1, param2 = 1) {// Method Code}
- 返回值: 能够省略 return,默认返回最初一行语句的值。例如:
def methodName() {return "返回值"}
等价于
def methodName() {"返回值"}
-
invokeMethod & methodMissing:
- invokeMethod: 分派对象上所有办法调用,包含已定义和未定义的办法,须要实现 GroovyInterceptable 接口;
- methodMissing: 分派对象上所有为定义方法的调用。
// 实现 GroovyInterceptable 接口,才会把办法调用分派到 invokeMethod。class Student implements GroovyInterceptable{
def name;
def hello() {println "Hello ${name}"
}
@Override
Object invokeMethod(String name, Object args) {System.out.println "invokeMethod : $name"}
}
def student = new Student(name: "Tom")
student.hello()
student.hello1()
输入:invokeMethod : hello
invokeMethod : hello1
-------------------------------------------------------------
class Student {
def name;
def hello() {println "Hello ${name}"
}
@Override
Object methodMissing(String name, Object args) {System.out.println "methodMissing : $name"}
}
def student = new Student(name: "Tom")
student.hello()
student.hello1()
输入:Hello Tom
methodMissing hello1
2.3 汇合
Groovy 反对通过 [] 关键字定义 List 列表或 Map 汇合:
- 列表: 例如 def list = [1, 2, 3, 4]
- 汇合: 例如 def map = [’name’:’Tom’,‘age’:18],空集合 [:]
- 范畴: 例如 def range = 1 .. 10
- 遍历:
// 列表
def list = [10, 11, 12]
list.each {value ->}
list.eachWIthIndex {value, index ->}
// 汇合
def map = [’name’:’Tom’,‘age’:18]
map.each {key, value ->}
map.eachWithIndex {entry, index ->}
map.eachWithIndex {key, value, index ->}
2.4 闭包
Groovy 闭包是一个匿名代码块,能够作为值传递给变量或函数参数,也能够接管参数和提供返回值,模式上与 Java / Kotlin 的 lambda 表达式相似。例如以下是无效的闭包:
{123}
{-> 123}
{println it}
{it -> println it}
{name -> println name}
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
- 闭包类型: Groovy 将闭包定义为
groovy.lang.Closure
的实例,使得闭包能够像其余类型的值一样复制给变量。例如:
Closure c = {123}
// 当然也能够用 def 关键字
def c = {123}
- 闭包调用: 闭包能够像办法一样被调用,能够通过 Closure#call() 实现,也能够间接通过变量实现。例如:
def c = {123}
// 通过 Closure#call() 调用
c.call()
// 间接通过变量名调用
c()
- 隐式参数: 闭包默认至多有一个形式参数,如果闭包没有显式定义参数列表(应用
→
),Groovy 总是带有隐式增加一个参数 it。如果调用者没有应用任何实参,则 it 为空。当你须要申明一个不接管任何参数的闭包,那么必须用显式的空参数列表申明。例如:
// 带隐式参数 it
def greeting = {"Hello, $it!"}
assert greeting('Patrick') == 'Hello, Patrick!'
// 不带隐式参数 it
def magicNumber = {-> 42}
// error 不容许传递参数
magicNumber(11)
- 闭包参数简化: 函数的最初一个参数是闭包类型的化,在调用时能够简化,省略圆括号:
def methodName(String param1, Closure closure) {// Method Code}
// 调用:methodName("Hello") {// Closure Code}
-
this、owner、delegate: 闭包委托是 Groovy Closure 相比 Java Lambda 最大的区别,通过批改闭包的委托能够实现灵活多样的 DSL。先意识闭包中的三个变量:
- this: 定义闭包的外部类,this 肯定指向类对象;
- owner: 定义闭包的内部对象,owner 可能是类对象,也可能是更外一层的闭包;
- delegate: 默认状况 delegate 等同于 owner,this 和 owner 的语义无奈批改,而 delegate 能够批改。
- 闭包委托策略: 在闭包中,如果一个属性没有显式申明接收者对象,则会通过闭包代理解析策略寻找定义的对象,例如:
class Person {String name}
def p = new Person(name:'Igor')
def cl = {// 相当于 delegate.name.toUpperCase()
name.toUpperCase()}
cl.delegate = p
assert cl() == 'IGOR'
闭包定义了多种解析策略,能够通过 Closure#resolveStrategy=Closure.DELEGATE_FIRST
批改:
- Closure.OWNER_FIRST(默认): 优先在 owner 对象中寻找,再去 delegate 对象中寻找;
- Closure.DELEGATE_FIRST: 优先在 delegate 对象中寻找,再去 owner 对象中寻找;
- Closure.OWNER_ONLY: 只在 owner 对象中寻找;
- Closure.DELEGATE_ONLY: 只在 delegate 对象中寻找;
- Closure.TO_SELF: 只在闭包自身寻找;
3. Gradle 构建生命周期
Gradle 将构建划分为三个阶段: 初始化 – 配置 – 执行 。了解构建生命周期(Gradle Build Lifecycle)十分重要,否则你可能连脚本中的每个代码单元的执行机会都搞不清楚。
3.1 初始化阶段
因为 Gradle 反对单模块构建或多模块构建,因而在初始化阶段(Initialization Phase),Gradle 须要晓得哪些模块将参加构建。次要蕴含 4 步:
-
1、执行 Init 脚本: Initialization Scripts 会在构建最开始执行,个别用于设置全局属性、申明周期监听、日志打印等。Gradle 反对多种配置 Init 脚本的办法,以下形式配置的所有 Init 脚本都会被执行:
- gradle 命令行指定的文件:
gradle —init-script <file>
- USER_HOME/.gradle/init.gradle 文件
- USER_HOME/.gradle/init.d/ 文件夹下的 .gradle 文件
- GRADLE_HOME/init.d/ 文件夹下的 .gradle 文件
- gradle 命令行指定的文件:
- 2、实例化 Settings 接口实例: 解析根目录下的
settings.gradle
文件,并实例化一个 Settings 接口实例; - 3、执行 settings.gradle 脚本: 在 settings.gradle 文件中的代码会在初始化阶段执行;
- 4、实例化 Project 接口实例: Gradle 会解析
include
申明的模块,并为每个模块build.gradle
文件实例化 Project 接口实例。Gradle 默认会在工程根目录下寻找 include 蕴含的我的项目,如果你想蕴含其余工程目录下的我的项目,能够这样配置:
// 援用以后工程目录下的模块
include ':app'
// 援用其余工程目录下的模块
include 'video' // 易错点:不要加’冒号 :‘project(:video).projectDir = new File("..\\libs\\video")
提醒: 模块 build.gradle 文件的执行程序和 include 程序没有关系。
3.2 配置阶段
配置阶段(Configuration Phase)将执行 build.gradle 中的构建逻辑,以实现 Project 的配置。次要蕴含 3 步:
- 1、下载插件和依赖: Project 通常须要依赖其余插件或 Project 来实现工作,如果有须要先下载;
- 2、执行脚本代码: 在 build.gradle 文件中的代码会在配置阶段执行;
- 3、结构 Task DAG: 依据 Task 的依赖关系结构一个有向无环图,以便在执行阶段依照依赖关系执行 Task。
提醒: 执行任何 Gradle 构建命令,都会先执行初始化阶段和配置阶段。
3.3 执行阶段
在配置阶段曾经结构了 Task DAG,执行阶段(Execution Phase)就是依照依赖关系执行 Task。这里有两个容易了解谬误的中央:
- 1、Task 配置代码在配置阶段执行,而 Task 动作在执行阶段执行;
- 2、即便执行一个 Task,整个工程的初始化阶段和所有 Project 的配置阶段也都会执行,这是为了反对执行过程中拜访构建模型的任何局部。
原文: This means that when a single task, from a single project is requested, all projects of a multi-project build are configured first. The reason every project needs to be configured is to support the flexibility of accessing and changing any part of the Gradle project model.
介绍完三个生命周期阶段后,你能够通过以下 Demo 领会各个代码单元所处的执行阶段:
USER_HOME/.gradle/init.gradle
println 'init.gradle:This is executed during the initialization phase.'
settings.gradle
rootProject.name = 'basic'
println 'settings.gradle:This is executed during the initialization phase.'
build.gradle
println 'build.gradle:This is executed during the configuration phase.'
tasks.register('test') {
doFirst {println 'build.gradle:This is executed first during the execution phase.'}
doLast {println 'build.gradle:This is executed last during the execution phase.'}
// 易错点:这里在配置阶段执行
println 'build.gradle:This is executed during the configuration phase as well.'
}
输入:
Executing tasks: [test] in project /Users/pengxurui/workspace/public/EasyUpload
init.gradle:This is executed during the initialization phase.
settings.gradle:This is executed during the initialization phase.
> Configure project :
build.gradle:This is executed during the configuration phase.
build.gradle:This is executed during the configuration phase as well.
> Task :test
build.gradle:This is executed first during the execution phase.
build.gradle:This is executed last during the execution phase.
...
提醒: Task 在执行阶段执行有一个特例,即通过 Project#defaultTasks 指定默认工作,会在配置阶段会执行,见 第 6.2 节 ,理解即可。
3.4 生命周期监听
Gradle 提供了一系列监听构建生命周期流程的接口,大部分的节点都有间接的 Hook 点,这里我总结一些罕用的:
- 1、监听初始化阶段
Gradle 接口提供了监听 Settings 初始化阶段的办法:
settings.gradle
// Settings 配置结束
gradle.settingsEvaluated {...}
// 所有 Project 对象创立(留神:此时 build.gradle 中的配置代码还未执行)gradle.projectsLoaded {...}
- 2、监听配置阶段
Project 接口提供了监听以后 Project 配置阶段执行的办法,其中 afterEvaluate 罕用于在 Project 配置实现后持续减少额定的配置,例如 Hook 构建过程中的 Task。
// 执行 build.gradle 前
project.beforeEvaluate {...}
// 执行 build.gradle 后
project.afterEvaluate {...}
除此之外,Gradle 接口也提供了配置阶段的监听:
// 执行 build.gradle 前
gradle.beforeProject { project ->
...
}
// 执行 build.gradle 后
gradle.afterProject { project ->
// 配置后,无论胜利或失败
if (project.state.failure) {println "Evaluation of $project FAILED"} else {println "Evaluation of $project succeeded"}
}
// 与 project.beforeEvaluate 和 project.afterEvaluate 等价
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
@Override
void beforeEvaluate(Project project) {...}
@Override
void afterEvaluate(Project project, ProjectState projectState) {...}
})
// 依赖关系解析结束
gradle.addListener(new DependencyResolutionListener() {
@Override
void beforeResolve(ResolvableDependencies dependencies) {....}
@Override
void afterResolve(ResolvableDependencies dependencies) {....}
})
// Task DAG 结构结束
gradle.taskGraph.whenReady { }
// 与 gradle.taskGraph.whenReady 等价
gradle.addListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {...}
})
// 所有 Project 的 build.gradle 执行结束
gradle.projectsEvaluated {...}
- 3、监听执行阶段
Gradle 接口提供了执行阶段的监听:
gradle.addListener(new TaskExecutionListener() {
// 执行 Task 前
@Override
void beforeExecute(Task task) {...}
// 执行 Task 后
@Override
void afterExecute(Task task, TaskState state) {...}
})
gradle.addListener(new TaskActionListener() {
// 开始执行 Action 列表前,回调机会略晚于 TaskExecutionListener#beforeExecute
@Override
void beforeActions(Task task) {...}
// 执行 Action 列表结束,回调机会略早于 TaskExecutionListener#afterExecute
@Override
void afterActions(Task task) {...}
})
// 执行 Task 前
gradle.taskGraph.beforeTask {Task task ->}
// 执行 Task 后
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (state.failure) {println "FAILED"}
else {println "done"}
}
- 4、监听 Task 创立
TaskContainer 接口提供了监听 Task 增加的办法,能够在 Task 增加到 Project 时收到回调:
tasks.whenTaskAdded {task ->}
- 5、监听构建完结
当所有 Task 执行结束,意味着构建完结:
gradle.buildFinished {...}
4. Project 外围 API
Project 能够了解为模块的构建管理器,在初始化阶段,Gradle 会为每个模块的 build.gradle 文件实例化一个接口对象。在 .gradle 脚本中编写的代码,实质上能够了解为是在一个 Project 子类中编写的。
4.1 Project API
Project 提供了一系列操作 Project 对象的 API:
- getProject(): 返回以后 Project;
- getParent(): 返回父 Project,如果在工程 RootProject 中调用,则会返回 null;
- getRootProject(): 返回工程 RootProject;
- getAllprojects(): 返回一个 Project Set 汇合,蕴含以后 Project 与所有子 Project;
- getSubprojects(): 返回一个 Project Set 汇合,蕴含所有子 Project;
- project(String): 返回指定 Project,不存在时抛出 UnKnownProjectException;
- findProject(String): 返回指定 Project,不存在时返回 null;
- allprojects(Closure): 为以后 Project 以及所有子 Project 减少配置;
- subprojects(Closure): 为所有子 Project 减少配置。
4.2 Project 属性 API
Project 提供了一系列操作属性的 API,通过属性 API 能够实现在 Project 之间共享配置参数:
- hasProperty(String): 判断是否存在指定属性名;
- property(Stirng): 获取属性值,如果属性不存在则抛出 MissingPropertyException;
- findProperty(String): 获取属性值,如果属性不存在则返回 null;
- setProperty(String, Object): 设置属性值,如果属性不存在则抛出 MissingPropertyException。
实际上,你不肯定须要显示调用这些 API,当咱们间接应用属性名时,Gradle 会帮咱们隐式调用 property() 或 setProperty()。例如:
build.gradle
name => 相当于 project.getProperty("name")
project.name = "Peng" => 相当于 project.setProperty("name", "Peng")
4.2.1 属性匹配优先级
Project 属性的概念比咱们了解的字段概念要简单些,不仅仅是一个简略的键值对。Project 定义了 4 种命名空间(scopes)的属性 —— 自有属性、Extension 属性、ext 属性、Task。 当咱们通过拜访属性时,会依照这个优先级顺序搜索。
getProperty()
的搜寻过程:
- 1、自有属性:Project 对象本身持有的属性,例如 rootProject 属性;
- 2、Extension 属性;
- 3、ext 属性;
- 4、Task:增加到 Project 上的 Task 也反对通过属性 API 拜访;
- 5、父 Project 的 ext 属性:会被子 Project 继承,因而当 1 ~ 5 未命中时,会持续从父 Project 搜寻。须要留神: 从父 Project 继承的属性是只读的;
- 6、以上未命中,抛出 MissingPropertyException 或返回 null。
setProperty()
的搜寻门路(因为局部属性是只读的,搜寻门路较短):
- 1、自有属性
- 2、ext 额定属性
提醒: 其实还有 Convention 命名空间,不过曾经过期了,咱们不思考。
4.2.2 Extension 扩大
Extension 扩大是插件为内部构建脚本提供的配置项,用于反对内部自定义插件的工作形式,其实就是一个对外开放的 Java Bean 或 Groovy Bean。例如,咱们相熟的 android{}
就是 Android Gradle Plugin 提供的扩大。
对于插件 Extension 扩大的更多内容,见下一篇文章。
4.2.3 ext 属性
Gradle 为 Project 和 Task 提供了 ext 命名空间,用于定义额定属性。如前所述,子 Project 会继承 父 Project 定义的 ext 属性,然而只读的。咱们常常会在 Root Project 中定义 ext 属性,而在子 Project 中能够间接复用属性值,例如:
我的项目 build.gradle
ext {kotlin_version = '1.4.31'}
模块 build.gradle
// 如果子 Project 也定义了 kotlin_version 属性,则不会援用父 Project
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
4.3 Project 文件 API
4.3.1 文件门路
- getRootDir(): Project 的根目录(不是工程根目录)
- getProjectDir(): 蕴含 build 文件夹的我的项目目录
- getBuildDir(): build 文件夹目录
4.3.2 文件获取
- File file(Object path): 获取单个文件,绝对地位从以后 Project 目录开始
- ConfigurableFileCollection files(Object… paths): 获取多个文件,绝对地位从以后 Project 目录开始
def destFile = file('releases.xml')
if (destFile != null && !destFile.exists()) {destFile.createNewFile()
}
4.3.3 文件拷贝
- copy(Closure): 文件拷贝,参数闭包用于配置 CodeSpec 对象
copy {
// 起源文件
from file("build/outputs/apk")
// 指标文件
into getRootProject().getBuildDir().path + "/apk/"
exclude {// 排除不须要拷贝的文件}
rename {// 对拷贝过去的文件进行重命名}
}
4.3.4 文件遍历
- fileTree(Object baseDir): 将指定目录转化为文件树,再进行遍历操作
fileTree("build/outputs/apk") { FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
// 文件操作
}
}
5. Task 外围 API
Project 的构建逻辑由一系列 Task 的组成,每个 Task 负责实现一个根本的工作,例如 Javac 编译 Task、资源编译 Task、Lint 查看 Task,签名 Task 等。在构建配置阶段,Gradle 会依据 Task 的依赖关系结构一个有向无环图,以便在执行阶段依照依赖关系执行 Task。
5.1 创立简略 Task
Gradle 反对两种创立简略 Task 的语法:
- 1、通过 task 关键字:
// 创立名为 MyTask 的工作
task MyTask(group: "MyGroup") {// Task 配置代码}
-
2、通过 TaskContainer 办法: 通过 Project 的 TaskContainer 属性,能够创立 Task,分为热创立和懒创立:
- Task create(String, Closure) 热创立: 立刻实例化 Task 对象;
- TaskProvider register(String, Closure) 懒创立: 注册 Task 结构器,但不会实例化对象。创立 Task 操作会提早到拜访该 Task 时,例如通过 TaskProvider#get() 或 TaskContainer#getByName()。
// 创立名为 MyTask 的工作
project.tasks.create(name: "MyTask") {// Task 配置代码}
5.2 创立加强 Task(自定义 Task 类型)
除了简略创立 Task 的形式,咱们还能够自定义 Task 类型,Gradle 将这类 Task 称为加强 Task。加强 Task 的可重用性更好,并且能够通过裸露属性的形式来定制 Task 的行为。
- 1、DefaultTask: 自定义 Task 必须继承 DefaultTask。
class CustomTask extends DefaultTask {
final String message
final int number
}
- 2、带参数创立 Task: 除了能够在创立 Task 后配置属性值,咱们也能够在调用 TaskContainer#create() 时传递结构器参数。为了将值传递给工作构造函数,必须应用
@Inject
注解润饰结构器。
class CustomTask extends DefaultTask {
final String message
final int number
@Inject
CustomTask(String message, int number) {
this.message = message
this.number = number
}
}
// 第二个参数为 Task 类型
tasks.register('myTask', CustomTask, 'hello', 42)
5.3 获取已创立 Task
能够获取 TaskContainer 中已创立的工作,对于通过 register 注册的工作会在这个机会实例化。例如:
- Task getByName(String): 获取 Task,如果 Task 不存在则抛出 UnKnownDomainObjectException;
- Task findByName(String): 获取 Task,如果 Task 不存在则返回 null。
// 获取已创立的 Task
project.MyTask.name => 等同于 project.tasks.getByName("MyTask").name
5.4 设置 Task 属性
设置 Task 属性的语法次要有三种:
- 1、在创立 Task 时设置
task MyTask(group: "MyGroup")
- 2、通过 setter 办法设置
task MyTask {group = "MyGroup" => 等同于 setGroup("MyGroup")
}
- 3、通过 ext 额定属性设置: Task 也反对与 Project 相似的额定属性。例如:
task MyTask(group:"111") {ext.goods = 2}
ext.goods = 1
println MyTask.good
输入:2
Task 罕用的自有属性如下:
属性 | 形容 |
---|---|
name | Task 标识符,在定义 Task 时指定 |
group | Task 所属的组 |
description | Task 的形容信息 |
type | Task 类型,默认为 DefaultTask |
actions | 动作列表 |
dependsOn | 依赖列表 |
注意事项:
- 严格防止应用带空格的 Task
name
,否则在一些版本的 Android Studio 中会被截断,导致不兼容; - Android Studio 的 Gradle 面板会依照
group
属性对 Task 进行分组显示。其中,Tasks 组为 Root Project 中的 Task,其余分组为各个 Project 中的 Task,未指定 group 的 Task 会调配在 other 中。
5.5 执行 Task
- 1、命令行: gradlew :[模块名]:[工作名],例如:gradlew -q :app:dependencies
- 2、IDE 工具: 通过 IDE 提供的用户界面工具执行,例如 Gradle 面板或绿色三角形,反对一般执行和调试执行;
- 3、默认工作: 通过 Project#defaultTasks 能够指定 Project 配置阶段的默认工作,在配置阶段会执行(这阐明 Task 是有可能在配置阶段执行的,理解即可,不必钻牛角尖)。
build.gradle
defaultTasks 'hello','hello2'
task hello {println "defaultTasks hello"}
task hello2 {println "defaultTasks hello2"}
输入:> Configure project :easyupload
defaultTasks hello
defaultTasks hello2
--afterEvaluate--
--taskGraph.whenReady--
5.6 Task Action 动作
每个 Task 外部都放弃了一个 Action 列表 actions
,执行 Task 就是按程序执行这个列表,Action 是比 Task 更细的代码单元。Task 反对增加多个动作,Task 提供了两个办法来增加 Action:
- doFirst(Closure): 在 Action 列表头部增加一个 Action;
- doLast(Closure): 在 Action 列表尾部增加一个 Action。
task MyTask
MyTask.doFirst{println "Action doFirst 1"}
MyTask.doFirst{println "Action doFirst 2"}
MyTask.doLast{println "Action doLast 1"}
执行 MyTask 输入:
Action doFirst 2
Action doFirst 1
Action doLast 1
对于自定义 Task,还能够通过 @TaskAction
注解增加默认 Action。例如:
abstract class CustomTask extends DefaultTask {
@TaskAction
def greet() {println 'hello from GreetingTask'}
}
5.7 跳过 Task 的执行
并不是所有 Task 都会被执行,Gradle 提供了多个办法来管制跳过 Task 的执行:
- 1、onlyIf{}: 闭包会在行将执行 Task 之前执行,闭包返回值决定了是否执行 Task;
- 2、enabled 属性: Task 的 enabled 属性默认为 true,设置为 false 示意有效工作,不须要执行。
剩下两种形式容许在执行 Task 的过程中中断执行:
-
3、Task 异样: Task 提供了两个异样,可能当 Action 执行过程中抛出以下异样,将跳过执行并持续后续的构建过程:
- StopActionException: 中断以后 Action,并持续以后 Task 的下一个 Action;
- StopExecutionException: 中断以后 Task,并持续 Task 依赖树上的下一个 Action。
- 4、timeouts 属性: 当 Task 执行工夫达到 timeouts 超时工夫时,执行线程会收到一个中断信号,能够借此许管制 Task 的执行工夫(前提是 Task 要响应中断信号)。
5.8 Task 依赖关系
通过建设 Task 的依赖关系能够构建实现的 Task 有向无环图:
- dependsOn 强依赖: Task 通过 dependsOn 属性建设强依赖关系,能够间接通过 dependsOn 属性设置依赖列表,也能够通过 dependsOn() 办法增加一个依赖;
- 输入输出隐式依赖: 通过建设 Task 之间的输出和输入关系,也会隐式建设依赖关系。例如 Transform Task 之间就是通过输入输出建设的依赖关系。
// 通过属性设置依赖列表
task task3(dependsOn: [task1, task2]) {
}
// 增加依赖
task3.dependsOn(task1, task2)
依赖关系:task3 依赖于 [task1, task2],在执行 task3 前肯定会执行 task1 和 task2
在某些状况下,管制两个工作的执行程序十分有用,而不会在这些工作之间引入显式依赖关系,能够了解为弱依赖。 工作排序和工作依赖关系之间的次要区别在于,排序规定不影响将执行哪些工作,只影响工作的执行程序。
- mustRunAfter 强制程序: 指定强制要求的工作执行程序;
- shouldRunAfter 非强制程序: 指定非强制的工作执行程序,在两种状况下会放弃此规定:1、该规定造成环形程序;2、并行执行并且工作的所有依赖项都曾经实现。
task3 mustRunAfter(task1, task2)
task3 shouldRunAfter(task1, task2)
依赖关系:无,在执行 task3 前不肯定会执行 task1 和 task2
程序关系:[task1, task2] 优先于 task3
5.9 Finalizer Task
给一个 Task 增加 Finalizer 终结器工作后,无论 Task 执行胜利还是执行失败,都会执行终结器,这对于须要在 Task 执行结束后清理资源的状况十分有用。
// taskY 是 taskX 的终结器
taskX finalizedBy taskY
6. 增量构建
6.1 什么是增量构建?
任何构建工具都会尽量避免反复执行雷同工作,这一个性称为 Incremental Build 增量构建,这一个性可能节俭大量构建工夫。例如编译过源文件后就不应该反复编译,除非产生了影响输入的更改(例如批改或删除源文件)。
Gradle 通过比照自从上一次构建之后,Task 的 inputs
和 outputs
是否变动,来决定是否跳过执行。如果雷同,则 Gralde 认为 Task 是最新的,从而会跳过执行。在 Build Outputs 中看到 Task 名称旁边呈现 UP-TO-DATE
标记,即阐明该 Task 是被跳过的。例如:
> Task :easyupload:compileJava NO-SOURCE
> Task :easyupload:compileGroovy UP-TO-DATE
> Task :easyupload:pluginDescriptors UP-TO-DATE
> Task :easyupload:processResources UP-TO-DATE
> Task :easyupload:classes UP-TO-DATE
> Task :easyupload:jar UP-TO-DATE
> Task :easyupload:uploadArchives
那么,在定义 Task 的输入输出时,要遵循一个准则:如果 Task 的一个属性会影响输入,那么应该将该属性注册为输出,否则会影响 Task 执行;相同,如果 Task 的一个属性不会影响输入,那么不应该将该属性注册为输出,否则 Task 会在不必要时执行。
6.2 Task 输入输出
大多数状况下,Task 须要接管一些 input 输出,并生成一些 output 输入。例如编译工作,输出是源文件,而输入是 Class 文件。Task 应用 TaskInputs 和 TaskOutputs 治理输入输出:
- Task#inputs: 返回 Task 的 TaskInputs 输出管理器;
- Task#outputs: 返回 Task 的 TaskOutputs 输入管理器。
对于 Task 的输入输出,咱们用面向对象的概念去了解是没问题的。如果咱们把 Task 了解为一个函数,则 Task 的输出就是函数的参数,而 Task 的输入就是函数的返回值。在此了解的根底上,再记住 2 个关键点:
- 1、隐式依赖: 如果一个 Task 的输出是另一个 Task 的输入,Gradle 会推断出两者之间的强依赖关系;
- 2、在配置阶段申明: 因为 Task 的输入输出会用于构建依赖关系,那么咱们应该确保在配置阶段定义输入输出,而不是在执行阶段定义。
Task 反对三种模式的输出:
- 1、简略值: 包含数值、字符串和任何实现 Serializable 的类;
- 2、文件: 包含单个文件或文件目录;
- 3、嵌套对象: 不满足以上两种条件,但其字段申明为输出。
public abstract class ProcessTemplates extends DefaultTask {
@Input
public abstract Property<TemplateEngineType> getTemplateEngine();
@InputFiles
public abstract ConfigurableFileCollection getSourceFiles();
@Nested
public abstract TemplateData getTemplateData();
@OutputDirectory
public abstract DirectoryProperty getOutputDir();
@TaskAction
public void processTemplates() {// ...}
}
public abstract class TemplateData {
@Input
public abstract Property<String> getName();
@Input
public abstract MapProperty<String, String> getVariables();}
6.3 Task 输入输出校验
通过注解形式注册输入输出时,Gradle 会在配置阶段会对属性值进行查看。如果属性值不满足条件,则 Gradle 会抛出 TaskValidationException
异样。非凡状况时,如果容许输出为 null 值,能够增加 @Optional
注解示意输出可空。
- @InputFile: 验证该属性值不为 null,并且关联一个文件(而不是文件夹),且该文件存在;
- @InputDirectory: 验证该属性值不为 null,并且关联一个文件夹(而不是文件),且该文件夹存在;
- @OutputDirectory: 验证该属性值不为 null,并且关联一个文件夹(而不是文件),当该文件夹不存在时会创立该文件夹。
7. 总结
到这里,Gradle 根底的局部就讲完了,下一篇文章咱们来探讨 Gradle 插件。提个问题,你晓得 Gradle 插件和 .gradle 文件有区别吗?关注我,带你理解更多。
参考资料
- 《实战 Gradle》—— [美] Benjamin Muschko 著,李建 朱本威 杨柳 译
- 《Gradle for Android》—— Kevin Pelgrims 著,余小乐 译
- Groovy 参考文档 —— Groovy 官网文档
- Gradle 阐明文档 —— Gradle 官网文档
- Gradle DSL 参考文档 —— Gradle 官网文档
- 深刻摸索 Gradle 自动化构建技术(系列)—— jsonchao 著