Gradle系列第(二)篇---Gradle編程主要對象

學習Gradle前,需要有一個Groovy語言的基礎,以免被Groovy的語法困擾,反而忽略了Gradle的知識,可以大致看下上篇文章Gradle系列第(一)篇—Groovy語法初探1。作爲一個菜鳥,很想知道 Gradle 的腳本怎麼去寫,也看了很多網上的文章,大多都是從腳本的角度來介紹Gradle,給我的感覺就是,只記住參數怎麼配置,卻不知道它們都是函數調用,有相關API對應的。比如我們很常見的一行代碼 apply plugin: ‘com.android.application’ 是什麼意思呢?原來一個 build.gradle 對應一個 Project , apply 是一個Project 的一個函數 ,這段代碼其實就是調用了project對象的apply方法,傳入了一個以plugin爲key的map。完整寫出來就是這樣的:project.apply([plugin: ‘com.android.application’])。這樣就明白了。所以我們先要學習Gradle的API,然後才能熟練用Gradle構建項目。

1、Project、Task、Action的關係

創建Android項目的時候,每一個項目中都有一個build.gradle文件,我們稱build.gradle文件爲構建腳本,後面你會了解到這個構建腳本定義了一個project和一些默認的task,在解析 Gradle 的編譯過程之前我們需要理解在 Gradle 中非常重要的兩個對象。Project和Task。每個項目的編譯至少有一個 Project,一個 build.gradle就代表一個project,每個project裏面包含了多個task,每個task代表了構建過程當中的一個原子性操作,比如編譯,打包,生成javadoc,發佈等這些操作。task 裏面又包含很多action,action是一個代碼塊,裏面包含了需要被執行的代碼。

2、Gradle對象

Gradle基於Groovy,Groovy又基於Java。所以,Gradle執行的時候和Groovy一樣,會把腳本轉換成Java對象。Gradle主要有三種對象,這三種對象和三種不同的腳本文件對應,在gradle執行的時候,會將腳本轉換成對應的對象:
Gradle對象:當我們執行gradle xxx或者什麼的時候,gradle會從默認的配置腳本中構造出一個Gradle對象。在整個執行過程中,只有這麼一個對象。Gradle對象的數據類型就是Gradle。我們一般很少去定製這個默認的配置腳本。
Project對象:每一個build.gradle會轉換成一個Project對象。
Settings對象:每一個settings.gradle都會轉換成一個Settings對象。

既然當我們執行gradle xxx或者什麼的時候,gradle會從默認的配置腳本中構造出一個Gradle對象,那麼看一看Gradle對象的信息有哪些。在build.gradle文件中定義一個Task,如下:

task printGradleInfo{
    println "----------------------------------------------- "
    println "In posdevice, gradle id is " +gradle.hashCode()
    println "Home Dir:" + gradle.gradleHomeDir
    println "User Home Dir:" + gradle.gradleUserHomeDir
    println "Parent: " + gradle.parent
}

執行gradlew -q printGradleInfo
輸出

F:\StudyProject\GradleTest2>gradlew -q printGradleInfo
In posdevice, gradle id is 635573988
Home Dir:C:\Users\wangjing\.gradle\wrapper\dists\gradle-2.14.1-all\8bnwg5hd3w55i
User Home Dir:C:\Users\wangjing\.gradle
Parent: null

如果你在打印gradle的hashCode,得到的輸出也是635573988,也驗證了在整個執行過程中,只有這麼一個對象

3、Gradle的生命週期

項目結構.png
如上圖,執行gradlew -q project
輸出

Root project 'GradleTest'
+--- Project ':app'
+--- Project ':library'
\--- Project ':library2'

每一個Library和每一個App都是單獨的Project。根據Gradle的要求,每一個Project在其根目錄下都需要有一個build.gradle。build.gradle文件就是該Project的編譯腳本。因爲包含了多個項目,所以還要有一個setting.gradle用於多項目的構建。Gradle的生命週期總共分成三個階段,初始化階段,配置階段,執行任務階段。首先是初始化階段,這個時候settings.gradle會執行。初始化的下一個階段是配置階段。配置階段的目標是解析每個project中的build.gradle,其內部的任務也會被添加到一個有向圖裏,用於解決執行過程中的依賴關係。在上圖中,gradle的解析順序是:rootproject 的setting.gradle,然後是rootproject的build.gradle,然後是各個subproject。最後一個階段就是執行任務了,你在gradle xxx中指定什麼任務,gradle就會將這個xxx任務鏈上的所有任務全部按依賴順序執行一遍!
gradle整個編譯過程都是可控的,通過實現TaskExecutionListener和BuildListener可以對整個編譯過程進行監聽。下面的代碼打印了一下task的名字。

gradle.addListener(new LifecycleListener())
class LifecycleListener implements  TaskExecutionListener,BuildListener{
    @Override
    void buildStarted(Gradle gradle) {

    }

    @Override
    void settingsEvaluated(Settings settings) {

    }

    @Override
    void projectsLoaded(Gradle gradle) {

    }

    @Override
    void projectsEvaluated(Gradle gradle) {

    }

    @Override
    void buildFinished(BuildResult result) {

    }

    @Override
    void beforeExecute(Task task) {

        println("beforeExecute "+task.name)
    }

    @Override
    void afterExecute(Task task, TaskState state) {
        println("afterExecute  name="+task.name+" state="+state.toString() )
    }
}

輸出結果:

beforeExecute printGradleInfo
afterExecute  name=printGradleInfo state=org.gradle.api.internal.tasks.TaskState

4、Project

一般app.build文件的第一行是apply plugin: ‘com.android.application’,這句話是什麼意思,剛剛解釋過,我們之前說在 Gradle 中構建腳本定義了一個項目(project)。在構建的每一個項目中,Gradle 創建了一個Project類型的實例,並在構建腳本中關聯此Project對象。並且Project接口是你在 Gradle API 中訪問一切 的入點,當構建腳本執行時,它會配置此Project對象。調用project的api來獲取和項目有關的信息。

task queryInfo<<{
    println name
    println project.name
}

執行命令gradlew -q queryInfo
輸出

queryInfo
app

第一個獲取的是任務名稱,第二個獲取的是Project名稱,如果把queryInfo中的 println name放在外面,他會打印項目名稱。

println name
task check<<{
 println project.name
}
app
app

查詢項目的項目信息:

task queryProjectInfo<<{
    //項目名
    println project.name
    //項目相對路徑
    println project.path
    //項目描述
    println project.description
    //項目的絕對路徑
    println project.projectDir
    //項目的build文件絕對路徑
    println project.buildDir
    //項目所在的group
    println project.group
    //項目的版本號
    println project.version
    //項目的ant對象
    println project.ant
}

執行命令gradlew -q queryInfo
輸出

app
:app
null
E:\Programs\android_studio\GradleTest\app
E:\Programs\android_studio\GradleTest\app\build
GradleTest
unspecified
org.gradle.api.internal.project.DefaultAntBuilder@3f1ae6ad

還有若干方法的使用

比如,在解析setting.gradle之後,開始解析build.gradle之前,這裏如果要幹些事情可以寫在beforeEvaluate。

在所有build.gradle解析完成後,開始執行task之前,此時所有的腳本已經解析完成,task,plugins等所有信息可以獲取,task的依賴關係也已經生成,如果此時需要做一些事情,可以寫在afterEvaluate。文檔對afterEvaluate(closure)的解釋是:

Adds a closure to be called immediately after this project has been evaluated. The project is passed to the closure as a parameter. Such a listener gets notified when the build file belonging to this project has been executed. A parent project may for example add such a listener to its child project. Such a listener can further configure those child projects based on the state of the child projects after their build files have been run.

舉個列子:過濾掉一些我不想執行的task.

def disableDebugBuild(){
    //project.tasks包含了所有的tasks,下面的findAll是尋找那些名字中帶debug的Task。
    //返回值保存到targetTasks容器中
    def targetTasks = project.tasks.findAll{task ->
        task.name.contains("Debug")
    }
    //對滿足條件的task,設置它爲disable。如此這般,這個Task就不會被執行
    targetTasks.each{
        println"disable debug task  :${it.name}"
        it.setEnabled false
    }
}

project.afterEvaluate{
    disableDebugBuild()
}

又比如

apply plugin: 'com.android.application'  的原形是
project.apply([plugin: 'com.android.application'])
dependencies {
       compile 'com.google.code.gson:gson:2.3'
}

原形:

project.dependencies({
       add('compile', 'com.google.code.gson:gson:2.3', {
           // Configuration statements
       })
})

看看下面的圖,Project的方法和屬性很多

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

project的方法.png

5、Task

如果你想知道你多少tasks可以用,直接運行gradlew tasks,其會爲你展示所有可用的tasks。當你創建了一個Android工程,那麼將包含Android tasks,build tasks,build setup tasks,help tasks,install tasks,verification tasks等。

項目構建過程中那麼多任務,有些test相關的任務可能根本不需要,可以直接關掉,在build.gradle中加入如下腳本:

tasks.whenTaskAdded { task ->
    if (task.name.contains('AndroidTest')) {
        task.enabled = false
    }
}

tasks會獲取當前project中所有的task,enabled屬性控制任務開關,whenTaskAdded後面的閉包會在gradle配置階段完成。

一般我們定義任務的時候採用的是task + 任務名的方式。例如

task hello << {
        println "hello"
}

現在再介紹另外兩種方式,和上面的定義是等價的。

task(hello)<<{
        println "hello"
}
task('hello')<<{
        println "hello"
}

gradle還提供了一個tasks容器來創建任務,通過調用create方法:

tasks.create(name:'hello')<<{
        println "hello"
}

如何獲取一個任務呢?

將任務看成項目的屬性的方式
println tasks.hello.name
println tasks['hello'].name

使用tasks容器來定位
println hello.name
println project.hello.name

tasks.getByPath()方式來獲得
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path

每個Task包含了Action對象的集合。當Task被執行的時候,其內部的Action集合會按次序逐個執行,所以藉助doFirst(),doLast()等方法來控制Action在隊列中的順序,同時也是執行的順序。

task testAction {
    doFirst {
        println("first")
    }
    doLast {
        println("last")
    }
}

輸出

first
last

其中對於doLast這個Action還有一個簡便的寫法

task testAction <<{
    println("last")
}

<<就代表doLast操作

task與task之間是有關聯的,關聯可以使用dependsOn和finalizedBy。

task A <<{
    println("i am task A")
}
task B <<{
    println("i am task B")
}

A.dependsOn B

執行gradlew -q A
輸出:

i am task B
i am task A

如果是 A.finalizedBy B

i am task A
i am task B

相關鏈接:

Gradle官網https://gradle.org/

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