前言
随着业务需求变得越来越复杂,项目的规模也变得越来越大,项目越大包含的代码资源文件也就越多,而越大的项目往往需要越多的开发者共同维护,这种状态就导致即使是简单的构建目标对象文件也非常困难,于是就有了自动构建工具。早期的自动构建工具主要是Ant和Maven,不过Ant只是命令式配置执行,不容易维护和复用,Maven虽然有了插件化和依赖管理功能,但它的构建过程相对死板无法灵活配置,对于条件式的构建支持不够友好,鉴于它们存在的这些问题大牛们又开发出了Gradle构建工具。
Gradle执行阶段
阶段名称 | 阶段工作 |
---|---|
初始化阶段 | 解析整个工程中的所有Project,构建所有的Project对应的project对象 |
配置阶段 | 解析所有的projects对象中的task,构建好所有task的拓扑图 |
执行阶段 | 执行具体的task及其依赖task |
需要注意的是写在Gradle构建脚本中的代码分成两大类,配置代码和动作代码,动作代码是指Task接口中写在doFirst和doLast闭包里的代码,而除了动作代码之外的代码都是配置代码,配置代码会在配置阶段执行,动作代码在执行阶段才会执行。
监听接口
Gradle提供了构建生命周期钩子来支持执行构建过程中发生某件事情时用户想要执行逻辑,这种逻辑可以添加在构建阶段的之前、过程中和之后,可以使用Gradle提供的监听器接口来做处理。
监听接口 | 监听阶段 |
---|---|
project.beforeEvaluate() | 在配置阶段开始之前,初始化阶段和配置阶段之间的监听 |
project.afterEvaluate() | 在配置阶段之后,在执行阶段之前执行的监听 |
project.gradle.buildFinished { } | 在gradle生命周期完之后的监听 |
如果在build.gradle里设置beforeEvaluate会发现里面的代码根本没有被执行,这是因为在初始化阶段build.gradle文件中的代码根本不会被执行,执行build.gradle里的配置代码其实已经到了配置阶段,这种在配置阶段之前执行的代码可以放到~/.gradle/init.d文件夹下的文件里执行,这样的话这个执行脚本的代码就会成为本机所有工程都会执行的逻辑。
// ~/.gradle/init.d/project.gradle
gradle.projectsLoaded {
gradle -> gradle.rootProject.beforeEvaluate {
println "Root Project Before Evaluate"
}
}
$ gradle clean
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
> Configure project :
Root Project Before Evaluate
还可以通过addListener接口来添加监听事件
Gradle命令行
Gradle命令行主要是针对构建脚本中的task任务执行,常用的任务分为帮助类Task、构建设置类Task和构建Task。在实际工作中会经常用到帮助类Task来查看构建相关的信息。
帮助任务 | 任务用处 |
---|---|
gradle projects | 可以查看所有的项目树形结构 |
gradle properties | 列出项目中所有的属性,可以是Project对象提供,也可以使用户自定义属性 |
gradle tasks | 显示所有可运行的Task包括它们的描述信息 |
gradle buildEnvironment | 显示根项目构建脚本的依赖 |
gradle components | 该项目所有的Comoponent |
gradle dependencies | 项目的所有依赖 |
除了上面的命令行后面还可以添加选项,常用选项如下:
选项 | 意义 |
---|---|
-b buildfile | 不使用默认的build.gradle构建脚本使用buildfile脚本 |
–offline | 构建时不访问远程仓库,只使用本地仓库构建 |
–deamon | 使用后台线程执行任务,可以加速构建过程 |
–stop | 停止处于后台的构建任务线程 |
Project接口
Gradle里的根project负责管理所有内部的子project,而子project负责输出某个资源,比如jar、aar、war或者apk等,每个Project都必须有一个build.gradle文件,没有build.gradle文件就会被认为是普通文件夹。可见Project对象在Gradle中占有重要的地位,掌握好它的各种接口用法对写好Gradle脚本至关重要。
project api分为六个部分:project相关API,让当前project拥有操作父project和管理子project功能;task相关API,为当前project新增task或者使用已有的task功能;project属性相关API,为project添加额外属性;file相关API,主要用来处理当前project之下的文件功能;gradle生命周期API;其他API为project添加依赖,添加配置,如何引入外部配置等。
Project相关API
接口 | 作用 |
---|---|
getAllProjects | 获取所有的project |
getSubProjects | 获取所有的子project |
getParent | 获取当前project的父project |
getRootProject | 获取根project对象 |
project(String path, Closure configureClosure) | 指定项目的路径,并且传入闭包配置该项目,获取指定位置的项目对象 |
allprojects {} | 配置所有项目的通用逻辑 |
subprojects {} | 不包括当前节点工程,只包括它的子工程 |
project('app') {
Project project -> println project.name
}
allprojects {
group 'com.meitu'
version '2.0.1'
}
println project('app').group
subprojects { Project project ->
apply from: '../publishToMaven.gradle'
}
属性相关API
定义扩展属性常用ext(Closure closure),使用起来非常简单,对于一些全局通用的属性,可以有如下四种定义方式,不过为了避免重复,通常都选择第三、四种定义方式。
- 在每个子project中定义扩展属性
- 在根project的subprojects里定义扩展属性,每个子project会自动包含这些扩展属性
- 定义在根project的扩展属性可以在所有的子project中引用
- 在gradle文件中定义扩展属性,并且apply到根project的配置文件中,在子project中引用这些扩展属性
// RootProject build.gradle文件中定义
ext {
// 定义全局使用的一些属性值
supportLibraryVersion = '27.1.1'
rxbindingVersion = '2.1.1'
rxjavaVersion = '2.1.3'
rxandroidVersion = '2.0.1'
mtsnsVersion = '1.7.0'
glideVersion = '4.4.0'
}
在gradle.properties文件中定义扩展属性,里面用key=value类型的数据,每一行定义一个属性。需要使用这个属性值的时候,可以直接使用Project.hasProperty判断是否存在属性值,存在可以直接使用属性名来引用属性,注意获取属性的时候它是Object类型的,如果需要使用基本类型需要调用转换方法。
文件相关API
如果当前Project想要访问其他Project里的文件资源,这时就需要获取其他Project的文件路径,Project对象已经提供好了用于访问统一工程里其他Project路径的接口,主要有下面三个:
接口名 | 作用 |
---|---|
getRootDir | 获取根工程的路径 |
getBuildDir | 获取build文件夹路径 |
getProjectDir | 获取当前工程的路径 |
Project对象作为构建工具,操作工程里的各种文件功能必不可少。file()接口以当前的project为根路径查找文件并且返回当前文件对象,这个和new File()功能一致而且不需要传递绝对路径,因而更加方便使用;files()接口可以定位一个或者多个,参数是一个数组,返回的是一个FileCollections对象。
copy{}接口接受一个闭包,闭包内部详细注明了要拷贝文件位置,移动到何处,有哪写文件需要被排除,那些需要被重命名。
copy {
from file('local.properties')
into project('app').getProjectDir()
exclude { }
rename { }
}
fileTree接口可以对文件树进行遍历,内部需要传入遍历的闭包。
fileTree('gradle') {
FileTree fileTree ->
fileTree.visit {
FileTreeElement element ->
println element.name
}
}
其他还有delete mkdir等接口,这里不再赘述。
调用外部命令
exec这个方法可以执行外部命令
task hello {
doLast {
def command = "ls"
exec {
executable 'sh'
args '-c', command
println 'The command is over'
}
}
}
Task配置与执行
Project类中包含一个task方法,只需要传入taskName和闭包配置任务就可以了,除了Project的task接口还有TaskContainer对象tasks也可以用来创建task对象,需要调用TaskContainer.create方法。
task hello {
println "Hello world"
}
task('hellotask') {
println 'I am hello task'
}
tasks.create(name: 'task2') {
println 'This is task2'
}
task的setGroup和setDescription方法可以很方便的查找和设置注释,这两个方法通常都会在配置时执行。Task中还提供了doFirst和doLast在任务最开始和最后位置添加动作功能,这两个配置可以多次调用添加,doFirst会一直在最开头位置添加,doLast会始终在尾部位置添加任务代码。
task hello(group: 'Hello'){
println "Hello world"
doFirst {
println "The task group is: " + group
}
doFirst {
println "I am the first executed block"
}
}
// I am the first executed block
// The task group is: Hello
task('hellotask') {
group 'Hello'
println 'I am hello task'
doLast {
println "The task group is " + group
}
doLast {
println "I am the last executed block"
}
}
:hellotask
// The task group is Hello
// I am the last executed block
除了自定义的任务,Android中内置的一些任务也可以配置,比如可以计算整个构建阶段花费的时间,只需要在最开始的那个任务preBuild计时,在最后的任务build最后减去即时时间就能够得到整体构建时间。
def startTime, endTime
afterEvaluate { Project project ->
def preBuild = project.tasks.findByName("preBuild")
preBuild.doFirst {
startTime = System.currentTimeMillis()
println "The gradle start build, time = " + startTime
}
def assemble = project.tasks.findByName("assemble")
assemble.doLast {
endTime = System.currentTimeMillis()
println "The gradle build time is = " + endTime
println "Total time = ${endTime - startTime}"
}
}
// The gradle build time is = 1526049444569
// Total time = 14338
Task执行顺序
dependOn强依赖形式,它也等价于通过Task输入输出指定,还有一种通过API指定执行顺序。
task taskX {
doLast {
println 'TaskX'
}
}
task taskY {
doLast {
println 'TaskY'
}
}
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task taskZ(dependsOn: [taskX, taskY]) {
dependsOn this.tasks.findAll {
task -> return task.name.startsWith('lib')
}
doLast {
println 'TaskZ'
}
}
:lib1
lib1
:lib2
lib2
:taskX
TaskX
:taskY
TaskY
:taskZ
TaskZ
上面的创建taskZ指定了它所依赖的taskX和taskY,执行最后一个taskZ就会自动执行前两个task,前两个方法的执行顺序其实是随机的,它们之间没有任何依赖关系。在配置代码中又将lib1、lib2添加到了依赖中,这是通过dependsOn接口实现的。
Task输入输出
TaskInputs类代表任务的输入类型,可以接受文件、文件夹、属性等,TaskOutputs代表任务输出类型,只包含文件和文件夹类型。前一个任务将输出作为第二个任务的输入,这样第二个任务就强依赖第一个任务。
ext {
versionName = '1.0.0'
versionCode = '100'
versionInfo = 'App Version 1.0.0'
destFile = file('release.json')
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
}
}
class VersionEntity {
String versionName
String versionCode
String versionInfo
@Override
String toString() {
return "VersionEntity{" +
"versionName='" + versionName + '\'' +
", versionCode='" + versionCode + '\'' +
", versionInfo='" + versionInfo + '\'' +
'}';
}
}
task writeTask {
inputs.property('versionName', this.versionName)
inputs.property('versionCode', this.versionCode)
inputs.property('versionInfo', this.versionInfo)
outputs.file destFile
doLast {
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()
def entity = new VersionEntity(data)
def json = JsonOutput.toJson(entity)
file.withWriter {
writer -> writer.println(json)
}
}
}
task readTask {
inputs.file destFile
doLast {
def file = inputs.files.singleFile
println file.text
}
}
要将前面写入应用版本的任务挂接到Android构建的生命周期中,只需要在已有的任务里添加动作代码。
afterEvaluate { Project project ->
def assemble = project.tasks.findByName("assemble")
assemble.doLast {
writeTask.execute()
}
}