Gradle之Project,Task

gradle基本概念

百度百科:Gradle是一個基於Apache Ant和Apache Maven概念的項目自動化構建開源工具。它使用一種基於Groovy的特定領域語言(DSL)來聲明項目設置,目前也增加了基於Kotlin語言的kotlin-based DSL,拋棄了基於XML的各種繁瑣配置。

gradle是一個構建工具,也是一個編程框架

gradle組成:

  • groovy核心語法
  • build script block
  • gradle api

gradle的優勢:

  • 相比於maven,ant構建更加靈活
  • 可以服用各種插件
  • 兼容上,兼容maven等所有功能
  • 細粒度,可以自定義編譯過程

gradle的生命週期

gradle的生命週期主要分爲3個階段:Initialization初始化階段,Configuration配置階段,Execution執行階段

  1. Initialization:解析真個工程中的所有Project,構建所有Project賭贏的project對象
  2. Configuration:解析project中的所有的task,構建成一個有向無環圖
  3. Execution:執行具體的task以及其依賴的task

setting.gradle:這個文件是在初始化階段執行,一個gradle項目中必須由setting.gradle這個文件,因爲它決定了那些項目參與構建。在gradle的構建中這是最先執行的一個文件。

如何監聽gradle的生命週期呢

在setting.gradle文件中可以監聽初始化階段和配置之前的階段

gradle.settingsEvaluated {
    println('初始化階段開始執行...')
}
gradle.beforeProject {
    println('配置之前調用...')
}

在build.gradle文件中,可以監聽配置之後、task的執行階段和執行完成的階段

this.afterEvaluate {
    println('配置之後....')
}
gradle.taskGraph.beforeTask {
    println "執行階段 before task"
}
gradle.taskGraph.afterTask {
    println "執行階段 afterTask "
}
this.gradle.buildFinished {
    println("執行階段完畢,構建完畢...")
}

gradle Project

官方文檔:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project

在一個gradle項目中,怎麼算是一個Project呢,以一個Android項目爲例,跟工程算是一個Project,其內部的子module也是一個Project。準確的說,只要該文件夾下有build.gradle文件,它就是一個Project。每一個module在編譯期間都會生成一個對應的project對象,我們在build.gradle中編寫的代碼,其實都是在一個project對象內部編寫。

獲取一個項目所有的project

this.getAllprojects().eachWithIndex { Project entry, int index ->
    println(entry.name)
}

獲取Project中的目錄

println(this.project.getRootDir().absolutePath)
println(this.project.getBuildDir().absolutePath)

gradle中拷貝文件,非常簡單,比如把app目錄下的proguard-rules.pro文件拷貝到根目錄下的build文件夾下面

copy {
    from(file('proguard-rules.pro'))
    into(getRootProject().getBuildDir())
}

file(’’)函數可以定位當前project中的某個文件。

Project的依賴:

在一個Android項目的根目錄中的build.gradle中,我們都會看到下面的一段配置:

buildscript {
    repositories {
         google()
         jcenter()
          maven{
            url 'http://localhost:8081/xxxx'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
        
    }
    allprojects {
       repositories {
        google()
        jcenter()
    }
}

buildscript 就是依賴配置的核心部分,它接受一個閉包,內部可以設置很多參數,最重要的就是前面的兩個repositories和dependencies。

  • repositories :配置工程的倉庫地址,按照順序從不同的倉庫中查找依賴。這裏也可以添加本地的maven倉庫地址
  • dependencies :配置工程的插件依賴地址(gradle程序所依賴的第三方庫)。比如com.android.tools.build:gradle:3.5.1就是谷歌開發的Android相關的插件還有平時開發經常用到的tinker相關插件,butterknife相關插件都在這裏。
  • allprojects 子項目的倉庫

在Project中執行外部指令,比如下面的一個複製的任務:

task('copytask'){
    //保證是在執行階段
    doLast{
        def fromPath = this.buildDir.path + '\\outputs\\apk'

        def toPath = 'D:\\data'
        //外部命令腳本
        def command = "xcopy ${fromPath} /s ${toPath}"
        println(command)
        //執行代碼塊
        exec {
            try {
                //執行 dir命令, /c 表示 cmd窗口執行完 dir 命令後立即關掉,
                // 至於 cmd 後可帶哪些參數,可在終端下 cmd /? 查看
                commandLine 'cmd', '/c', command
                println('copytask is success')
            }catch(GradleException e){
                println('copytask is failed')
            }
        }
    }
}

上面的代碼:定義一個資源路徑、一個目標路徑和一個複製的指令。這裏使用的是windows中的複製目錄的指令xcopy,在mac和linux中需要使用對應系統的指令。然後使用commandLine來執行指令。

gradle Task

官方文檔 :https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#org.gradle.api.Task

task是構建過程中的基本單元,每一個task都屬於一個project,每一個task都有一個自己的名字和一個唯一的路徑,該路徑在所有project和所有的task中都是唯一的。

Task的創建方式:

第一種直接創建:

task('helloTask'){
    println('hello task')
}

第二種通過TaskContainer容器創建

this.tasks.create('helloTask2'){
    println('hello task2')
}

這兩種方式創建的task沒有區別,TaskContainer可以更好的管理task,它裏面有創建task和查找task等方法。

Task的配置方式

同樣,Task的配置也有兩種方式:比如下面的兩種配置分組和描述信息。

//第一種
task helloTask(group:'hello',description:'hello task des'){
    println('hello task')
}
//第二種
this.tasks.create(name:'helloTask2'){
    setGroup('hello')
    setDescription('hello create task')
    println('hello task2')
}

不同的task設置相同的分組,方便查找,比如把我們自己自定義的task都設置到一個同樣的分組中,跟系統的區分開。

自定義Task的執行時機

當我們在studio的命令行執行前面的代碼的時候,比如執行gradlew helloTask,我們只執行第一個task,執行結果會看到helloTask和helloTask2都會輸出。這是因爲前面的寫法都是在gradle生命週期的配置階段執行。

那如何讓一個自定義的task在gradle的執行階段執行,可以使用doFirst和doLast這兩個閉包函數。因爲系統中會有很多默認的task,比如Android中build和clean等。doFirst就是在系統task執行之前執行,doLast就是在系統的task執行之後執行。

task helloTask(group:'hello',description:'hello task des'){
    //配置階段執行
    println('hello task')
    //系統task執行之前
    doFirst{
        println('hello task do First')
    }
    //系統task執行之後
    doLast{
        println('hello task do Last')
    }
}

通過doFirst和doLast,我們可以做一些事情,比如統計build執行階段的時間

def buildStartTime
def buildEndTime
this.afterEvaluate { Project project ->
    //afterEvaluate配置階段完成後執行
    def preTask = project.tasks.getByName('preBuild')
    preTask.doFirst{
        buildStartTime = System.currentTimeMillis()
    }
    def endTask = project.tasks.getByName('build')
    endTask.doLast{
        buildEndTime = System.currentTimeMillis()
        println("build 執行時間:${buildEndTime - buildStartTime}")
    }
}

//輸出
> Task :app:build
build 執行時間:28018

Task的依賴

一個task可能會依賴一個或者多個別的task,那麼在執行的時候,它所依賴的task會先執行

靜態添加依賴,當我們明確知道需要依賴那幾個task的時候

task taskA{
    doLast{
        println('i am taskA')
    }
}
task taskC{
    doLast{
        println('i am taskC')
    }
}
task taskB(dependsOn:[taskA,taskC]){
    doLast{
        println('i am taskB')
    }
}

taskB依賴了taskA和taskC,運行可以看到,taskA和taskC在taskB之前執行。

動態添加依賴,比如下面的taskB依賴所有name以lib開頭的task

task lib1 {
    doLast{
        println('i am lib1')
    }
}
task lib2  {
    doLast{
        println('i am lib2')
    }
}
task lib3 {
    doLast{
        println('i am lib3')
    }
}

task taskB {
    dependsOn project.tasks.findAll {
        task -> 
            return task.name.startsWith('lib')
    }
    doLast{
        println('i am taskB')
    }
}

控制task的執行順序可以通過前面的依賴的方式,也可以通過mustRunAfter這個關鍵字來指定某個task的執行必須在誰的後面。

task taskC{
    doLast{
        println('i am taskC')
    }
}
task taskB {
    mustRunAfter taskC
    doLast{
        println('i am taskB')
    }
}
task taskA{
    mustRunAfter taskB
    doLast{
        println('i am taskA')
    }
}

前面我們知道,通過this.afterEvaluate {}這個閉包我們可以在配置完成之後來執行我們自己的一些操作,那如何將我們自己的task插入到系統構建的中間部位執行呢?

辦法就是:通過mustRunAfter關鍵字,讓我們的task必須執行在一個系統的task之後,然後通過dependsOn來讓另一個一個系統的task依賴我們自己的task。這樣就把我們自己的task插入到了這兩個系統的task之間了。

Task的輸入輸出

一個Task的inputs和outputs可以是一個或多個文件,可以是文件夾,還可以是Project的某個Property,甚至可以是某個閉包所定義的條件。

從proguard-rules.pro中讀取數據,寫入到destination.txt文件中

task combineFileContent {
    def source = file('proguard-rules.pro')
    def destination = file('destination.txt')

    //inputs 屬性應該被賦值爲 一個目錄、一個或多個文件、或是一個任意屬性
    inputs.file source        // 將sources聲明爲該Task的inputs
    //outputs 應該被賦值爲一個或多個目錄或者一個或多個文件
    outputs.file destination  // 將destination聲明爲outputs

    doLast {
        source.withReader {
            reader ->
                def  lines = reader.readLines()
                destination.withWriter { writer ->
                    lines.each { line ->
                        writer.append(line+'\r\n')
                    }
                }
        }
    }
}

上面的代碼,如果我們不寫inputs和outputs也是可以完成複製的,那有什麼區別呢,區別就是如果寫了,只有當源文件改變的時候,纔會重新執行此task,如果沒寫,那麼每次都會執行此task。如此看來還是寫這個執行效率會高點。在gradle中這個稱爲增量構建。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章