本文內容大部分來自 Gradle 官方文檔,英文 OK 的同窗能夠直接看官方文檔。 本文示例代碼已放在 zjxstar 的 GitHub。html
上一篇文章中,咱們學習了 Gradle 中 Project 的相關知識,也提到了簡單 Task 的定義。一個 Project 裏包含了多個 Task(任務),Gradle 的一系列操做都歸功於 Task。本文將深刻學習 Task 的相關知識,掌握如何建立、訪問和配置一個 Task。java
Task(任務)表示構建過程當中的單個原子工做,例如編譯類或生成 javadoc。每一個任務都屬於某個 Project,在 Project 中能夠經過任務名或者 TaskContainer 來訪問任務。每一個任務都有一個徹底限定的路徑,該路徑在全部項目的全部任務中都是惟一的。路徑是所在項目路徑和任務名稱的串聯,使用 :
字符分隔。git
當 Gradle 執行一個任務時,它能夠在控制檯 UI 和 Tooling API 中使用不一樣的結果標記任務。這些標籤基於任務是否具備要執行的操做,是否應該執行這些操做,是否執行了這些操做以及這些操做是否進行了任何更改。github
主要有如下 5 種標記:(在運行 Task 時增長 -i 或者 --info 便可查看)api
在 Gradle 中,咱們有多種方式來建立任務。主要用到 Project 提供的 task
方法以及 TaskContainer ( tasks
) 提供的 create 方法。緩存
方式一:使用字符串做爲任務名建立 Task,示例:gradlew -q helloA閉包
task('helloA') {
doLast {
println("helloA task(name)")
}
}
複製代碼
方式二:使用 tasks 的 create 方法,示例:gradlew -q helloBapp
tasks.create('helloB') {
doFirst {
println("helloB tasks.create(name)")
}
}
複製代碼
方式三:使用 DSL 的特殊語法,示例:gradlew -q helloCide
task helloC {
doLast {
println("helloC task name")
}
}
// 執行 gradlew -q helloD 命令運行
task(helloD) {
doLast {
println("helloD task(name)")
}
}
複製代碼
在建立 Task 時,能夠傳入一個 Map 來簡單配置任務,經常使用配置項有:學習
配置項 | 描述 | 默認值 |
---|---|---|
type | 基於一個存在的 Task 來建立,相似於繼承 | DefaultTask |
overwrite | 是否替換已經存在的 Task,和 type 配合使用 | false |
dependsOn | 用於配置任務依賴 | [] |
action | 添加到任務中的一個 Action 或者一個閉包 | null |
description | 用於配置任務的描述 | null |
group | 用於配置任務的分組 | null |
使用示例:
// 使用 Map 增長配置項
task copy(type: Copy)
// 覆蓋了原 copy 任務
task copy(overwrite: true) {
doLast {
println('I am the new one.')
}
}
// 使用 gradlew tasks 命令查看 helloE 的配置
task helloE {
description 'i am helloE'
group BasePlugin.BUILD_GROUP
doLast {
println('this is helloE')
}
}
複製代碼
以上是建立一個 Task 的基本方法。但在 build.gradle 腳本中,咱們能夠利用 Groovy 語言的強大特性來動態建立多個任務。例如:gradlew -q task1
// 同時建立4個任務:task0、task一、task二、task3
4.times { counter ->
task "task$counter" {
doLast {
println "I'm task number $counter"
}
}
}
複製代碼
當建立完 Task 以後,咱們能夠訪問它們以進行配置或者依賴。那麼怎麼訪問一個已經定義好的 Task 呢?主要有三種方法。
方式一:使用 Groovy 中 DSL 特殊語法訪問。
// 承接上文示例
// 以helloE任務爲例
println '任務helloE的name: ' + helloE.name
println '任務helloE的description: ' + project.helloE.description
複製代碼
方式二:使用 tasks 訪問任務集。
// 利用tasks
println tasks.named('helloD').get().name
println tasks.copy.doLast {
println 'configure by tasks.copy.doLast'
}
println tasks['helloC'].name
println tasks.getByName('helloB').name
複製代碼
方式三:經過路徑訪問任務
println tasks.getByPath('helloE').path // 找不到拋異常UnknownTaskException
println tasks.getByPath(':app:helloE').path
def ehelloE = tasks.findByPath("EhelloE") // 找不到返回null;
println ehelloE == null
複製代碼
經過路徑查找任務有兩種方法,一種是 get,另外一種是 find 。它們的區別在於 get 方法若是找不到指定任務就會拋出 UnknownTaskException 異常,而 find 方法則會返回 null 。
既然可以訪問到 Task,那就能夠對 Task 進行一些操做了,而這些操做又涉及到 Task 的屬性和方法,這裏簡單介紹下。
Task 的屬性範圍有四個。你能夠經過屬性名或者 Task.property( java.lang.String )
方法訪問到指定屬性。也能夠經過 Task.setProperty( java.lang.String, java.lang.Object )
方法修改屬性值。四個範圍以下:
名稱 -> 值
對,可用於動態地向任務對象添加屬性。此範圍的屬性是可讀寫的。經常使用屬性有:
屬性名 | 描述 |
---|---|
actions | 該任務將要執行的一系列動做 |
dependsOn | 返回該任務依賴的任務 |
description | 任務的描述 |
enabled | 該任務是否開啓 |
finalizedBy | 返回完成此任務以後的任務 |
group | 任務的分組 |
mustRunAfter | 返回該任務必須在哪一個任務以後運行的任務 |
name | 任務的名字 |
path | 任務的路徑 |
project | 任務所屬的 Project |
咱們舉個額外屬性的例子:
// 定義 Task 的額外屬性
task myTask {
ext.myProperty = "myValue"
}
// 訪問該屬性
task printTaskProperties {
doLast {
println myTask.myProperty
}
}
複製代碼
其餘的屬性會在下面的小節中詳細介紹。
至於 Task 的方法,這裏只簡單列舉出來:
方法名(不列出參數) | 描述 |
---|---|
dependsOn | 給任務設置依賴任務 |
doFirst | 給 Task 添加一個任務動做開始執行以前的動做 |
doLast | 給 Task 添加一個任務動做執行結束以後的動做 |
finalizedBy | 給任務添加終結任務,即該任務結束後執行的任務 |
hasProperty | 判斷該任務是否有指定屬性 |
mustRunAfter | 聲明該任務必須在某些任務以後執行 |
onlyIf | 給任務添加斷言,只有知足條件才能夠執行任務 |
property | 返回指定屬性的值 |
setProperty | 修改指定屬性的值 |
前面的例子中常常看見 doFirst 和 doLast,下文中會對它們作詳細的介紹。
Gradle 其實還給咱們提供一個 DefaultTask 基類,經過繼承它能夠用來自定義任務,多用在自定義 Gradle 插件中。這裏簡單寫個示例,來講明 DefaultTask 的用法。
// app的build.gradle
class CustomTask extends DefaultTask {
final String message
final int number
def content // 配置參數
// 添加構造參數
@Inject
CustomTask(String message, int number) {
this.message = message
this.number = number
}
// 添加要執行動做
@TaskAction
def greet() {
println content
println "message is $message , number is $number !"
}
}
// 使用tasks建立
// 須要傳遞兩個參數,不能爲null
tasks.create('myCustomTask1', CustomTask, 'hahaha', 110)
myCustomTask1.content = 'i love you'
myCustomTask1.doFirst {
println 'my custom task do first'
}
myCustomTask1.doLast {
println 'my custom task do last'
}
// 使用task建立,構造參數使用constructorArgs傳遞,參數不能爲null
task myCustomTask2(type: CustomTask, constructorArgs: ['xixi', 120])
myCustomTask2.content = 'i hate you'
複製代碼
示例中的 CustomTask 類是在 build.gradle 文件中定義的,直接繼承 DefaultTask 便可。若是但願該 Task 有可執行的動做,就須要在動做方法上添加 @TaskAction
的註解,這樣 Gradle 就會將該動做添加到 Task 的動做列表中。咱們能夠在類中爲任務配置屬性,如示例中的 content 、message 、number 。其中,message 和 number 經過 @javax.inject.Inject
註解設置成了構造參數,在建立該自定義任務時須要傳遞這兩個參數。
講到這裏,咱們已經瞭解了 Task 的建立與使用,那麼如今有必要對 Task 的執行作一個大概的分析,這對咱們深刻理解 Task 有很大幫助。
當咱們執行一個 Task 的時候,實際上是執行其擁有的 actions 列表,這個列表保存在 Task 對象實例的 actions 成員變量中,其類型是一個 List:
private List<ContextAwareTaskAction> actions;
複製代碼
那麼怎麼將執行動做加入到該列表中呢?在前文的示例代碼中,你們應該注意到建立 Task 的時候用到了 doFirst 和 doLast 兩個方法。沒錯,這兩個方法就能夠將待執行的動做添加到 actions 列表中。
咱們粗略看下 doFirst 和 doLast 的源碼實現:(這兩個方法在類 org.gradle.api.internal.AbstractTask 中)
@Override
public Task doFirst(final String actionName, final Action<? super Task> action) {
...
taskMutator.mutate("Task.doFirst(Action)", new Runnable() {
public void run() {
// 每次都添加到列表頭
getTaskActions().add(0, wrap(action, actionName));
}
});
return this;
}
@Override
public Task doLast(final String actionName, final Action<? super Task> action) {
...
taskMutator.mutate("Task.doLast(Action)", new Runnable() {
public void run() {
// 每次都添加到表尾
getTaskActions().add(wrap(action, actionName));
}
});
return this;
}
複製代碼
能夠看到,doFirst 會把動做添加到表頭,而 doLast 則會把動做添加到表尾。
那怎麼把動做添加到列表中間呢?是否是直接在 Task 裏寫動做?咱們能夠試一下:
// 經過 gradlew -q greetC 執行
task greetC { // 能夠顯示聲明 doFirst doLast
// 在配置階段執行
println 'i am greet C, in configure'
// 在執行階段執行
doFirst {
println 'i am greet C, in doFirst'
}
// 在執行階段執行
doLast {
println 'i am greet C, in doLast'
}
}
複製代碼
執行該任務後,你會發現 " i am greet C, in configure " 這句話沒有在 doFirst 和 doLast 中間打印,而是打印在任務的配置階段。說明直接在 Task 裏寫的動做並不會添加到 Task 的動做列表中,只會當作 Task 的配置信息執行。那有沒有其餘辦法呢?
答案是確定的,這就得利用到上小節中提到的 DefaultTask 類了。咱們用 @TaskAction
註解標記的動做就會被添加到 Task 的動做列表中間。咱們直接執行上節中的 myCustomTask1 任務 ( gradlew -q myCustomTask1 ) 。結果以下:
my custom task do first // doFirst
i love you // @TaskAction
message is hahaha , number is 110 ! // @TaskAction
my custom task do last // doLast
複製代碼
這樣,Task 的執行順序基本就清晰了。
這裏須要提一個注意點,咱們在建立任務的時候,若是隻想給任務配置一個 doFirst 的動做,可使用左移符號 <<
來表示。
task greetB << { // << 等價於 doFirst
println 'i am greet B, in doFirst'
// 不能繼續配置doLast
// doLast {
// println 'i am greet B, in doLast'
// }
}
// 只能單獨配置
greetB.doLast {
println 'i am greet B, in doLast'
}
複製代碼
左移符號就等價於 doFirst ,並且,此時不能再給該任務配置 doLast 動做了,只能單獨進行配置。
咱們知道,一個 Project 擁有多個 Task,這些 Task 之間的關係由 DAG 圖維護。而 DAG 圖是在構建的配置過程當中生成的,咱們能夠經過 gradle.taskGraph
來監聽這個過程。
舉例:這個方法經常使用來給某些變量賦不一樣的值。( gradlew -q greetD )
def versionD = '0.0'
task greetD {
doLast {
println "i am greet D, versionD is $versionD"
}
}
// task的DAG圖是在配置階段生成的
// 任務準備好了
gradle.taskGraph.whenReady {taskGraph ->
if (taskGraph.hasTask('greetC')) {
versionD = '1.0'
} else {
versionD = '1.0-alpha'
}
}
// 任務執行前
gradle.taskGraph.beforeTask { Task task ->
println "executing $task ..."
}
// 任務執行後
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (state.failure) {
println "FAILED"
} else {
println "$task done"
}
}
複製代碼
咱們能夠經過監聽 DAG 圖的回調來對特定的 Task 進行定製化處理。
既然任務是經過 DAG 圖維護的,那任務之間確定存在依賴和前後執行順序,咱們在定義任務的時候是否也能夠給任務添加依賴或者執行順序呢?這就得利用到任務的 dependsOn 、mustRunAfter 等方法了。
dependsOn :給某個任務設置依賴任務。
task dependsA { // 定義一個基礎Task
doLast {
println 'i am depends A task'
}
}
// 當執行B時,會先執行它的依賴任務A
task dependsB {
dependsOn dependsA // 經過方法設置
doLast {
println 'i am depends B task'
}
}
// 經過Map參數依賴任務A
task dependsC(dependsOn: dependsA) {
doLast {
println 'i am depends C task'
}
}
// 任務D懶依賴任務E
// 任務E後定義
task dependsD {
dependsOn 'dependsE'
doLast {
println 'i am depends D task'
}
}
task dependsE {
doLast {
println 'i am depends E task'
}
}
task dependsF {
doLast {
println 'i am depends F task'
}
}
// 經過dependsOn方法同時依賴兩個任務E和A
dependsF.dependsOn dependsE, dependsA
複製代碼
從示例中能夠看到,經過 dependsOn 設置任務的依賴關係後,當執行任務時,其依賴的任務會先完成執行。並且,能夠給某個任務同時設置多個依賴任務;也能夠進行懶依賴,即依賴那些尚未定義的任務。
finalizedBy :給某個任務設置終結任務。
task taskX { // 定義任務X
doLast {
println 'i am task X'
}
}
task taskY { // 定義任務Y
doLast {
println 'i am task Y'
}
}
task taskZ { // 定義任務Z
doLast {
println 'i am task Z'
}
}
// 任務X執行後,馬上執行任務Y和任務Z
taskX.finalizedBy taskY, taskZ
task taskM { // 定義任務M
doLast {
println 'i am task M'
}
}
task taskN {
finalizedBy taskM // 將任務M設置成任務N的終結任務
doLast {
println 'i am task N'
}
}
複製代碼
對任務進行 finalizedBy 配置和 dependsOn 很相似,其做用和 dependsOn 剛好相反。在某任務執行完後,會執行其設置的終結任務。
mustRunAfter :若是 taskB.mustRunAfter(taskA) 則表示 taskB 必須在 taskA 執行以後再執行,這個規則比較嚴格。
task taskA {
doLast {
println 'i am task A'
}
}
task taskB {
doLast {
println 'i am task B'
}
}
// 任務A必須在任務B以後執行
taskA.mustRunAfter taskB
複製代碼
運行命令 gradlew taskA taskB ,你會發現 taskB 會先執行。
shouldRunAfter :若是 taskB.shouldRunAfter(taskA) 則表示 taskB 應該在 taskA 以後執行,但這不是必須的,任務可能不會按預設的順序執行。
task shouldTaskC << {
println 'i am task C'
}
task shouldTaskD << {
println 'i am task D'
}
shouldTaskC.shouldRunAfter shouldTaskD
複製代碼
運行命令 gradlew shouldTaskC shouldTaskD 查看執行結果。
這裏舉個 shouldRunAfter 失效的例子:
task shouldTaskX {
doLast {
println 'taskX'
}
}
task shouldTaskY {
doLast {
println 'taskY'
}
}
task shouldTaskZ {
doLast {
println 'taskZ'
}
}
shouldTaskX.dependsOn shouldTaskY
shouldTaskY.dependsOn shouldTaskZ
shouldTaskZ.shouldRunAfter shouldTaskX // 這裏實際上是失效的
複製代碼
運行命令 gradlew -q shouldTaskX 會發現任務 Z 會先於任務 X 執行。
這裏再提一個 tasks 的 whenTaskAdded 方法。若是在構建過程當中有任務添加到 project ,則會觸發此回調。咱們能夠監聽這個回調來配置一些任務依賴或者修改某些變量,示例以下 ( gradlew HelloSecond )。
// 定義任務greetE
tasks.create('greetE') {
doLast {
println 'i am greetE'
}
}
// 構建過程當中添加任務時會觸發此回調,經常使用來配置一些任務依賴或者賦值
// 經測試,該回調只針對插件中的任務有效
project.tasks.whenTaskAdded { task ->
println "task ${task.name} add"
if (task.name == 'HelloSecond') { // 執行HelloSecond任務時,會先執行greetE
task.dependsOn 'greetE'
}
}
// 定義一個自定義插件
class SecondPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println 'Hello Second Gradle Plugin'
// 添加一個task到project中
project.task('HelloSecond', {
println '===== SecondPlugin HelloSecond Task ====='
logger.quiet('hello')
})
}
}
apply plugin: SecondPlugin
複製代碼
該回調只針對插件(插件的相關知識會在後續文章中給你們介紹)中定義的任務,示例中爲插件中的 HelloSecond 任務添加了一個 greetE 任務依賴。這樣,執行 HelloSecond 時會先執行 greetE 。
可能你有這樣的需求:某些任務禁止執行或者知足某個條件才能執行。Gradle 提供了多種方式來跳過任務。
方式一:每一個任務都有個 enabled 屬性,能夠啓用和禁用任務,默認是 true,表示啓用。若是設置爲 false ,則會禁止該任務執行,輸出會提示該任務被跳過,即被標記成 SKIPPED 。
// 使用gradlew disableMe運行
// 輸出:Task :app:disableMe SKIPPED
task disableMe {
doLast {
println 'This should not be printed if the task is disabled.'
}
}
disableMe.enabled = false // 禁止該任務執行
複製代碼
方式二:使用 onlyIf 判斷方法。只有當 onlyIf 裏返回 true 時該任務才能夠執行。
// 使用gradlew sayBye -PskipSayBye運行
// 這裏的-P是添加參數的意思
task sayBye {
doLast {
println 'i am sayBye task'
}
}
// 只有當project中沒有 skipSayBye 屬性時,任務才能夠執行
sayBye.onlyIf { !project.hasProperty('skipSayBye') }
複製代碼
方式三:使用 StopExecutionException 。若是任務拋出這個異常,Gradle 會跳過該任務的執行,轉而去執行下一個任務。
// 使用gradlew nextTask運行
task byeTask {
doLast {
println 'We are doing the byeTask.'
}
}
// 不會影響後續任務的執行
byeTask.doFirst {
// Here you would put arbitrary conditions in real life.
// But this is used in an integration test so we want defined behavior.
if (true) { throw new StopExecutionException() }
}
task nextTask {
dependsOn('byeTask') // 先執行byeTask,而byeTask會異常中斷
doLast { // 並不影響nextTask的執行
println 'I am not affected'
}
}
複製代碼
方式四:利用 Task 的 timeout 屬性來限制任務的執行時間。一旦任務超時,它的執行就會被中斷,任務將被標記失敗。Gradle 中內置任務都能及時響應超時。
// 故意超時
task hangingTask() {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
複製代碼
雖然有四種方法,但經常使用的仍是方法一和方法二。
若是咱們在要執行某個不存在的任務時,Gradle 會直接報異常提示找不到該任務。其實,咱們能夠經過添加規則的方式來作自定義處理。這須要利用到 TaskContainer 的 addRule 方法。
// 第一個參數是該規則的描述
// 第二個閉包參數是該規則要執行的動做
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
複製代碼
例子中添加了一個規則:若是運行的任務是以 ping 開頭的,則會建立該任務(該任務運行前並不存在),並賦予 doLast 操做。可使用 gradlew pingServer1 執行。
咱們不只能夠經過命令行來使用規則,也能夠在基於規則的任務上建立 dependsOn 關係。
// gradlew groupPing
task groupPing {
dependsOn pingServer1, pingServer2
}
複製代碼
生命週期任務是不能自行完成的任務,它們一般沒有任何執行動做。這些任務主要體如今:
除非生命週期任務具備操做,不然其結果由其依賴性決定。 若是執行任何任務的依賴項,則將認爲生命週期任務已執行。 若是全部任務的依賴項都是最新的,跳過的或來自緩存,則生命週期任務將被視爲最新的。
讀完本文內容,相信你已經學會了如何建立和使用 Task 。Task 做爲 Gradle 的主要執行骨架是很是重要的,咱們能夠經過 Task 的各類屬性、方法來靈活地配置和調整任務的依賴、執行順序以及運行規則。你們不妨多寫一些示例,有助於理解 Gradle 的工做機制,也爲後續的自定義插件奠基基礎。