天天看點

Android Gradle學習(三) Task進階學習1. Task類圖2. Task接口解析3. TaskContainer接口解析4. Task增量建構

前面通過很多範例講了在 build.gradle 中怎麼建立 Task,但是 Task 到底是個什麼東西,它裡面有些什麼,我們并不清楚,本文試圖揭開 Task 神秘的面紗。

1. Task類圖

Gradle 所說的 Task 是 org.gradle.api.Task 接口,預設實作是 org.gradle.api.DefaultTask 類,其類圖大概如下(隻節選了比較重要的部分):

Android Gradle學習(三) Task進階學習1. Task類圖2. Task接口解析3. TaskContainer接口解析4. Task增量建構

我們隻需關注 Task 接口即可,從這裡基本可以了解到 Task 有哪些特性。

2. Task接口解析

為了了解每個接口方法的含義,我們直接用代碼來測試驗證下:

class SayHelloTask extends DefaultTask {

    String msg = "default name";
    int age = 20

    @TaskAction
    void sayHello() {
        println "Hello $msg ! Age is ${age}"
    }

}

task test1 << {
    println "task test1 exec..."
}
task test2 << {
    println "task test2 exec..."
}
task test3 << {
    println "task test3 exec..."
}
task hello(type: SayHelloTask, group: "MyGroup")

//對task進行配置,
hello.configure {
    println "hello task configure"
    msg = "hjy"
}

//擷取task的名稱
println "task name is ${hello.getName()}"
//擷取task的組名
println "task group is ${hello.getGroup()}"

//設定task裡的屬性值,設定 age = 70
hello.setProperty("age", 70)
//擷取task裡的某個屬性值
println "task msg is ${hello.property('msg')}"

//設定依賴的task,隻有test1 task執行完後才會執行hello task
hello.dependsOn(test1)
//設定終結者任務,執行完hello task之後會執行test2 task,通常可以用該方法做一些清理操作
hello.finalizedBy(test2)

//如果同時執行hello、test3這2個task,會確定test3執行完之後才執行hello這個task,用這個來保證執行順序
hello.setMustRunAfter([test3])

//設定滿足某個條件後才執行該task
hello.setOnlyIf {
    //隻有當 age = 70 時,才會執行task,否則不會執行
    return hello.property("age") == 70
}
           

我們執行任務gradle hello test3,結果如下:

> Configure project :
hello task configure
task name is hello
task group is MyGroup
task msg is hjy

> Task :test3
task test3 exec...

> Task :test1
task test1 exec...

> Task :hello
Hello hjy ! Age is 70

> Task :test2
task test2 exec...
           

3. TaskContainer接口解析

TaskContianer 是用來管理所有的 Task 執行個體集合的,可以通過 Project.getTasks() 來擷取 TaskContainer 執行個體。

org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

//建立task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task

//當task被加入到TaskContainer時的監聽
whenTaskAdded(action: Closure)
           

我們先來看看建立 task 的方法:

//當有task建立時
getTasks().whenTaskAdded { Task task ->
    println "The task ${task.getName()} is added to the TaskContainer"
}

//采用create(name: String)建立
getTasks().create("task1")

//采用create(options: Map<String, ?>)建立
getTasks().create([name: "task2", group: "MyGroup", description: "這是task2描述", dependsOn: ["task1"]])

//采用create(options: Map<String, ?>, configure: Closure)建立
getTasks().create("task3", {
    group "MyGroup"
    setDependsOn(["task1", "task2"])
    setDescription "這是task3描述"
})
           

執行指令gradle -q tasks --all,檢視是否建立成功,結果如下:

MyGroup tasks
-------------
task2 - 這是task2描述
task3 - 這是task3描述

Other tasks
-----------
task1
           

我們再來試試查找 task 的方法:

//通過名字查找指定的task
def task3 = getTasks().findByName("task3")
println "findByName() return task is " + task3

def taskList = getTasks().withType(DefaultTask)
def count = 0
//周遊所有的task,列印出其名字
taskList.all { Task t ->
    println "${count++} task name is ${t.name}"
}
           

4. Task增量建構

Gradle 支援一種叫做 up-to-date 檢查的功能,也就是常說的增量建構。Gradle 的 Task 會把每次運作的結果緩存下來,當下次運作時,會檢查輸出結果有沒有變更,如果沒有變更則跳過運作,這樣可以提高 Gradle 的建構速度。

通常,一個 task 會有一些輸入(inputs)和一些輸出(outputs),task 的輸入會影響其輸出結果,以官網中的一張圖為例:

Android Gradle學習(三) Task進階學習1. Task類圖2. Task接口解析3. TaskContainer接口解析4. Task增量建構

圖中表示一個 java 編譯的 task,它的輸入有2種,一是 JDK 版本号,一是源檔案,它的輸出結果為 class 檔案,隻要 JDK 版本号與源檔案有任何變動,最終編譯出的 class 檔案肯定是不同的。當我們執行過一次編譯任務後,再次運作該 task ,如果發現它的輸入沒有任何改變,那麼它編譯後的結果肯定也是不會變化的,可以直接從緩存裡擷取輸出,這樣 Gradle 會辨別該 task 為 UP-TO-DATE,進而跳過該 task 的執行。

4.1 TaskInputs、TaskOutputs

那麼怎麼實作一個增量建構呢?一個增量建構必須至少指定一個輸入、一個輸出,從前面 Task 的類圖中可以看到,Task.getInputs() 對象類型為 TaskInputs,Task.getOutputs() 對象類型為 TaskOuputs,從中也可以看到inputs、outputs都支援哪些資料類型。同樣以一個簡單例子來說明:

task test1 {
    //設定inputs
    inputs.property("name", "hjy")
    inputs.property("age", 20)
    //設定outputs
    outputs.file("$buildDir/test.txt")

    doLast {
        println "exec task task1"
    }
}

task test2 {
    doLast {
        println "exec task task2"
    }
}
           

連續2次運作task,執行指令gradle test1 test2,結果如下:

//第一次的運作結果
> Task :test1
exec task task1

> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

//第二次的運作結果
> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date
           

從結果中可以看到,第2次運作時,test1 task 并沒有運作,而是被标記為 up-to-date,而 test2 task 則每次都會運作,這就是典型的增量建構。

4.2 taskInputs、taskOutputs注解

當你自定義 task class 時,可以通過注解來實作增量建構,這是一種更加靈活友善的方式。我們常用的注解包括:

注解名 屬性類型 描述
@Input 任意Serializable類型 一個簡單的輸入值
@InputFile File 一個輸入檔案,不是目錄
@InputDirectory File 一個輸入目錄,不是檔案
@InputFiles Iterable File清單,包含檔案和目錄
@OutputFile File 一個輸出檔案,不是目錄
@OutputDirectory File 一個輸出目錄,不是檔案
@OutputFiles Map<String, File>或Iterable 輸出檔案清單
@OutputDirectories Map<String, File>或Iterable 輸出目錄清單

我們通過自定義一個 task class 來示範一下用法:

class SayHelloTask extends DefaultTask {

    //定義輸入
    @Input
    String username;
    @Input
    int age

    //定義輸出
    @OutputDirectory
    File destDir;

    @TaskAction
    void sayHello() {
        println "Hello $username ! age is $age"
    }

}

task test(type: SayHelloTask) {
    age = 18
    username = "hjy"
    destDir = file("$buildDir/test")
}
           

執行該 task 之後,會自動生成一個 $buildDir/test 檔案目錄,當你再次運作時就實施增量建構。但是當你修改 age、username 的值,或者删除磁盤上 $buildDir/test 目錄,再次運作該 task ,該 task 就會重新運作。

相關文章

Android Gradle學習(一) Gradle基礎入門

Android Gradle學習(二) 如何建立Task

Android Gradle學習(三) Task進階學習

Android Gradle學習(四) Project詳解

Android Gradle學習(五) Extension詳解

Android Gradle學習(六) NamedDomainObjectContainer詳解

Android Gradle學習(七) Gradle建構生命周期

Android Gradle學習(八) 統計Task執行時長