Gradle核心(三):Gradle Task詳解及實戰

博客主頁java

接下來說解Gradle核心Task——任務。主要內容有如何多種方式建立任務,如何訪問任務的方法和屬性,任務執行過程和實戰,任務執行順序和實戰, 任務依賴和實戰,任務的輸入輸出,若是掛載自定義Task到構建過程當中,如何對任務進行分組、排序,以及一些規則性的知識。segmentfault

任務建立方式,以及配置

可使用Project提供的task方法或者經過TaskContainer提供的create方法。網絡

任務名字方式建立任務

經過Project中的task(String name)方法建立任務閉包

def customTask0 = task('customTask0')
// 調用任務的doLast 方法,該方法在任務執行階段執行。
customTask0.doLast {
    println "建立任務方法原型: Task task(String name)"
}

customTask0就是建立的任務名字,經過gradlew customTask0執行這個任務。app

任務名字+一個對該任務配置的Map對象來建立任務

def customTask1 = task(group: 'help', 'customTask1')
customTask1.doLast {
    println "建立任務方法原型: Task task(Map<String, ?> args, String name)"
    println "任務分組: ${customTask1.group}, 任務名字:${customTask1.name}"
    // 任務分組: build, 任務名字:customTask1
}

其中Map參數用來對建立的Task進行配置,上例中指定任務的分組爲help,該任務就會分組到help組中。
單元測試

任務名+閉包方式建立任務

// 方式一:建立任務並配置任務
task customTask2 {
    // 配置任務的分組
    group 'myTask'
    // 配置任務的描述信息
    description '任務名+閉包方式建立任務'

    // 處理任務執行後須要作的事
    doLast {
        println "建立方法原型:Task task(String name, Closure configureClosure)"
        println "任務分組:${customTask2.group}, 任務描述:${customTask2.description}"
    }
}

// 方式二:建立任務並配置任務
task customTask2(group: 'myTask', description: '任務名+閉包方式建立任務') {
    doLast {
        println "建立方法原型:Task task(String name, Closure configureClosure)"
        println "任務分組:${customTask2.group}, 任務描述:${customTask2.description}"
    }
}

除了可使用Map參數配置任務,還可使用閉包的方式對任務進行配置。由於閉包中的委託對象就是Task,全部可使用Task對象的任何方法、屬性進行配置。測試

查看Task源碼可知,可用的配置以下:

配置項的詳細講解:gradle

// 用於配置任務的描述,默認值:null
    String TASK_DESCRIPTION = "description";

    // 用於配置任務的分組,默認值:null
    String TASK_GROUP = "group";

    // 基於一個存在的Task來建立,和咱們類繼承差很少,默認值:DefaultTask
    String TASK_TYPE = "type";

    // 用於配置任務的依賴,默認值:[]
    String TASK_DEPENDS_ON = "dependsOn";

    // 是否替換存在的Task,這個和type配合起來用,默認值:false
    String TASK_OVERWRITE = "overwrite";

    // 添加到任務中的一個Action或者一個閉包,默認值:null
    String TASK_ACTION = "action";

經過TaskContainer建立任務

tasks.create('customTask3') {
    group 'myTask'
    description '經過TaskContainer建立任務'

    doLast {
        println "TaskContainer的create建立任務原型:Task create(String name, Closure configureClosure)"
        println "任務分組: ${group}, 任務描述: ${description}"
    }
}

tasksProject對象的屬性,類型是TaskContainer,能夠直接調用它的create方法建立任務。ui

任務訪問方式

經過任務名訪問

咱們建立的任務都會做爲Project的一個屬性,屬性名就是任務名,因此能夠直接經過任務名訪問該任務。this

def customTask0 = task('customTask0')
// 經過任務名訪問
customTask0.doLast {
    println "建立任務方法原型: Task task(String name)"
}

經過TaskContainer集合方式訪問

其實TaskContainer就是咱們建立任務的集合,在Project中能夠經過tasks屬性訪問TaskContainer,能夠經過訪問集合的方式訪問任務。

tasks['customTask3'].doFirst {
    println "經過訪問集合的方式訪問任務."
}

經過任務名獲取任務,其實調用的就是tasks.getAt('customTask3'),最後調用的是findByName(String name)實現。

經過TaskContainer的find或者get方式訪問

get訪問方式若是找不到任務,就會拋出UnknownTaskException異常。
find訪問方式若是找不到任務,就會返回null

task customTask4

tasks['customTask4'].doLast {
    // find方式訪問
    println "經過路徑find方式訪問: ${tasks.findByPath(':customTask4')}"
    println "經過名稱find方式訪問: ${tasks.findByName('customTask4')}"

    // get方式訪問
    println "經過路徑get方式訪問: ${tasks.getByPath(':customTask4')}"
    println "經過名稱get方式訪問: ${tasks.getByName('customTask4')}"
}

任務執行實戰

統計執行階段的時間,也就是全部Task的執行的全部時間。

def startBuildTime, endBuildTime
// afterEvaluate配置階段完成調用
this.afterEvaluate { Project project ->
    println "-----------配置階段完成--------------"
    // 全部Task配置完成後,找到第一個執行的Task
    def preBuildTask = project.tasks.findByName('preBuild')
    if (preBuildTask) {
        preBuildTask.doFirst {
            startBuildTime = System.currentTimeMillis()
            println "task build start time: ${startBuildTime}"
        }
    }


    def buildTask = project.tasks.findByName('build')
    if (buildTask) {
        buildTask.doLast {
            endBuildTime = System.currentTimeMillis()
            println "the build cost time: ${endBuildTime - startBuildTime}"
        }
    }
}

任務依賴

上一篇中已經講解了任務依賴,單個任務和多個任務依賴,能夠經過dependsOn指定其依賴的任務。可是咱們也能夠經過匹配指定依賴的任務。

task myTask1 {
    doLast {
        println "myTask1>>doLast"
    }
}

task myTask2 {
    doLast {
        println "myTask2>>doLast"
    }
}

task customTask5 {
   // 經過匹配,查看依賴任務
    dependsOn this.project.tasks.findAll { Task task ->
        println "task name>>> ${task.name}"
        return task.name.startsWith('myTask')
    }
    doLast {
        println "customTask5>>doLast"
    }
}

任務依賴-項目實戰

將發佈版本文檔的輸出到每一個版本單獨文檔中實戰。

// releases.xml,發佈版本文檔格式
<releases>
    <release>
        <versionCode>100</versionCode>
        <versionName>1.0.0</versionName>
        <versionInfo>App的第1個版本,上線了一些最基礎核心的功能.</versionInfo>
    </release>

    <release>
        <versionCode>110</versionCode>
        <versionName>1.1.0</versionName>
        <versionInfo>App的第2個版本,上線了一些最基礎核心的功能.</versionInfo>
    </release>
</releases>

將解析文檔後的內容寫入到${buildDir}/generated/release/release-${versionCode}.txt文件中

tasks.create('handleReleaseInfoTask') {
    println "buildDir>>> ${this.buildDir.path}"
    def srcFile = file('releases.xml')
    def destDir = new File(this.buildDir, 'generated/release/')

    doLast {
        println "開始解析releases.xml文件"
        if (!destDir.isDirectory()) destDir.mkdirs()

        def releases = new XmlParser().parse(srcFile)
        releases.release.each { Node releaseNode ->
            def versionCode = releaseNode.versionCode.text()
            def versionName = releaseNode.versionName.text()
            def versionInfo = releaseNode.versionInfo.text()
            // 建立文件寫入
            def descFile = new File(destDir, "release-${versionCode}.txt")
            descFile.withWriter { writer ->
                writer.write("${versionCode}->${versionName}->${versionInfo}")
            }
        }
    }
}

// 測試任務handleReleaseInfoTaskTest依賴handleReleaseInfoTask任務
task handleReleaseInfoTaskTest(dependsOn: handleReleaseInfoTask) {
    def fileDir = fileTree("${this.buildDir.path}/generated/release/")

    doLast {
        fileDir.each {
            println "the file name>>> ${it}"
        }
        println "解析完成."
    }
}

任務分組和描述

任務是能夠分組和添加描述的,分組就是對任務分類。在經過執行gradlew tasks查看任務信息時,就能夠看到不一樣組下的任務,並還能夠看到任務描述信息。

// 配置任務的分組和描述信息
task customTask6(group: 'myTask', description: '任務分組和描述案例') {
    doLast {
        println "group: ${group}, description: ${description}"
    }
}

添加分組後,能夠在組裏找到相應的任務,以下圖所示:
查看分組任務

輸出任務分組和描述信息

<< 操做符(已過期,建議doLast)

<< 操做符是Gradle的Task中的doLast方法的短標記形式,也就是<<代替doLast方法。

task customTask7 << {
    println "customTask7 doLast"
}

其實<<操做符在Groovy中能夠重載的,查看源碼可知,在Task接口中對應leftShift方法重載了<<操做符。

任務執行流程分析

當執行一個Task時,其實就是執行Task對象中的actions列表,其類型是一個List

task customTask8(type: CustomTask) {

    doFirst {
        println "Task執行以前執行:doFirst"
    }

    doLast {
        println "Task執行以後執行:doLast"
    }
}

class CustomTask extends DefaultTask {
    @TaskAction
    def doSelf() {
        println "Task本身自己在執行:doSelf"
    }
}

> gradlew customTask8

// 執行Task後輸出的日誌信息
Task執行以前執行:doFirst
Task本身自己在執行:doSelf
Task執行以後執行:doLast

上例中定義一個Task類型CustomTask , 被TaskAction註解標記的方法,表明Task自己執行要執行的方法。
其實doFirst ,doSelf,doLast 這個三個方法可以按照順序執行,那麼在actions列表中必須按照順序排列的。

在Task建立時,Gradle就會解析被TaskAction標記的方法做爲其Task執行的Action,經過actions.add(0, action)添加 到actions列表中。

doFirst方法經過actions.add(0, action)添加到actions列表中,doFirst就會出如今doSelf前面;而doLast經過actions.add(action)添加到actions列表中,doLast就會出如今doSelf後面。因此在執行Task的時,就達到順序執行的目的。

任務排序

能夠經過 mustRunAftershouldRunAfter 方法控制一個任務必須或者應該在某個任務後執行。

taskB.shouldRunAfter(taskA) 表示taskB應該在taskA執行後執行,可能任務順序不會按照指望的執行。
taskB.mustRunAfter(taskA) 表示taskB必須在taskA執行後執行。

task customTask10 {
    doLast {
        println "TasK: customTask10"
    }
}

task customTask9 {
    mustRunAfter customTask10
    doLast {
        println "TasK: customTask9"
    }
}

> gradlew customTask9 customTask10 

// 執行後輸出日誌信息
TasK: customTask10
TasK: customTask9

任務的啓動和禁用

Task有個enabled屬性能夠啓動和禁用任務。默認爲true,表示啓動;當設置爲false,輸出會提示該任務被Skipping。

task customTask11 {
    doLast {
        println "TasK: customTask11"
    }
}
customTask11.enabled = false

> gradlew -i customTask11 

// 輸出的日誌信息
Skipping task ':customTask11' as task onlyIf is false.

任務的onlyIf斷言

斷言就是一個條件表達式。Task中有一個onlyIf方法,接受閉包做爲參數,當該閉包返回true,該任務執行,不然跳過。
應用場景:能夠控制程序哪些狀況下打什麼包,何時執行單元測試,什麼狀況下執行單元測試時候不執行網絡測試。

案例實戰:假設打渠道包時,若是直接build會編譯出全部的包,太慢!能夠經過onlyIf控制

tasks.create('buildHuaweiRelease') {
    doLast {
        println "build 華爲渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildHuaweiRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildMIUIRelease {
    doLast {
        println "build MIUI渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildMIUIRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildQQRelease {
    doLast {
        println "build QQ渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildMIUIRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'exclude_shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildTask {
    group BasePlugin.BUILD_GROUP
    description '打渠道包'
    dependsOn buildHuaweiRelease, buildMIUIRelease, buildQQRelease
}

上例中buildHuaweiReleasebuildMIUIRelease 是首發渠道包,buildQQRelease 不是首發渠道包,能夠經過build_apps屬性控制打哪些渠道包

// 打全部渠道包
gradlew buildTask
gradlew -Pbuild_apps=all buildTask

// 打首發渠道包
gradlew -Pbuild_apps=shoufa buildTask

// 打非首發渠道包
gradlew -Pbuild_apps=exclude_shoufa buildTask

命令行中-P意思是:爲Project指定K-V格式的屬性鍵值對,格式爲:-PK=V

任務添加規則

當執行或者依賴的任務不存在時,添加任務規則後,能夠對執行失敗的任務作一些操做。

// 任務名做爲閉包的參數
tasks.addRule('規則描述') {String taskName ->
    task(taskName) {
        doLast {
            println "${taskName}任務不存在"
        }
    }
}

task ruleTaskTest {
    dependsOn missTask
}

// 執行後屬性日誌信息
missTask任務不存在

任務輸入輸出

Task提供了inputsoutputs 輸入輸出屬性。

Task輸入輸出案例實戰:版本發佈文檔自動維護

步驟:請求本次發佈的版本相關信息->將版本相關信息解析出來->將解析出來的數據生成xml格式數據->寫入已有的文檔數據中

請求版本信息這一步使用自定義屬性方式代替,首先定義版本相關信息以下

ext {
    versionCode = 105
    versionName = '1.0.5'
    versionInfo = 'App first version.'
 
    destVersionOutputsFile = this.project.file('releases.xml')
    if (!destVersionOutputsFile.exists()) {
        destVersionOutputsFile.createNewFile()
    }
}

// 用於封裝版本信息
class Version {
    def versionCode
    def versionName
    def versionInfo
}

建立一個寫入任務writeVersionTask

tasks.create('writeVersionTask') {
    group 'myTask'
    description '版本信息自動寫入任務.'

    inputs.property('versionCode', versionCode)
    inputs.property('versionName', versionName)
    inputs.property('versionInfo', versionInfo)

    outputs.file destVersionOutputsFile

    doLast {
        println "版本信息自動寫入任務開始."
        def versionData = inputs.getProperties()
        def version = new Version(versionData)

        def writerFile = outputs.files.singleFile

        def sw = new StringWriter()
        def markupBuilder = new MarkupBuilder(sw)

        if (writerFile.text != null && writerFile.text.size() <= 0) {
            // 第一次寫入
            markupBuilder.releases {
                markupBuilder.release {
                    versionCode(version.versionCode)
                    versionName(version.versionName)
                    versionInfo(version.versionInfo)
                }
            }

            writerFile.withWriter { Writer writer ->
                writer.write(sw.toString())
            }

        } else {
            // 已有其餘版本信息
            markupBuilder.release {
                versionCode(version.versionCode)
                versionName(version.versionName)
                versionInfo(version.versionInfo)
            }

            def lines = writerFile.readLines()
            def linesSize = lines.size()

            writerFile.withWriter { Writer writer ->
                lines.eachWithIndex { line, index ->
                    println "line: ${line}, index: ${index}"
                    if (index != linesSize - 1) {
                        writer.append(line).append('\n')
                    } else {
                        // 最後一行
                        writer.append(sw.toString()).append('\n').append('\n')
                        writer.append(line)
                    }
                }
            }
        }
        println "版本信息自動寫入任務結束."
    }
}

建立一個讀取任務readVersionTask

tasks.create('readVersionTask') {
    group 'myTask'
    description '版本信息自動讀取任務.'

    mustRunAfter writeVersionTask

    inputs.file destVersionOutputsFile

    doLast {
        def readFile = inputs.files.singleFile
        println readFile.text
    }
}

建立一個測試任務versionTaskTest

tasks.create('versionTaskTest') {
    dependsOn writeVersionTask, readVersionTask
    doLast {
        println "版本信息自動維護結束"
    }
}

掛載自定義的Task到構建過程當中

上例中,每次發佈版本,都要手動執行writeVersionTask任務,怎麼掛載在build構建過程當中呢?

// afterEvaluate:配置階段完成調用,此時全部的Task解析完成
this.afterEvaluate {
    // 找到build任務
    def buildTask = project.tasks.findByName('build')
    if (buildTask != null) {
        buildTask.doLast {
            // build任務執行完後調writeVersionTask任務
            writeVersionTask.execute()
        }
    }
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索