gradle同maven、ant一样,可以作为构建工具进行项目的构建,不同的是,gradle中的包可以被项目所引用(也就是说相比较于构建工具,gradle更像是一个框架)。
1、生命周期
gradle生命周期包括初始化阶段、配置阶段、执行阶段。
- 初始化阶段解析整个工程中所有的Project,构建所有的Project对应的project对象。
- 配置阶段解析所有project对象中的task,构建好所有task的拓扑图。
- 执行阶段执行具体的task及其依赖task。
Initialization初始化阶段
Configuration配置阶段
Execution执行阶段
build.gradle中有如下方法用于监听gardle的生命周期。
No | 方法名 | 描述 |
---|
1 | beforeEvaluate | 配置阶段开始前的监听回调 | 2 | afterEvaluate | 配置阶段完成之后的监听回调 | 3 | buildFinished | gradle生命结束之后的监听 |
示例:
在settings.gradle中输出(初始化阶段实际上就是执行了settings.gradle文件)。
println '初始化阶段开始执行...'
在build.gradle中添加如下方法
this.beforeEvaluate {}
this.afterEvaluate {
println '配置阶段执行完毕..'
}
this.gradle.buildFinished {
println '执行阶段执行完毕..'
}
在控制台输入:gradle clean 后得到如下结果:
E:\idea-projects\hello-gradle>gradle clean 初始化阶段开始执行… Configure project : > 配置阶段执行完毕… 执行阶段执行完毕… BUILD SUCCESSFUL in 612ms 1 actionable task: 1 up-to-date
gradle还有其他方法可以监听生命周期。
No | 方法 | 描述 |
---|
1 | this.gradle.beforeProject{} | 等同于beforeEvaluate | 2 | this.gradle.afterProject{} | 等同于afterEvaluate | 3 | this.gradle.addListener{} | 其他监听 |
执行gradle的build命令(build实际上也是一个task)会先将build所依赖的task先执行完,最后在执行build。
compileJava
processResources
classes
javaDoc
compileTestjava
processTestResources
jar
testClasses
uploadArchives
assemible
test
check
build
task的依赖实际上就是一个有向无环图,这种依赖关系在配置阶段完成之后生成。
2、Project
project包括一个根project和若干个子project(子project中同样可以包含project),project中一定会包含有一个build.gradle文件,输入gradlew project 可以查看当前项目所有的project,project的api基本可以分为以下几种。
Project Api组成
gradle生命周期Api
Project相关Api
task相关Api
属性相关Api
file相关Api
其他Api
gradle生命周期Api就是以上一系列的gradle生命周期监听函数。
2.1、Project相关Api
Project相关Api与project的生命周期有关。
-
getAllProjects()方法可以获取到项目中所有的project(类似于gradlew project 的功能)。
getProjects()
def getProjects() {
println '-------------------------------------------'
this.getAllprojects().eachWithIndex {
Project project, int index ->
println((index == 0 ? 'Root project : ' : '+--- project : ') + project.name)
}
println '-------------------------------------------'
}
执行任意的gradle命令都会执行build.gradle脚本,以clean命令为例 输入gradlew build
>Configure project : ------------------------------------------- Root project : hello-gradle ±-- project : gradle-back ±-- project : gradle-web -------------------------------------------
-
getSubProjects方法可以获取到项目中所有的子project。 getProjects()
def getProjects() {
this.getSubprojects().eachWithIndex { Project project, int i ->
println('Sub Project : ' + project.name)
}
}
> Configure project : Sub Project : gradle-back Sub Project : gradle-web
-
getParent方法可以获取到父project。 在gradle-back模块的build.gradle文件输入如下代码:
getParentProject()
def getParentProject() {
println('parent project name: ' + this.getParent().name)
}
> Configure project :gradle-back parent project name: gradle-test
-
getRootProject方法可以获取到根project。
println("root project : ${this.getRootProject().name}")
> Configure project : root project : gradle-test
-
project方法可以在父工程或根工程中完成对子工程的配置。 由于project是树形结构的,那么可以通过父project获取到所有的子project,随后对子project进行修改操作。
project('gradle-web') { Project project ->
group 'com.gradle'
version '1.0.0-release'
dependencies {
}
}
一个gradle的project对应一个gradle.build文件,因此一般情况下不会在父工程中指定子工程的配置,子工程的配置信息写在其build.gradle 文件中,父工程配置整个项目公用的信息。 -
allProjects方法配置当前工程和以当前工程为根的所有工程的信息。 allprojects {
group 'com.gradle'
version '1.0.0-release'
dependencies {
}
}
println(project('gradle-web').group)
-
subProjects方法配置当前工程的子工程的信息。
subprojects { Project project ->
apply from: '../publishMaven.gradle'
}
println(project('gradle-web').group)
apply from 用于导入其他的.gradle 配置文件(gradle的配置文件可以写在多个文件中,使用的时候进行导入即可)。
2.2、属性相关Api
打开Project接口的源码发现其有如下默认属性: gradle的默认属性非常的少,但是可以对其进行扩展。
2.2.1、ext + 闭包可以定义扩展属性。
ext {
defaultGroup = 'com.it.gradle'
defaultVersion = '1.0.0-release'
}
subprojects {
group this.defaultGroup
version this.defaultVersion
dependencies {
}
}
使用扩展属性的好处是可以在其他的project中获取到本project定义的属性,也可以在其他project中指定此project的扩展属性(没有此作用,相当于直接定义变量)。
subprojects {
ext {
defaultGroup = 'com.it.gradle'
defaultVersion = '1.0.0-release'
}
}
虽然以上形式属性只在根project中定义一次,但是经过编译后会在所有的子project中生成2个属性(从本质上来说还是相当于重复定义了多个变量)。可以将ext 扩展块直接定义在根工程中,如果子工程想要使用属性,则需先使用this.rootProject 获取到根工程后再获取属性,这样属性只定义了一次。
根工程:
ext {
defaultGroup = 'com.it.gradle'
defaultVersion = '1.0.0-release'
}
子工程
group this.rootProject.defaultGroup
version this.rootProject.defaultVersion
groovy规定,父工程的所有属性可以被子工程所继承,所以子工程的代码可以简写为:
group this.defaultGroup
version this.defaultVersion
为了使项目更加模块化,可以将扩展属性定义在其他配置文件中,随后通过apply from 引入到根工程中。
新建common.gradle文件,并编写ext扩展块:
ext {
defaultGroup = 'com.it.gradle'
defaultVersion = '1.0.0-release'
}
根project引入common.gradle文件:
apply from: this.file('common.gradle')
2.2.2、gradle.properties文件中定义扩展属性
通过上面的Project接口源码可以发现,project默认引入了gradle.properties文件,所以可以直接将扩展属性写在gradle.properties中,但是由于是.properties 文件,所以属性只能定义key-value的格式(ext扩展块可以定义list、map…格式的属性)。
gradle.properties文件:
isLoadTest=true
settings.gradle文件:
if (hasProperty('isLoadTest') ? isLoadTest.toBoolean() : false) {
include 'test'
}
2.3、文件相关Api
gradle对文件的操作主要有以下几种:
文件相关Api
路径相关Api
get...
文件操作相关Api
文件定位
文件拷贝
文件数遍历
2.3.1. 路径相关Api
路径相关Api有以下几种:
No. | 方法名 | 描述 |
---|
1 | getRootDir() | 获取根工程下文件所在路径 | 2 | getBuildDir() | 获取当前工程下build文件所在路径 | 3 | getProjectDir() | 获取当前工程下project文件所在路径 |
println "the root file path : ${getRootDir().absolutePath}"
println "the build file path : ${getBuildDir().absolutePath}"
println "the project file path : ${getProjectDir().absolutePath}"
2.3.2. 文件操作常用Api
file() 方法用于在project下寻找文件(相对路径)。
def extText = file('./common.gradle')
println extText.text
files() 方法用于在project下寻找多个文件(与file()方法相似)
def extFiles = files('./common.gradle')
extFiles.each {println it.text}
copy() 方法用于实现的文件拷贝。
copy {
from file('copy-test.txt')
into getRootProject().getBuildDir().absolutePath
exclude {}
rename {}
}
fileTree() 方法用于文件树的遍历。
fileTree('build') { FileTree fileTree ->
fileTree.visit { FileTreeElement element ->
println "the file name is ${element.name}"
}
}
2.4、其他类型Api
其他Api包含两部分:
2.4.1. 依赖相关Api
buildscript() 方法用于配置项目仓库地址和插件:
buildscript { ScriptHandler scriptHandler ->
scriptHandler.repositories {
jcenter()
mavenCentral()
mavenLocal()
maven {
name = 'personal'
url = 'http://localhost:8081/nexus/repository'
credentials{
username = 'admin'
password = '123456'
}
}
ivy {}
}
scriptHandler.dependencies {
classpath: 'com.android.tools.build:gradle:2.2.2'
classpath: 'com.tencent.tinker:tinker-patch-gradle-plugin:1.7.7'
}
}
repositories() 方法也可以配置仓库信息:
repositories {
jcenter()
mavenCentral()
mavenLocal()
maven {
name = 'personal'
url ='http://localhost:8081/nexus/repository'
credentials{
username = 'admin'
password = '123456'
}
}
ivy {}
}
dependencies() 方法用于配置项目依赖:
dependencies {
compile 'junit:junit:4.12'
compile ('org.springframework.boot:spring-boot-starter-web:5.3.0') {
exclude group: 'org.springframework'
exclude module: 'spring-boot-starter-web'
}
}
2.4.2. 外部命令
exec() 方法用于执行外部命令。
task(name: 'apkCopy') {
doLast {
def sourcePath = this.buildDir.path + '/outputs/apk'
def desPath = '/usr/local/apk'
def command = "mv -f ${sourcePath} ${desPath}"
exec{
try{
executable 'bash'
args '-c ', command
}catch(GradleException e) {
println 'copy fail : ' + e.message
}
}
}
}
3、Task
3.1、Task的创建与配置
执行gradlew tasks 可以查看工程中有多少个task。可以通过task()方法直接定义一个task。
task helloTask {
println 'i am hello task'
}
通过gradlew helloTask 执行task。
>Configure project : i am hello task
task还可以通过TaskContainer的create()方法来创建。
this.tasks.create(name: 'helloTask') {
println 'i am hello task'
}
以上两种方式创建的task并无任何区别,通过task()方法创建的task实际上也会被添加到project的taskContainer中,TaskContainer实际上是一个管理类,负责管理project中的Task,其有如下主要方法:
No. | 方法名 | 描述 |
---|
1 | findByPath | 通过路径查找task,找不到返回null | 2 | getByPath | 通过路径查找task,找不到抛出异常 | 3 | create | 创建一个task |
在task创建的时候可以直接指定一些配置。
task helloTask(group:'com.it.utool','description':'task study') {
println 'i am hello task'
}
也可以在闭包中调用task的setter方法进行配置。
this.tasks.create(name: 'helloTask') {
setGroup('com.it.utool')
setDescription('task study')
println 'i am hello task'
}
task的所有配置可以通过查看Task源码来查找。
3.2、Task执行详解
task闭包中的代码会在task的配置阶段执行,想要让代码在执行阶段执行,需要调用task的doFirst 方法和doLast 方法。
this.tasks.create(name: 'helloTask') {
setGroup('com.it.utool')
setDescription('task study')
doFirst {
}
doLast {
}
}
也可以定义多个doFirst 和doLast (doFirst 方法会在已有task之前添加逻辑,doLast 会在已有方法之后添加逻辑),执行的顺序就是代码中的顺序。doFirst 和doLast 也可以写在代码块外面。
this.tasks.create(name: 'helloTask') {
setGroup('com.it.utool')
setDescription('task study')
}
helloTask.doFirst {
}
例:计算build时常:
def buildStartTime=0,buildEndTime=0
this.afterEvaluate { Project project ->
def preBuildTask = project.tasks.getByName('preBuild')
preBuildTask.doFirst {
buildStartTime = System.currentTimeMillis()
println 'the start time is: ' + buildStartTime
}
def buildTask = project.tasks.getByName('build')
buildTask.doLast {
buildEndTime = System.currentTimeMillis()
println "the build time is : ${(buildEndTime - buildStartTime) / 1000}s"
}
}
以上代码发现,通过doFirst 、doLast 的合理使用可以在已有Task的基础上添加自己的逻辑。
3.3、Task依赖和执行顺序
task有三种方式可以指定执行顺序:
Task执行顺序
dependsOn强依赖方式
通过Task输入输出指定
通过Api指定执行顺序
3.3.1. dependsOn强依赖方式
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
task taskZ(dependsOn: [taskX,taskY]) {
doLast {
println 'taskZ'
}
}
输入:gradlew taskZ
>Task :taskX taskX >Task :taskY taskY >Task :taskZ taskZ
通过以上taskZ的执行结果可以发现,taskZ依赖于taskX和taskY,并且在taskX和taskY都执行后在执行(taskX和taskY没有依赖关系,所以执行顺序是随机的)。
使用Task类的denpendsOn()方法也可以为task指定依赖。
taskZ.dependsOn(taskX,taskY)
task还可以在配置阶段动态指定依赖。
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task onlib {
doLast {
println 'nolib'
}
}
task taskZ {
dependsOn this.tasks.findAll {task ->
task.name.startsWith('lib')
}
doLast {
println 'taskZ'
}
}
输入:gradlew taskZ 后观察输出结果发现,taskZ依赖于lib1和lib2。
3.3.2 通过Task输入输出指定执行顺序
例:文件的写入和读取
class VersionMessage {
String versionName
String versionCode
String versionInfo
}
ext {
versionName = '1.0.0'
versionCode = '100'
versionInfo = 'this is the first version, add some base info'
destFile = file('release.xml')
}
定义一个task写入文件。
task writeTask {
inputs.property('versionName', this.versionName)
inputs.property('versionCode', this.versionCode)
inputs.property('versionInfo', this.versionInfo)
if (this.destFile != null && !this.destFile.exists()) {
this.destFile.createNewFile()
}
outputs.file this.destFile
doLast {
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()
def message = new VersionMessage(data)
def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw)
if (file.text == null || file.text.size() <= 0) {
xmlBuilder.releases {
release {
versionCode(message.versionCode)
versionName(message.versionName)
versionInfo(message.versionInfo)
}
}
file.withWriter {writer -> writer.append(sw.toString())}
println 123
} else {
xmlBuilder.release {
versionCode(message.versionCode)
versionName(message.versionName)
versionInfo(message.versionInfo)
}
def lines = file.readLines()
def length = lines.size() - 1
file.withWriter {writer ->
lines.eachWithIndex{ String line, int index ->
if(index != length) {
writer.append(line + '\r\n')
}else if(index == length) {
writer.append('\r\r\n' + sw.toString() + '\r\n')
writer.append(lines.get(length))
}
}
}
}
}
}
定义一个task读取文件。
task readTask {
inputs.file this.destFile
doLast {
def file = inputs.files.singleFile
println file.text
}
}
以上代码看来两个task没有任何的依赖关系,但是二者都引用了destFile 这个属性,destFile 对writeTask来说是输出文件,对readTask来说时输入文件,所以这两个task通过destFile 这个属性关联了起来。gradle规定输出属性对应的task会先被执行,所以如果writeTask和readTask同时执行的话,writeTask一定会先执行。
新建一个testTask并依赖于writeTask和readTask。
task testTask {
dependsOn writeTask,readTask
doLast {
println 'test task !!! '
}
}
执行testTask 发现writeTask 会先执行。
3.3.3. 通过Api指定执行顺序
mustRunAfter 可以使task在指定task执行后执行,mustRunAfter 可以指定一个task,也可以指定一个task数组。
定义3个task: taskX、taskY、taskZ,并让按照taskX->taskY->taskZ的顺序执行。
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
mustRunAfter taskX
doLast {
println 'taskY'
}
}
task taskZ {
mustRunAfter taskY
doLast {
println 'taskZ'
}
}
打乱执行顺序,在控制台输入:gradlew taskY taskZ taskX
>Task :taskX taskX
>Task :taskY taskY
>Task :taskZ taskZ
发现任务按照taskX->taskY->taskZ的顺序执行。
shouldRunAfter的用法与mustRunAfter完全相同,区别在于shouldRunAfter(仅仅是建议)不强制指定执行顺序。
3.4、Task挂接到生命周期
将自定义task挂接到gradle生命周期只需要在生命周期task执行前后执行自定义task即可。
可以让生命周期task依赖于自定义task,这样自定义task就可以在生命周期task之前执行。
// 自定义一个task
task selfTask {
doLast {
println 'self task...'
}
}
this.project.afterEvaluate { project ->
def buildTask = tasks.findByName('clean')
if (buildTask == null) {
throw new GradleException('build task not find...')
}
buildTask.dependsOn selfTask // clean之前执行selfTask
}
finizedBy 可以指定当前task之后执行的task。
this.project.afterEvaluate { project ->
def buildTask = tasks.findByName('clean')
if (buildTask == null) {
throw new GradleException('build task not find...')
}
buildTask.finalizedBy selfTask
}
4、其他模块
4.1、settings.gradle文件
gradle项目中的settings.gradle文件实际上是通过配置代码来配置Setting类,而Settings类的作用则是在初始化时配置项目。 Settings类的主要方法为include ,将我们新建的子工程引入进来,这样gradle才会把它当做一个工程去处理。
4.2、SourceSet类
SourceSet类负责管理项目中所有的代码、资源、第三方库存放的位置。gradle约定源码存放在java文件夹下,资源存放在resources文件夹下(gradle项目结构与maven相似,但是maven项目结构不可以修改,gradle提供了一系列的DSL,可以让你方便的定义或者修改配置)。
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
4.3、自定义Plugin
将完成某一特定功能的task都封装在一起就形成了插件,这样项目只要引入了插件,就能调用特定的功能。
gradle插件工程和普通gradle工程目录结构区别不大,在main文件夹下有一个groovy 文件夹用于存放groovy类,有一个resources文件夹用于存放配置文件。
插件的build.gradle文件与普通项目不同。
apply plugin: 'groovy'
dependencies {
implementation gradleApi()
implementation localGroovy()
}
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
resources {
srcDir 'src/main/resources'
}
}
}
在groovy中创建一个com.it.study 的包并里面创建一个名为GradleStudyPlugin的groovy类。
package com.it.study
import org.gradle.api.*
class GradleStudyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println 'Hello Plugin : ' + project.name
}
}
这样就完成了一个简单的自定义插件,如果想要让其他项目或模块引入此插件,则需要声明引入方式。
在resources 文件夹下新建META-INF/gradle-plugins 文件夹,并在里面新建com.it.study.properties 文件,并输入:
implementation-class=com.it.study.GradleStudyPlugin
这样做就声明了自定义插件的插件名为com.it.study 。
|