對于初學(xué)者來說,面對各種各樣的Gradle構(gòu)建腳本,想要梳理它的構(gòu)建流程,往往不知道從何入手。Gradle的構(gòu)建過程有著固定的生命周期,理解Gradle的生命周期和Hook點,有助于幫你梳理、擴(kuò)展項目的構(gòu)建流程。
構(gòu)建的生命周期
任何Gradle的構(gòu)建過程都分為三部分:初始化階段、配置階段和執(zhí)行階段。
初始化階段
初始化階段的任務(wù)是創(chuàng)建項目的層次結(jié)構(gòu),并且為每一個項目創(chuàng)建一個Project實例。
與初始化階段相關(guān)的腳本文件是settings.gradle(包括<USER_HOME>/.gradle/init.d目錄下的所有.gradle腳本文件,這些文件作用于本機(jī)的所有構(gòu)建過程)。一個settings.gradle腳本對應(yīng)一個Settings對象,我們最常用來聲明項目的層次結(jié)構(gòu)的include就是Settings類下的一個方法,在Gradle初始化的時候會構(gòu)造一個Settings實例對象,它包含了下圖中的方法,這些方法都可以直接在settings.gradle中直接訪問。

比如可以通過如下代碼向Gradle的構(gòu)建過程添加監(jiān)聽:
gradle.addBuildListener(new BuildListener() {
void buildStarted(Gradle var1) {
println '開始構(gòu)建'
}
void settingsEvaluated(Settings var1) {
println 'settings評估完成(settins.gradle中代碼執(zhí)行完畢)'
// var1.gradle.rootProject 這里訪問Project對象時會報錯,還未完成Project的初始化
}
void projectsLoaded(Gradle var1) {
println '項目結(jié)構(gòu)加載完成(初始化階段結(jié)束)'
println '初始化結(jié)束,可訪問根項目:' + var1.gradle.rootProject
}
void projectsEvaluated(Gradle var1) {
println '所有項目評估完成(配置階段結(jié)束)'
}
void buildFinished(BuildResult var1) {
println '構(gòu)建結(jié)束 '
}
})
執(zhí)行gradle build,打印結(jié)果如下:
settings評估完成(settins.gradle中代碼執(zhí)行完畢)
項目結(jié)構(gòu)加載完成(初始化階段結(jié)束)
初始化結(jié)束,可訪問根項目:root project 'GradleTest'
所有項目評估完成(配置階段結(jié)束)
:buildEnvironment
------------------------------------------------------------
Root project
------------------------------------------------------------
classpath
No dependencies
BUILD SUCCESSFUL
Total time: 0.959 secs
構(gòu)建結(jié)束
配置階段
配置階段的任務(wù)是執(zhí)行各項目下的build.gradle腳本,完成Project的配置,并且構(gòu)造Task任務(wù)依賴關(guān)系圖以便在執(zhí)行階段按照依賴關(guān)系執(zhí)行Task。
該階段也是我們最常接觸到的構(gòu)建階段,比如應(yīng)用外部構(gòu)建插件apply plugin: 'com.android.application',配置插件的屬性android{ compileSdkVersion 25 ...}等。每個build.gralde腳本文件對應(yīng)一個Project對象,在初始化階段創(chuàng)建,Project的接口文檔。
配置階段執(zhí)行的代碼包括build.gralde中的各種語句、閉包以及Task中的配置段語句,在根目錄的build.gradle中添加如下代碼:
println 'build.gradle的配置階段'
// 調(diào)用Project的dependencies(Closure c)聲明項目依賴
dependencies {
// 閉包中執(zhí)行的代碼
println 'dependencies中執(zhí)行的代碼'
}
// 創(chuàng)建一個Task
task test() {
println 'Task中的配置代碼'
// 定義一個閉包
def a = {
println 'Task中的配置代碼2'
}
// 執(zhí)行閉包
a()
doFirst {
println '這段代碼配置階段不執(zhí)行'
}
}
println '我是順序執(zhí)行的'
調(diào)用gradle build,得到如下結(jié)果:
build.gradle的配置階段
dependencies中執(zhí)行的代碼
Task中的配置代碼
Task中的配置代碼2
我是順序執(zhí)行的
:buildEnvironment
------------------------------------------------------------
Root project
------------------------------------------------------------
classpath
No dependencies
BUILD SUCCESSFUL
Total time: 1.144 secs
一定要注意,配置階段不僅執(zhí)行build.gradle中的語句,還包括了Task中的配置語句。從上面執(zhí)行結(jié)果中可以看到,在執(zhí)行了dependencies的閉包后,直接執(zhí)行的是任務(wù)test中的配置段代碼(Task中除了Action外的代碼段都在配置階段執(zhí)行)。
另外一點,無論執(zhí)行Gradle的任何命令,初始化階段和配置階段的代碼都會被執(zhí)行。
同樣是上面那段Gradle腳本,我們執(zhí)行幫助任務(wù)gradle help,任然會打印出上面的執(zhí)行結(jié)果。我們在排查構(gòu)建速度問題的時候可以留意,是否部分代碼可以寫成任務(wù)Task,從而減少配置階段消耗的時間。
執(zhí)行階段
在配置階段結(jié)束后,Gradle會根據(jù)任務(wù)Task的依賴關(guān)系創(chuàng)建一個有向無環(huán)圖,可以通過Gradle對象的getTaskGraph方法訪問,對應(yīng)的類為TaskExecutionGraph,然后通過調(diào)用gradle <任務(wù)名>執(zhí)行對應(yīng)任務(wù)。
下面我們展示如何調(diào)用子項目中的任務(wù)。
- 在根目錄下創(chuàng)建目錄subproject,并添加文件build.gradle
- 在settings.gradle中添加
include ':subproject' - 在subproject的build.gradle中添加如下代碼
task grandpa {
doFirst {
println 'task grandpa:doFirst 先于 doLast 執(zhí)行'
}
doLast {
println 'task grandpa:doLast'
}
}
task father(dependsOn: grandpa) {
doLast {
println 'task father:doLast'
}
}
task mother << {
println 'task mother 先于 task father 執(zhí)行'
}
task child(dependsOn: [father, mother]){
doLast {
println 'task child 最后執(zhí)行'
}
}
task nobody {
doLast {
println '我不執(zhí)行'
}
}
// 指定任務(wù)father必須在任務(wù)mother之后執(zhí)行
father.mustRunAfter mother
它們的依賴關(guān)系如下:
:subproject:child
+--- :subproject:father
| \--- :subproject:grandpa
\--- :subproject:mother
執(zhí)行gradle :subproject:child,得到如下打印結(jié)果:
:subproject:mother
task mother 先于 task father 執(zhí)行
:subproject:grandpa
task grandpa:doFirst 先于 doLast 執(zhí)行
task grandpa:doLast
:subproject:father
task father:doLast
:subproject:child
task child 最后執(zhí)行
BUILD SUCCESSFUL
Total time: 1.005 secs
因為在配置階段,我們聲明了任務(wù)mother的優(yōu)先級高于任務(wù)father,所以mother先于father執(zhí)行,而任務(wù)father依賴于任務(wù)grandpa,所以grandpa先于father執(zhí)行。任務(wù)nobody不存在于child的依賴關(guān)系中,所以不執(zhí)行。
Hook點
Gradle提供了非常多的鉤子供開發(fā)人員修改構(gòu)建過程中的行為,為了方便說明,先看下面這張圖。

Gradle在構(gòu)建的各個階段都提供了很多回調(diào),我們在添加對應(yīng)監(jiān)聽時要注意,監(jiān)聽器一定要在回調(diào)的生命周期之前添加,比如我們在根項目的build.gradle中添加下面的代碼就是錯誤的:
gradle.settingsEvaluated { setting ->
// do something with setting
}
gradle.projectsLoaded {
gradle.rootProject.afterEvaluate {
println 'rootProject evaluated'
}
}
當(dāng)構(gòu)建走到build.gradle時說明初始化過程已經(jīng)結(jié)束了,所以上面的回調(diào)都不會執(zhí)行,把上述代碼移動到settings.gradle中就正確了。
下面通過一些例子來解釋如何Hook Gradle的構(gòu)建過程。
-
為所有子項目添加公共代碼
在根項目的build.gradle中添加如下代碼:
gradle.beforeProject { project ->
println 'apply plugin java for ' + project
project.apply plugin: 'java'
}
這段代碼的作用是為所有子項目應(yīng)用Java插件,因為代碼是在根項目的配置階段執(zhí)行的,所以并不會應(yīng)用到根項目中。
這里說明一下Gradle的beforeProject方法和Project的beforeEvaluate的執(zhí)行時機(jī)是一樣的,只是beforeProject應(yīng)用于所有項目,而beforeEvaluate只應(yīng)用于調(diào)用的Project,上面的代碼等價于:
allprojects {
beforeEvaluate { project ->
println 'apply plugin java for ' + project
project.apply plugin: 'java'
}
}
after***也是同理的,但afterProject還有一點不一樣,無論Project的配置過程是否出錯,afterProject都會收到回調(diào)。
-
為指定Task動態(tài)添加Action
gradle.taskGraph.beforeTask { task ->
task << {
println '動態(tài)添加的Action'
}
}
task Test {
doLast {
println '原始Action'
}
}
在任務(wù)Test執(zhí)行前,動態(tài)添加了一個doLast動作。
-
獲取構(gòu)建各階段耗時情況
long beginOfSetting = System.currentTimeMillis()
gradle.projectsLoaded {
println '初始化階段,耗時:' + (System.currentTimeMillis() - beginOfSetting) + 'ms'
}
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
gradle.beforeProject { project ->
if (!configHasBegin) {
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project, System.currentTimeMillis())
}
gradle.afterProject { project ->
def begin = beginOfProjectConfig.get(project)
println '配置階段,' + project + '耗時:' + (System.currentTimeMillis() - begin) + 'ms'
}
def beginOfProjectExcute
gradle.taskGraph.whenReady {
println '配置階段,總共耗時:' + (System.currentTimeMillis() - beginOfConfig) + 'ms'
beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println '執(zhí)行階段,' + task + '耗時:' + (System.currentTimeMillis() - task.beginOfTask) + 'ms'
}
}
gradle.buildFinished {
println '執(zhí)行階段,耗時:' + (System.currentTimeMillis() - beginOfProjectExcute) + 'ms'
}
將上述代碼段添加到settings.gradle腳本文件的開頭,再執(zhí)行任意構(gòu)建任務(wù),你就可以看到各階段、各任務(wù)的耗時情況。
-
動態(tài)改變Task依賴關(guān)系
有時我們需要在一個已有的構(gòu)建系統(tǒng)中插入我們自己的構(gòu)建任務(wù),比如在執(zhí)行Java構(gòu)建后我們想要刪除構(gòu)建過程中產(chǎn)生的臨時文件,那么我們就可以自定義一個名叫cleanTemp的任務(wù),讓其依賴于build任務(wù),然后調(diào)用cleanTemp任務(wù)即可。
但是這種方式適用范圍太小,比如在使用IDE執(zhí)行構(gòu)建時,IDE默認(rèn)就是調(diào)用build任務(wù),我們沒法修改IDE的行為,所以我們需要將自定義的任務(wù)插入到原有的任務(wù)關(guān)系中。
-
尋找插入點
如果你對一個構(gòu)建的任務(wù)依賴關(guān)系不熟悉的話,可以使用一個插件來查看,在根項目的build.gradle中添加如下代碼:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.2.2"
}
}
apply plugin: "com.dorongold.task-tree"
然后執(zhí)行gradle <任務(wù)名> taskTree --no-repeat,即可看到指定Task的依賴關(guān)系,比如在Java構(gòu)建中查看build任務(wù)的依賴關(guān)系:
:build
+--- :assemble
| \--- :jar
| \--- :classes
| +--- :compileJava
| \--- :processResources
\--- :check
\--- :test
+--- :classes *
\--- :testClasses
+--- :compileTestJava
| \--- :classes *
\--- :processTestResources
我們看到build主要執(zhí)行了assemble包裝任務(wù)和check測試任務(wù),那么我們可以將我們自定義的cleanTemp插入到build和assemble之間。
-
動態(tài)插入自定義任務(wù)
我們先定義一個自定的任務(wù)cleanTemp,讓其依賴于assemble。
task cleanTemp(dependsOn: assemble) {
doLast {
println '清除所有臨時文件'
}
}
接著,我們將cleanTemp添加到build的依賴項中。
afterEvaluate {
build.dependsOn cleanTemp
}
注意,dependsOn方法只是添加一個依賴項,并不清除之前的依賴項,所以現(xiàn)在的依賴關(guān)系如下:
:build
+--- :assemble
| \--- :jar
| \--- :classes
| +--- :compileJava
| \--- :processResources
+--- :check
| \--- :test
| +--- :classes
| | +--- :compileJava
| | \--- :processResources
| \--- :testClasses
| +--- :compileTestJava
| | \--- :classes
| | +--- :compileJava
| | \--- :processResources
| \--- :processTestResources
\--- :cleanTemp
\--- :assemble
\--- :jar
\--- :classes
+--- :compileJava
\--- :processResources
可以看到,cleanTemp依賴于assemble,同時build任務(wù)多了一個依賴,而build和assemble原有的依賴關(guān)系并沒有改變,執(zhí)行gradle build后任務(wù)調(diào)用結(jié)果如下:
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:cleanTemp
清除所有臨時文件
:build
BUILD SUCCESSFUL
結(jié)語
理解Gradle構(gòu)建的生命周期是學(xué)習(xí)Gradle構(gòu)建系統(tǒng)的基礎(chǔ),對于梳理構(gòu)建系統(tǒng)執(zhí)行流程以及編寫自己的構(gòu)建流程都是非常有幫助的,希望這篇文章能夠幫助到迷茫的初學(xué)者。