博客主頁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
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";
tasks.create('customTask3') { group 'myTask' description '經過TaskContainer建立任務' doLast { println "TaskContainer的create建立任務原型:Task create(String name, Closure configureClosure)" println "任務分組: ${group}, 任務描述: ${description}" } }
tasks是Project對象的屬性,類型是TaskContainer,能夠直接調用它的create方法建立任務。ui
咱們建立的任務都會做爲Project的一個屬性,屬性名就是任務名,因此能夠直接經過任務名訪問該任務。this
def customTask0 = task('customTask0') // 經過任務名訪問 customTask0.doLast { println "建立任務方法原型: Task task(String name)" }
其實TaskContainer就是咱們建立任務的集合,在Project中能夠經過tasks屬性訪問TaskContainer,能夠經過訪問集合的方式訪問任務。
tasks['customTask3'].doFirst { println "經過訪問集合的方式訪問任務." }
經過任務名獲取任務,其實調用的就是tasks.getAt('customTask3'),最後調用的是findByName(String name)實現。
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}" } }
添加分組後,能夠在組裏找到相應的任務,以下圖所示:
<< 操做符是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的時,就達到順序執行的目的。
能夠經過 mustRunAfter 和 shouldRunAfter 方法控制一個任務必須或者應該在某個任務後執行。
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.
斷言就是一個條件表達式。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 }
上例中buildHuaweiRelease 和 buildMIUIRelease 是首發渠道包,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提供了inputs 和 outputs 輸入輸出屬性。
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 "版本信息自動維護結束" } }
上例中,每次發佈版本,都要手動執行writeVersionTask任務,怎麼掛載在build構建過程當中呢?
// afterEvaluate:配置階段完成調用,此時全部的Task解析完成 this.afterEvaluate { // 找到build任務 def buildTask = project.tasks.findByName('build') if (buildTask != null) { buildTask.doLast { // build任務執行完後調writeVersionTask任務 writeVersionTask.execute() } } }
若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)