1. 程式人生 > >使用groovy理解gradle配置檔案

使用groovy理解gradle配置檔案

groovy語言特點:
1、動態弱型別,不需要明確指定型別,也就是說變數可以以多種方式使用,變數和方法的定義使用的都是def關鍵字。
2、groovy是動態型別語言,也就是說具體的型別取決於物件本身,而不是宣告的引用變數,這樣做的好處groovy不要求宣告變數的型別,方法引數和方法返回值的型別,具體的型別根據賦值或者返回的引數決定,所以我們可以將型別確定的時間推遲到程式碼執行時。

1、groovy函式定義

使用關鍵字fun來定義函式,因為groovy為動態弱型別語言,因此不需要宣告引數型別和返回型別。

fun apply(map) {
    map.each {
        println it
.key + ":" + it.value } }

函式的呼叫

def map = [plugin:"com.android.application"]
apply(map)

我們引數傳入的是一個map物件,我們其實可以不用提前定義一個map變數,直接將物件傳入:

apply(map)
等價於
apply(plugin:"com.android.application")

在groovy語言中,對於函式的呼叫其實可以省略括號,函式名和引數之間直接使用空格即可,因此上面函式呼叫等價於:

apply(plugin:"com.android.application")
等價於 apply plugin: 'com.android.application'

2、groovy閉包

groovy閉包可以理解為一個可執行程式碼塊的方法,它是一個物件。

基本格式為:

{ line -> println line}

例子:

def clos = {println("Hello World!")}

閉包的呼叫有兩種:
1、可以把閉包理解為一個方法物件,因此將它作為方法來呼叫

clos()

2、因為閉包也是一個物件型別,因此可以使用它的一個物件方法來呼叫

clos.call()

對於有引數的閉包:

def clos = {param ->  println
("Hello ${param}!")}
clos("Mirhunana") clos.call("Mirhunana")

需要注意的是:當只有一個引數傳遞給閉包 的時候,這個引數的宣告是可選的,魔術變數 it 代替了宣告

def clos = {println("Hello ${it}!")}

如果有多個引數可以宣告多個引數,這個跟方法是相同的

def clos = {param1, param2 ->  println("Hello ${param1} and ${param1}!")}
clos("Mirhunana", "Mirhunana")
clos.call("Mirhunana", "Mirhunana")

我們知道閉包其實就是一個普通的物件型別,因此我們可以跟其他物件的使用方法一樣來使用閉包,在方法的呼叫中,我們當然可以將閉包作為引數來傳入。

def defaultConfig(clos) {
    clos.call()
}

呼叫該函式:

defaultConfig({
    applicationId "com.example.demo.demo"
    minSdkVersion 15
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
})

對於下面這語句,結合我們前面講的我們應該很容易知道,其他他們就是一些函式的呼叫了吧。

applicationId "com.example.demo.demo"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

另外,我們知道我們對於函式的呼叫時可以將括號省略掉的因此,進一步可以得到:

defaultConfig {
    applicationId "com.example.demo.demo"
    minSdkVersion 15
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

讀到這裡在看看我們Android專案中的build.gradle程式碼,應該就可以看懂是怎麼回事了,原來就是進行的一系列api的呼叫。

3、Project物件和build.gradle

我們應該知道,build.gradle是對一個Project物件的配置,Project物件與build.gradle是一對一的關係,我們在build.gradle中的所做的一系列呼叫其實都是針對Project物件進行操作的,也就是前面呼叫的其實是Project物件的方法來完成對整個專案的配置。

傳統上我們都是通過project.apply(map)來實現對project方法的呼叫,但是在build.gradle指令碼中,為什麼可以不使用project物件來實現對其方法的呼叫呢?

Test.groovy檔案
==================================================
class DelegateDemo {
    String author = "Mirhunana"

    // 定義一個成員方法,方法的引數為閉包型別
    // 讓closure物件成為DelegateDemo物件的一個代理
    def someMethod(closure) {
        println "The original delegate of closure is: ${closure.delegate}"
        closure.delegate = this
        closure()
    }

    def greet(words) {
        println words
    }
}


//下面我們來實現對greet方法的呼叫
def delegateDemo = new DelegateDemo()
delegateDemo.someMethod {
    // 此處的delegate可以省略
    delegate.greet("Hello, " + delegate.author)
}
=====================================================

上面首先呼叫someMethod方法,然後使用代理類來實現對greet方法的呼叫。在呼叫的過程中可以將delegate省略,即:

delegateDemo.someMethod {
    greet("Hello, " + delegate.author)
}

輸出:

The original delegate of closure is: [email protected]1700915
Hello, Mirhunana

這樣就實現了不需要delegateDemo物件,就可以對其方法進行呼叫。

另外,closure的delegate的預設值就是呼叫以closure為引數的方法所在context中的this,看到上面預設列印的代理物件為[email protected],原因就是我們someMethod方法的呼叫所處的context是Test.groovy檔案。

如果我們希望預設delegate為delegateDemo物件,我們可以做如下修改:

==================================================
class DelegateDemo {
    String author = "Mirhunana"

    def someMethod2(closure) {
        println "The original delegate of closure is: ${closure.delegate}"
        // 無需設定closure.delegate
        // 因為closure的delegate預設值已經是DelegateDemo.this
        closure()
    }

    def greet(words) {
        println words
    }

    def test() {
        def delegateDemo = new DelegateDemo()

        // 可以看到以閉包物件為引數的方法的呼叫是在DelegateDemo裡面
        // 所以閉包的預設delegate為delegateDemo物件
        delegateDemo.someMethod2 {
            // 省略了delegate
            greet("Hello, " + author)
        }
    }
}

//下面我們來實現對greet方法的呼叫
def delegateDemo = new DelegateDemo()
delegateDemo.test()
==================================================

那麼我們build.gradle裡面是怎麼實現對project物件方法的呼叫的呢?

class Project {
    def with(closure) {
        closure.delegate = this;
        closure();
    }

    def apply(map) {...}
    def android(closure) {...}
    // ...
}

def project = new Project()
project.with {
    // build.gradle內容
    // 例如:
    // apply plugin: 'com.android.application'
    // android {
    //   compileSdkVersion 25
    //   buildToolsVersion '25.0.0'
    //   defaultConfig {
    //      applicationId "com.example.demo.demo"
    //      minSdkVersion 15
    //      targetSdkVersion 25
    //      versionCode 1
    //      versionName "1.0"
    //      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    //   }
    //   buildTypes {
    //        release {
    //             minifyEnabled false
    //             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    //        }
    //    }
    //  }
  }

可以看到閉包內的內容就是build.gradle對project物件的操作。這樣我們在閉包裡面就可以呼叫project裡面的方法了,而且不需要顯示的使用project。所以,我們build.gradle裡面的內容其實最終轉化為一個閉包方法中的內容。

最終附上Android專案中build.gradle原始碼,看完上面的解析之後,再來看這個指令碼,是不是感覺很清晰,其實build.gradle指令碼的配置就是進行一系類函式api的呼叫過程。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.0'
    defaultConfig {
        applicationId "com.example.demo.demo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.0', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7'
    testCompile 'junit:junit:4.12'
    compile 'fm.jiecao:jiecaovideoplayer:5.5.2'
}

注意:上面只是根據自己的理解來演示原理,僅供參考,與gradle底層的實現可能會有些出入。

歡迎關注微信公眾號:DroidMind
精品內容獨家釋出平臺


呈現與部落格不一樣的技術乾貨