欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)之Gradle?進(jìn)階Tasks深入了解

 更新時(shí)間:2022年08月24日 14:14:11   作者:程序員江同學(xué)  
這篇文章主要為大家介紹了Android開發(fā)之Gradle?進(jìn)階Tasks深入了解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

Gradle自定義Task看起來非常簡單,通過tasks.register等API就可以輕松實(shí)現(xiàn)。但實(shí)際上為了寫出高效的,可緩存的,不拖慢編譯速度的task,還需要了解更多知識(shí)。

本文主要包括以下內(nèi)容:

  • 定義Task
  • 查找Task
  • 配置Task
  • 將參數(shù)傳遞給Task構(gòu)造函數(shù)
  • Task添加依賴
  • Task排序
  • Task添加說明
  • 跳過Task
  • Task支持增量編譯
  • Finalizer Task

定義Task

如上所說,自定義Task一般可以通過register API實(shí)現(xiàn)

tasks.register("hello") {
    doLast {
        println("hello")
    }
}
tasks.register<Copy>("copy") {
    from(file("srcDir"))
    into(buildDir)
}

如果是kotlin或者kts中,也可以通過代理來實(shí)現(xiàn)

val hello by tasks.registering {
    doLast {
        println("hello")
    }
}
val copy by tasks.registering(Copy::class) {
    from(file("srcDir"))
    into(buildDir)
}

register與create的區(qū)別

除了上面介紹的register,其實(shí)create也可以用于創(chuàng)建Task,那么它們有什么區(qū)別呢?

  • 通過register創(chuàng)建時(shí),只有在這個(gè)task被需要時(shí)才會(huì)真正創(chuàng)建與配置該Task(被需要是指在本次構(gòu)建中需要執(zhí)行該Task)
  • 通過create創(chuàng)建時(shí),則會(huì)立即創(chuàng)建與配置該Task

總得來說,通過register創(chuàng)建Task性能更好,更推薦使用

查找Task

我們有時(shí)需要查找Task,比如需要配置或者依賴某個(gè)Task,我們可以通過named方法來查找對(duì)應(yīng)名字的task

tasks.register("hello")
tasks.register<Copy>("copy")
println(tasks.named("hello").get().name) // or just 'tasks.hello' if the task was added by a plugin
println(tasks.named<Copy>("copy").get().destinationDir)

也可以使用tasks.withType()方法來查找特定類型的Task

tasks.withType<Tar>().configureEach {
    enabled = false
}
tasks.register("test") {
    dependsOn(tasks.withType<Copy>())
}

除了上述方法,也可以通過tasks.getByPath()方法來查找task,不過這種方式破壞了configuration avoidance和project isolation,因此不被推薦使用

配置Task

在創(chuàng)建了Task之后,我們常常需要配置Task

我們可以在查找到Task之后進(jìn)行配置

tasks.named<Copy>("myCopy") {
    from("resources")
    into("target")
    include("**/*.txt", "**/*.xml", "**/*.properties")
}

我們還可以將Task引用存儲(chǔ)在變量中,并用于稍后在腳本中進(jìn)一步配置任務(wù)。

val myCopy by tasks.existing(Copy::class) {
    from("resources")
    into("target")
}
myCopy {
    include("**/*.txt", "**/*.xml", "**/*.properties")
}

我們也可以在定義Task時(shí)進(jìn)行配置,這也是最常用的一種

tasks.register<Copy>("copy") {
   from("resources")
   into("target")
   include("**/*.txt", "**/*.xml", "**/*.properties")
}

將參數(shù)傳遞給Task構(gòu)造函數(shù)

除了在Task創(chuàng)建后配置參數(shù),我們也可以將參數(shù)傳遞給Task的構(gòu)建函數(shù),為了實(shí)現(xiàn)這點(diǎn),我們必須使用@Inject注解

abstract class CustomTask @Inject constructor(
    private val message: String,
    private val number: Int
) : DefaultTask()

然后,我們可以創(chuàng)建一個(gè)Task,在參數(shù)列表的末尾傳遞構(gòu)造函數(shù)參數(shù)。

tasks.register<CustomTask>("myTask", "hello", 42)

需要注意的是,在任何情況下,作為構(gòu)造函數(shù)參數(shù)傳遞的值都必須是非空的。如果您嘗試傳遞一個(gè)null值,Gradle 將拋出一個(gè)NullPointerException指示哪個(gè)運(yùn)行時(shí)值是null.

Task添加依賴

有幾種方法可以定義Task的依賴關(guān)系,首先我們可以通過名稱定義依賴項(xiàng)

project("project-a") {
    tasks.register("taskX") {
        dependsOn(":project-b:taskY")
        doLast {
            println("taskX")
        }
    }
}
project("project-b") {
    tasks.register("taskY") {
        doLast {
            println("taskY")
        }
    }
}

其次我們也可以通過Task對(duì)象定義依賴項(xiàng)

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskX {
    dependsOn(taskY)
}

還有一些更高端的用法,我們可以用provider懶加載塊來定義依賴項(xiàng),在evaluated階段,provider被傳遞給正在計(jì)算依賴的task

provider塊應(yīng)返回單個(gè)對(duì)象Task或Task對(duì)象集合,然后將其視為任務(wù)的依賴項(xiàng),如下所示:taskx添加了所有以lib開頭的對(duì)象

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
// Using a Gradle Provider
taskX {
    dependsOn(provider {
        tasks.filter { task -> task.name.startsWith("lib") }
    })
}
tasks.register("lib1") {
    doLast {
        println("lib1")
    }
}tasks.register("lib2") {
    doLast {
        println("lib2")
    }
}
tasks.register("notALib") {
    doLast {
        println("notALib")
    }
}

Task排序

有時(shí)候,兩個(gè)task之間沒有依賴關(guān)系,但是對(duì)兩個(gè)task的執(zhí)行順序卻有所要求

任務(wù)排序和任務(wù)依賴之間的主要區(qū)別在于,排序規(guī)則不會(huì)影響將執(zhí)行哪些任務(wù),只會(huì)影響它們的執(zhí)行順序。

任務(wù)排序在許多場景中都很有用:

  • 強(qiáng)制執(zhí)行任務(wù)的順序:例如,build 永遠(yuǎn)不會(huì)在clean 之前運(yùn)行。
  • 在構(gòu)建的早期運(yùn)行構(gòu)建驗(yàn)證:例如,在開始發(fā)布構(gòu)建工作之前驗(yàn)證我是否擁有正確的憑據(jù)。
  • 通過在長時(shí)間驗(yàn)證任務(wù)之前運(yùn)行快速驗(yàn)證任務(wù)來更快地獲得反饋:例如,單元測試應(yīng)該在集成測試之前運(yùn)行。
  • 聚合特定類型的所有任務(wù)的結(jié)果的任務(wù):例如測試報(bào)告任務(wù)組合所有已執(zhí)行測試任務(wù)的輸出。

gradle提供了兩個(gè)可用的排序規(guī)則:mustRunAfter 和 shouldRunAfter

當(dāng)您使用mustRunAfter排序規(guī)則時(shí),您指定taskB必須始終在taskA之后運(yùn)行,這表示為taskB.mustRunAfter(taskA)

而shouldRunAfter規(guī)則理加弱化,因?yàn)樵趦煞N情況下這條規(guī)則會(huì)被忽略。一是使用這條規(guī)則會(huì)導(dǎo)致先后順序成環(huán)的情況,二是當(dāng)并行執(zhí)行task,并且任務(wù)的所有依賴關(guān)系都已經(jīng)滿足時(shí),那么無論它的shouldRunAfter依賴關(guān)系是否已經(jīng)運(yùn)行,這個(gè)任務(wù)都會(huì)運(yùn)行。

因此您應(yīng)該在排序有幫助但不是嚴(yán)格要求的情況下使用shouldRunAfter

示例如下:

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
// mustRunAfter 
taskY {
    mustRunAfter(taskX)
}
// shouldRunAfter
taskY {
    shouldRunAfter(taskX)
}

需要注意的是,B.mustRunAfter(A)或B.shouldRunAfter(A)并不意味著任務(wù)之間存在任何執(zhí)行依賴關(guān)系:

我們可以獨(dú)立執(zhí)行A或者任務(wù)B。排序規(guī)則僅在兩個(gè)任務(wù)都計(jì)劃執(zhí)行時(shí)才有效。

Task添加說明

您可以為Task添加說明。執(zhí)行時(shí)gradle tasks時(shí)會(huì)顯示此說明。

tasks.register<Copy>("copy") {
   description = "Copies the resource directory to the target directory."
   from("resources")
   into("target")
   include("**/*.txt", "**/*.xml", "**/*.properties")
}

跳過Task

gradle提供了多種方式來跳過task的執(zhí)行

使用onlyIf

你可以通過onlyIf為任務(wù)的執(zhí)行添加條件,如果任務(wù)應(yīng)該執(zhí)行,則應(yīng)該返回 true,如果應(yīng)該跳過任務(wù),則返回 false

val hello by tasks.registering {
    doLast {
        println("hello world")
    }
}
hello {
    onlyIf { !project.hasProperty("skipHello") }
}

Output of gradle hello -PskipHello
> gradle hello -PskipHello
> Task :hello SKIPPED 

如上所示,hello任務(wù)被跳過了

使用 StopExecutionException

如果跳過任務(wù)邏輯不能使用onlyIf實(shí)現(xiàn),您可以使用StopExecutionException。如果某個(gè)Action拋出此異常,則跳過該Action的進(jìn)一步執(zhí)行以及該任務(wù)的任何后續(xù)Action的執(zhí)行。構(gòu)建繼續(xù)執(zhí)行下一個(gè)任務(wù)。

val compile by tasks.registering {
    doLast {
        println("We are doing the compile.")
    }
}
compile {
    doFirst {
        // Here you would put arbitrary conditions in real life.
        if (true) {
            throw StopExecutionException()
        }
    }
}
tasks.register("myTask") {
    dependsOn(compile)
    doLast {
        println("I am not affected")
    }
}

禁用與啟用Task

每個(gè)任務(wù)都有一個(gè)enabled的標(biāo)志位,默認(rèn)為true。將其設(shè)置為false可以阻止執(zhí)行任何Task的執(zhí)行。禁用的任務(wù)將被標(biāo)記為 SKIPPED。

val disableMe by tasks.registering {
    doLast {
        println("This should not be printed if the task is disabled.")
    }
}
disableMe {
    enabled = false
}

Task超時(shí)

每個(gè)Task都有一個(gè)timeout屬性,可用于限制其執(zhí)行時(shí)間。當(dāng)一個(gè)任務(wù)達(dá)到它的超時(shí)時(shí)間時(shí),它的任務(wù)執(zhí)行線程被中斷。該任務(wù)將被標(biāo)記為失敗。但是Finalizer Task任務(wù)仍將運(yùn)行。

如果構(gòu)建時(shí)使用了--continue參數(shù),其他任務(wù)可以在它之后繼續(xù)運(yùn)行。不響應(yīng)中斷的task不能超時(shí)。Gradle 的所有內(nèi)置task都會(huì)及時(shí)響應(yīng)超時(shí)

Task支持增量編譯

任何構(gòu)建工具的一個(gè)重要部分是避免重復(fù)工作。在編譯過程中,就是在編譯源文件后,除非發(fā)生了影響輸出的更改(例如源文件的修改或輸出文件的刪除),無需重新編譯它們。因?yàn)榫幾g可能會(huì)花費(fèi)大量時(shí)間,因此在不需要時(shí)跳過該步驟可以節(jié)省大量時(shí)間。

Gradle 支持增量構(gòu)建,當(dāng)您運(yùn)行構(gòu)建時(shí),有些Task被標(biāo)記為UP-TO-DATE,這就是增量編譯生效了

那么Gradle增量編譯如何工作?自定義Task如何支持增量編譯?我們一起來看看

Task的輸入輸出

Task最基本的功能就是接受一些輸入,進(jìn)行一系列運(yùn)算后生成輸出。比如在編譯過程中,Java源文件是輸入,生成的classes文件是輸出。其他輸入可能包括諸如是否應(yīng)包含調(diào)試信息之類的內(nèi)容。

task輸入的一個(gè)重要特征是它會(huì)影響一個(gè)或多個(gè)輸出,從上圖中可以看出。根據(jù)源文件的內(nèi)容和target jdk版本,會(huì)生成不同的字節(jié)碼。這使他們成為task輸入。

但是編譯期的一些其他屬性,比如編譯最大可用內(nèi)存,由memoryMaximumSize屬性決定,memoryMaximumSize對(duì)生成的字節(jié)碼沒有影響。因此,memoryMaximumSize不是task輸入,它只是一個(gè)內(nèi)部task屬性。

作為增量構(gòu)建的一部分,Gradle 會(huì)檢查自上次構(gòu)建以來是task的輸入或輸出有沒有發(fā)生變化。如果沒有,Gradle 可以認(rèn)為task是最新的,因此跳過執(zhí)行其action。需要注意的是,除非task至少有一個(gè)task輸出,否則增量構(gòu)建將不起作用

總得來說:

您需要告訴 Gradle 哪些task屬性是輸入,哪些是輸出。

如果task屬性影響輸出,請(qǐng)務(wù)必將其注冊(cè)為輸入,否則該任務(wù)將被認(rèn)為是最新的而不是最新的。

相反,如果屬性不影響輸出,則不要將其注冊(cè)為輸入,否則任務(wù)可能會(huì)在不需要時(shí)執(zhí)行。

還要注意可能為完全相同的輸入生成不同輸出的非確定性task:不應(yīng)將這些任務(wù)配置為增量構(gòu)建,因?yàn)樽钚聶z查將不起作用。

接下來讓我們看看如何將task屬性注冊(cè)為輸入和輸出。

自定義task類型

為了讓自定義task支持增量編譯,只需要以下兩個(gè)步驟

  • 為每個(gè)task輸入和輸出創(chuàng)建類型化屬性(通過 getter 方法)
  • 為每個(gè)屬性添加適當(dāng)?shù)淖⒔?/li>

Gradle 支持四種主要的輸入和輸出類型:

  • 簡單值
    例如字符串和數(shù)字類型。更一般地說,任何一個(gè)實(shí)現(xiàn)了Serializable的類型。
  • 文件系統(tǒng)類型
    包括RegularFile,Directory和標(biāo)準(zhǔn)File類,也包括 Gradle 的FileCollection類型的派生類,以及任何可以被Project.file(java.lang.Object)和Project.files(java.lang.Object...)方法接收的參數(shù)
  • 依賴解析結(jié)果
    這包括包含Artifact元數(shù)據(jù)的ResolvedArtifactResult類型和包含依賴圖的ResolvedComponentResult類型。請(qǐng)注意,它們僅支持包裝在Provider中.
  • 包裝類型
    不符合其他幾個(gè)類型但具有自己的輸入或輸出屬性的自定義類型。task的輸入或輸出包裝在這些自定義類型中。

接下來我們看個(gè)例子

假設(shè)您有一個(gè)task處理不同類型的模板,例如 FreeMarker、Velocity、Moustache 等。它獲取模板源文件并將它們與一些模型數(shù)據(jù)結(jié)合以生成不同結(jié)果。

此任務(wù)將具有三個(gè)輸入和一個(gè)輸出:

  • 模板源文件
  • 模型數(shù)據(jù)
  • 模板引擎
  • 輸出文件的寫入位置

在編寫自定義task類時(shí),我們很容易通過注解將屬性注冊(cè)為輸入或輸出

public abstract class ProcessTemplates extends DefaultTask {
    @Input
    public abstract Property<TemplateEngineType> getTemplateEngine();
    @InputFiles
    public abstract ConfigurableFileCollection getSourceFiles();
    @Nested
    public abstract TemplateData getTemplateData();
    @OutputDirectory
    public abstract DirectoryProperty getOutputDir();
    @TaskAction
    public void processTemplates() {
        // ...
    }
}
public abstract class TemplateData {
    @Input
    public abstract Property<String> getName();
    @Input
    public abstract MapProperty<String, String> getVariables();
}

可以看出,我們定義了3個(gè)輸入,一個(gè)輸出

  • templateEngine,表示使用什么模板引擎,我們傳入一個(gè)枚舉類型,枚舉類型都實(shí)現(xiàn)了Serializable,因此可作為輸入
  • sourceFiles,表示源文件,我們傳入FileCollection作為輸入
  • templateData,表示模型數(shù)據(jù),自定義類型,在它的內(nèi)部包裝了真正的輸入,通過@Nested注解表示
  • outputDir,表示輸出目錄,表示單個(gè)目錄的屬性需要@OutputDirectory注解

當(dāng)我們重復(fù)運(yùn)行以上task之后,就可以看到以下輸出

> gradle processTemplates
> Task :processTemplates UP-TO-DATE
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 up-to-date

如上所示,task在執(zhí)行過程中會(huì)判斷輸入輸出有沒有發(fā)生變化,由于task的輸入輸出都沒有發(fā)生變化,該task可以直接跳過,展示為up-to-date

除了上述幾種注解,還有其他常用注解如@Internal,@Optional,@Classpath等,具體可查看文檔:Incremental build property type annotations

聲明輸入輸出的好處

一旦你聲明了一個(gè)task的正式輸入和輸出,Gradle 就可以推斷出關(guān)于這些屬性的一些事情。例如,如果一個(gè)task的輸入設(shè)置為另一個(gè)task的輸出,這意味著第一個(gè)task依賴于第二個(gè),gradle可以推斷出這一點(diǎn)并添加隱式依賴

推斷task依賴關(guān)系

想象一個(gè)歸檔task,會(huì)將processTemplates task的輸出歸檔??梢钥吹綒w檔task顯然需要processTemplates首先運(yùn)行,因此可能會(huì)添加顯式的dependsOn. 但是,如果您像這樣定義歸檔task:

tasks.register<Zip>("packageFiles") {
    from(processTemplates.map {it.outputs })
}

Gradle 會(huì)自動(dòng)使packageFiles依賴processTemplates。它可以這樣做是因?yàn)樗?packageFiles 的輸入之一需要 processTemplates 任務(wù)的輸出。我們稱之為推斷的task依賴。

上面的例子也可以寫成

tasks.register<Zip>("packageFiles2") {
    from(processTemplates)
}

這是因?yàn)閒rom()方法可以接受task對(duì)象作為參數(shù)。然后在幕后,from()使用project.files()方法包裝參數(shù),進(jìn)而將task的正式輸出轉(zhuǎn)化為文件集合

輸入和輸出驗(yàn)證

增量構(gòu)建注解為 Gradle 提供了足夠的信息來對(duì)帶注解的屬性執(zhí)行一些基本驗(yàn)證。它會(huì)在task執(zhí)行之前對(duì)每個(gè)屬性執(zhí)行以下操作:

  • @InputFile- 驗(yàn)證屬性是否有值,并且路徑是否對(duì)應(yīng)于存在的文件(不是目錄)。
  • @InputDirectory- 與@InputFile相同,但路徑必須對(duì)應(yīng)于目錄。
  • @OutputDirectory- 驗(yàn)證路徑是否是個(gè)目錄,如果該目錄尚不存在,則創(chuàng)建該目錄。

如果一個(gè)task在某個(gè)位置產(chǎn)生輸出,而另一個(gè)任務(wù)task將其作為輸入使用,則 Gradle 會(huì)檢查消費(fèi)者任務(wù)是否依賴于生產(chǎn)者任務(wù)。當(dāng)生產(chǎn)者和消費(fèi)者任務(wù)同時(shí)執(zhí)行時(shí),構(gòu)建就會(huì)失敗。

此類驗(yàn)證提高了構(gòu)建的穩(wěn)健性,使您能夠快速識(shí)別與輸入和輸出相關(guān)的問題。

您偶爾會(huì)想要禁用某些驗(yàn)證,特別是當(dāng)輸入文件可能實(shí)際上不存在時(shí)。這就是 Gradle 提供@Optional注釋的原因:您使用它來告訴 Gradle 特定輸入是可選的,因此如果相應(yīng)的文件或目錄不存在,則構(gòu)建不應(yīng)失敗。

并行task

定義task輸入和輸出的另一個(gè)好處是:當(dāng)使用--parallel選項(xiàng)時(shí),Gradle 可以使用此信息來決定如何運(yùn)行task。

例如,Gradle 將在選擇下一個(gè)要運(yùn)行的任務(wù)時(shí)檢查task的輸出,并避免并發(fā)執(zhí)行寫入同一輸出目錄的任務(wù)。

同樣,當(dāng)另一個(gè)task正在運(yùn)行消耗或創(chuàng)建一些文件時(shí),Gradle 將使用有關(guān)task銷毀哪些文件的信息(例如,由Destroys注釋)來避免運(yùn)行刪除這些文件的task,反之亦然。

它還可以確定創(chuàng)建一組文件的task已經(jīng)運(yùn)行,并且使用這些文件的task尚未運(yùn)行,并且將避免在這中間運(yùn)行刪除這些文件的task。

總得來說,通過以這種方式提供task的輸入和輸出信息,Gradle 可以推斷task之間的創(chuàng)建/消費(fèi)/銷毀關(guān)系,并可以確保task執(zhí)行不會(huì)違反這些關(guān)系。

增量編譯原理解析

上面我們介紹了如何自定義一個(gè)支持增量編譯的task,那么它的原理是什么呢?

在第一次執(zhí)行task之前,Gradle 會(huì)獲取輸入的指紋。該指紋包含輸入文件的路徑和每個(gè)文件內(nèi)容的哈希值。Gradle 然后執(zhí)行task。如果任務(wù)成功完成,Gradle 會(huì)獲取輸出的指紋。該指紋包含一組輸出文件和每個(gè)文件內(nèi)容的哈希值。Gradle 會(huì)在下次執(zhí)行task時(shí)保留兩個(gè)指紋。

之后每次執(zhí)行task之前,Gradle 都會(huì)獲取輸入和輸出的新指紋。如果新指紋與之前的指紋相同,Gradle 會(huì)假定輸出是最新的并跳過該task。如果它們不相同,Gradle 將執(zhí)行task。Gradle 會(huì)在下次執(zhí)行task時(shí)保留兩個(gè)指紋。

如果文件的統(tǒng)計(jì)信息(即lastModified和size)沒有改變,Gradle 將重用上次運(yùn)行的文件指紋。這意味著當(dāng)文件的統(tǒng)計(jì)信息沒有更改時(shí),Gradle 不會(huì)檢測到更改。

Gradle 還將task的代碼視為task輸入的一部分。當(dāng)task、其操作或其依賴項(xiàng)在執(zhí)行之間發(fā)生變化時(shí),Gradle 會(huì)認(rèn)為該task已過期。

Gradle 了解文件屬性(例如,包含 Java classpath 的屬性)是否是順序敏感的。在比較此類屬性的指紋時(shí),即使文件順序發(fā)生變化也會(huì)導(dǎo)致task過時(shí)。

請(qǐng)注意,如果task指定了輸出目錄,則自上次執(zhí)行以來添加到該目錄的任何文件都將被忽略,并且不會(huì)導(dǎo)致任務(wù)過期。這是因?yàn)椴幌嚓P(guān)的任務(wù)可以共享一個(gè)輸出目錄而不會(huì)相互干擾。如果由于某種原因這不是您想要的行為,請(qǐng)考慮使用TaskOutputs.upToDateWhen(groovy.lang.Closure)

一些高端操作

上面介紹的內(nèi)容涵蓋了您將遇到的大多數(shù)用例,但有些場景需要特殊處理

將@OutputDirectory鏈接到@InputFiles

當(dāng)您想將一個(gè)task的輸出鏈接到另一個(gè)task的輸入時(shí),類型通常匹配,例如,F(xiàn)ile可以將輸出屬性分配給File輸入。

不幸的是,當(dāng)您希望將一個(gè)task的@OutputDirectory中的文件作為另一個(gè)task的@InputFiles屬性(類型FileCollection)的源時(shí),這種方法就會(huì)失效。

例如,假設(shè)您想使用 Java 編譯task的輸出(通過destinationDir屬性)作為自定義task的輸入,該task檢測一組包含 Java 字節(jié)碼的文件。這個(gè)自定義task,我們稱之為Instrument,有一個(gè)使用@InputFiles注解的classFiles屬性。您最初可能會(huì)嘗試像這樣配置task:

tasks.register<Instrument>("badInstrumentClasses") {
    classFiles.from(fileTree(tasks.compileJava.map { it.destinationDir }))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

這段代碼沒有明顯的問題,但是您如果實(shí)際運(yùn)行的話可以看到compileJava并沒有執(zhí)行。在這種情況下,您需要通過dependsOn在instrumentClasses和compileJava之間添加顯式依賴。因?yàn)槭褂胒ileTree()意味著 Gradle 無法推斷task依賴本身。

一種解決方案是使用TaskOutputs.files屬性,如以下示例所示:

tasks.register<Instrument>("instrumentClasses") {
    classFiles.from(tasks.compileJava.map { it.outputs.files })
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

或者,您可以使用project.files(),project.layout.files(),project.objects.fileCollection()來代替project.fileTree()

tasks.register<Instrument>("instrumentClasses2") {
    classFiles.from(layout.files(tasks.compileJava))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

請(qǐng)記住files(),layout.files()和objects.fileCollection()可以將task作為參數(shù),而fileTree()不能。

這種方法的缺點(diǎn)是源task的所有文件輸出都成為目標(biāo)task的輸入文件。如果源task只有一個(gè)基于文件的輸出,就像JavaCompile一樣,那很好。但是如果你必須在多個(gè)輸出屬性中選擇一個(gè),那么你需要明確告訴 Gradle 哪個(gè)task使用以下builtBy方法生成輸入文件:

tasks.register<Instrument>("instrumentClassesBuiltBy") {
    classFiles.from(fileTree(tasks.compileJava.map { it.destinationDir }) {
        builtBy(tasks.compileJava)
    })
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
}

當(dāng)然你也可以通過dependsOn添加明確的task依賴,但是上面的方法提供了更多的語義,解釋了為什么compileJava必須預(yù)先運(yùn)行。

禁用up-to-date檢查

Gradle 會(huì)自動(dòng)處理對(duì)輸出文件和目錄的up-to-date檢查,但如果task輸出完全是另一回事呢?也許它是對(duì) Web 服務(wù)或數(shù)據(jù)庫表的更新?;蛘哂袝r(shí)你有一個(gè)應(yīng)該始終運(yùn)行的task。

這就是doNotTrackState的作用,可以使用它來完全禁用task的up-to-date檢查,如下所示:

tasks.register<Instrument>("alwaysInstrumentClasses") {
    classFiles.from(layout.files(tasks.compileJava))
    destinationDir.set(file(layout.buildDirectory.dir("instrumented")))
    doNotTrackState("Instrumentation needs to re-run every time")
}

如果你的自定義task需要始終運(yùn)行,那么您也可以在任務(wù)類上使用注解@UntrackedTaskTask

提供自定義up-to-date檢查

Gradle 會(huì)自動(dòng)處理對(duì)輸出文件和目錄的up-to-date檢查,但如果task輸出是對(duì) Web 服務(wù)或數(shù)據(jù)庫表的更新。在這種情況下,Gradle 無法知道如何檢查task是否是up-to-date的。

這是就是TaskOutputs.upToDateWhen方法的作用,使用它我們就可以自定義up-to-date檢查的邏輯。例如,您可以從數(shù)據(jù)庫中讀取數(shù)據(jù)庫模式的版本號(hào)?;蛘?,您可以檢查數(shù)據(jù)庫表中的特定記錄是否存在或已更改。

請(qǐng)注意,up-to-date檢查應(yīng)該節(jié)省您的時(shí)間。不要添加比task的標(biāo)準(zhǔn)執(zhí)行花費(fèi)更多時(shí)間的檢查。事實(shí)上,如果一個(gè)task經(jīng)常需要運(yùn)行,因?yàn)樗苌偈莡p-to-date的,那么它可能根本不值得進(jìn)行up-to-date檢查,如禁用up-to-date中所述。

一個(gè)常見的錯(cuò)誤是使用upToDateWhen()而不是Task.onlyIf(). 如果您想根據(jù)與task輸入和輸出無關(guān)的某些條件跳過任務(wù),那么您應(yīng)該使用onlyIf(). 例如,如果您想在設(shè)置或未設(shè)置特定屬性時(shí)跳過task

Finalizer Task

我們常常使用dependsOn來在一個(gè)task之前做一些工作,但如果我們想要在task執(zhí)行之后做一些操作,該怎么實(shí)現(xiàn)呢?

這里我們可以用到finalizedBy方法

val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskX { finalizedBy(taskY) }

如上所示,taskY將在taskX之后執(zhí)行,需要注意的是finalizedBy并不是依賴關(guān)系,就算taskX執(zhí)行失敗,taskY也將正常執(zhí)行

Finalizer task在構(gòu)建創(chuàng)建的資源無論構(gòu)建失敗還是成功都必須清理的情況下很有用,一個(gè)示例是在集成測試任務(wù)中啟動(dòng)的 Web 容器,即使某些測試失敗,也應(yīng)該始終關(guān)閉它。這樣看來finalizedBy類似java中的finally

要指定Finalizer task,請(qǐng)使用Task.finalizedBy(java.lang.Object...?)方法。此方法接受task實(shí)例、task名稱或Task.dependsOn(java.lang.Object...?)接受的任何其他輸入

總結(jié)

到這里這篇文章已經(jīng)相當(dāng)長了,gradle自定義task上手非常簡單,但實(shí)際上有非常多的細(xì)節(jié),尤其是要支持增量編譯時(shí)??偟脕碚f,為了寫出高效的,可緩存的,不拖慢編譯速度的task,還是有必要了解一下這些知識(shí)的

參考資料

docs.gradle.org/current/use…

以上就是Android開發(fā)之Gradle 進(jìn)階Tasks深入了解的詳細(xì)內(nèi)容,更多關(guān)于Android開發(fā)Gradle Tasks的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • android編程實(shí)現(xiàn)系統(tǒng)圖片剪裁的方法

    android編程實(shí)現(xiàn)系統(tǒng)圖片剪裁的方法

    這篇文章主要介紹了android編程實(shí)現(xiàn)系統(tǒng)圖片剪裁的方法,涉及Android針對(duì)圖片的獲取、修改、保存等操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11
  • Android Studio多渠道打包套路

    Android Studio多渠道打包套路

    最近有好多朋友向小編咨詢Android Studio多渠道的打包方法,今天小編給大家分享Android Studio多渠道打包套路,需要的朋友參考下吧
    2017-11-11
  • Android實(shí)現(xiàn)頂部導(dǎo)航菜單左右滑動(dòng)效果

    Android實(shí)現(xiàn)頂部導(dǎo)航菜單左右滑動(dòng)效果

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)頂部導(dǎo)航菜單左右滑動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-06-06
  • 理解Android系統(tǒng)Binder機(jī)制

    理解Android系統(tǒng)Binder機(jī)制

    這篇文章主要為大家介紹了Android系統(tǒng)Binder機(jī)制,幫助大家理解Binder機(jī)制,感興趣的朋友可以參考一下
    2016-05-05
  • RecyclerView索引溢出異常的解決方法

    RecyclerView索引溢出異常的解決方法

    本篇文章主要介紹了RecyclerView索引溢出異常的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • 詳解Android應(yīng)用main函數(shù)的調(diào)用

    詳解Android應(yīng)用main函數(shù)的調(diào)用

    Android常識(shí),App主線程初始化了Looper,調(diào)用prepare的地方是ActivityThread.main函數(shù)。問題來了,App的main函數(shù)在哪兒調(diào)用,下面我們來一起學(xué)習(xí)一下吧
    2019-06-06
  • Android中通過AsyncTask類來制作炫酷進(jìn)度條的實(shí)例教程

    Android中通過AsyncTask類來制作炫酷進(jìn)度條的實(shí)例教程

    這篇文章主要介紹了Android中通過AsyncTask來制作炫酷進(jìn)度條的實(shí)例教程,借助AsyncTask類的線程操作方法來管理異步任務(wù),需要的朋友可以參考下
    2016-05-05
  • Android RxJava異步數(shù)據(jù)處理庫使用詳解

    Android RxJava異步數(shù)據(jù)處理庫使用詳解

    RxJava是一種異步數(shù)據(jù)處理庫,也是一種擴(kuò)展的觀察者模式。對(duì)于Android開發(fā)者來說,使用RxJava時(shí)也會(huì)搭配RxAndroid,它是RxJava針對(duì)Android平臺(tái)的一個(gè)擴(kuò)展,用于Android 開發(fā),它提供了響應(yīng)式擴(kuò)展組件,使用RxAndroid的調(diào)度器可以解決Android多線程問題
    2022-11-11
  • Android使用Intent.ACTION_SEND分享圖片和文字內(nèi)容的示例代碼

    Android使用Intent.ACTION_SEND分享圖片和文字內(nèi)容的示例代碼

    這篇文章主要介紹了Android使用Intent.ACTION_SEND分享圖片和文字內(nèi)容的示例代碼的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過來看看吧
    2018-05-05
  • windows10安裝adb/fastboot驅(qū)動(dòng)超詳細(xì)圖文教程

    windows10安裝adb/fastboot驅(qū)動(dòng)超詳細(xì)圖文教程

    這篇文章主要介紹了windows10安裝adb/fastboot超詳細(xì)圖文教程,安裝方法也很簡單,只要adb安裝成功,fastboot就安裝好了,文中給大家介紹了問題分析及解決方法,需要的朋友可以參考下
    2023-01-01

最新評(píng)論