Gradle技巧之語法淺談

開發技術前線發表於2015-07-18

回顧

在上一篇的博文(Gradle tip #2 : Tasks)中,我們討論了gradle構建的基本單位Task. 並且介紹了構建過程的各個階段及其生命週期.而本文會重點介紹gradle的語法.只有具備了gradle
的相關語法知識,才會大幅度的提高對於閱讀、學習或者編寫gradle指令碼的效率,正所謂”磨刀不誤砍柴工”是也.

引言

gradle 是groovy語言實現的構建工具. groovy是執行在jvm平臺的一門敏捷開發語言.其語法和java有諸多類似之處,然而其具備一些java沒有的概念需要讀者細細體會.下面會詳細的介紹
groovy的基本語法,當然如果您已經對groovy的語法有了一定的瞭解.可以直接跳過這一小節.

1、閉包的基本語法

閉包是groovy中最重要的概念之一. 簡單地說閉包(Closures)是一段程式碼塊. 這個程式碼塊可以接受引數並具有返回值. 有一點要非常注意的是, 閉包往往不是在需要使用的時候才寫出來
這麼一段程式碼(就像Java的匿名類那樣), 通過def 關鍵字可以宣告一個變數代表一個閉包,然後在需要的時候直接使用該變數即可,多說無益,請看如下的例子:

一個簡單的Hello World閉包:

def myClosure = { println 'Hello world!' }

//execute our closure
myClosure()

output: Hello world!

如下是一個接受引數的閉包的例子:

def myClosure = {String str -> println str }

//execute our closure
myClosure('Hello world!')

output: Hello world!

如果閉包只接受一個引數,這個引數在程式碼塊中可以用it代替:

def myClosure = {println it }

//execute our closure
myClosure('Hello world!')

output: Hello world!

如下是接受多個引數的閉包的例子:

def myClosure = {String str, int num -> println "$str : $num" }

//execute our closure
myClosure('my string', 21)

output: my string : 21

閉包裡面的引數型別可以省略不寫,例子如下:

def myClosure = {str, num -> println "$str : $num" }

//execute our closure
myClosure('my string', 21)

output: my string : 21

閉包還有一個比較酷的寫法就是,可以直接呼叫context裡面的變數,預設的context就是建立這個閉包的類(class) 例子如下:

def myVar = 'Hello World!'
def myClosure = {println myVar}
myClosure()

output: Hello world!

上面提到了閉包可以直接呼叫context的變數,這個context可以通過setDelegate()方法來改變,極大的增加了閉包的靈活性!

def myClosure = {println myVar} //I'm referencing myVar from MyClass class
MyClass m = new MyClass()
myClosure.setDelegate(m)
myClosure()

class MyClass {
    def myVar = 'Hello from MyClass!'
}

output: Hello from MyClass!

2、閉包可以作為引數進行傳遞

在groovy中,將閉包作為引數傳遞進函式,是將邏輯進行分離解耦的重要手段.在上述的例子中,我們已經嘗試瞭如何將閉包作為引數進行傳遞.下面我們總結一下傳遞閉包的方法:

1 接受一個引數的函式

myMethod(myClosure)

2 如果函式只接受一個引數,括號可以忽略

myMethod myClosure

3 可以將閉包以插入語的形式建立

myMethod {println ‘Hello World’}

4 函式接受兩個引數

myMethod(arg1, myClosure)

5 接受兩個引數,同樣可以用插入語建立閉包

myMethod(arg1, { println ‘Hello World’ })

6 如果存在多個引數,且最後一個引數是閉包,閉包可以不寫在括號內

myMethod(arg1) { println ‘Hello World’ }

細心的朋友們是不是覺得上述的六種用法中,第三條和第六條很眼熟?很像gradle中的scripts了?

Gradle

在知道了groovy的基本語法(尤其是閉包)之後,下面我們就以一個簡單的gradle 指令碼作為例子具體感受一下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

結合前文的例子,我們可以很容易的理解到,buildscript是一個接受閉包作為引數的函式,這個函式會在編譯的時候被gradle呼叫.這個函式的定義就類似於:def buildscript(Closure closure). 而allprojects 同理也是一個接受閉包作為引數的函式.

那麼問題來了,這些函式具體會在什麼時候被gradle呼叫呢?要回答這個問題就需要介紹另一個知識點:Project

Project

在這裡,我覺得逐句翻譯作者查閱文件的步驟沒有太大的意義,我自己總結了一下作者的概念如下:

理解gradle配置檔案中的script如何呼叫的關鍵就是理解project的相關概念.在gradle執行某個”任務”的時候,會按照各個task的依賴關係來依次執行. 而執行這些task的物件就是Project.說的在通俗一些,project就是你希望gradle為你做的事情,而要完成這些事情,需要將事情分成步驟一步一步的做,這些步驟就是task.

Script blocks

通過前文的學習,我們已經很清楚的瞭解到scipt block就是一段接受閉包的函式,這些函式會被Project呼叫,預設的情況下,gradle 已經準備
好了很多script用於我們對專案進行配置,例如buildScript{} … … 當然你也可以自己寫出符合規範的task來在編譯的過程中被呼叫.

下面我們先看一下Android Studio中預設的script:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.trickyandroid.testapp"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

按照我們已經有的知識,上面的指令碼說明有一個名稱為android的函式,該函式接收閉包作為引數,然而其實在Gradle的文件中是不存在這個函式的. 那麼android指令碼怎麼會出現在這裡呢? 答案就是最上面的apply plugin:‘com.android.application’.這個外掛提供了android構建所需要的各種script.

既然gradle官方的文件中沒有android相關的script資訊,那我們該怎麼查閱呢? 您可以去官方的android網站上查閱,如果懶得找的話請點選這個連結:https://developer.android.com/shareables/sdk-tools/android-gradle-plugin-dsl.zip

您下載了前文連線的文件後,可以發現有一個html格式的文件的名字是AppExtension, 這個文件主要就是介紹了Android configuration blocks. 即在gradle官方文件中沒有的關於Android 配置的各種gradle script都可以在這裡進行查閱(幾個例子):
1、 compileSdkVersion 在文件中的描述是Required. Compile SDK version. 即這個指令碼是gradle進行Android構建之必需,並且這個指令碼是用來描述編譯的時候使用的sdk版本.
2、buildToolsVersion在文件中的描述是Required. Version of the build tools to use. 即該指令碼是構建之必需,其用於告訴gradle使用
哪個版本的build tools
3 … … (詳細情況請參閱文件吧)

Exercise

有了前文的學習作為基礎,我們已經瞭解了gradle語法以及android 外掛的指令碼查閱方法. 那麼接下來我們實際運用這些知識,自定義的對我們的Android專案進行一些配置. 在上述的AppExtension文件中,我查閱到了一個指令碼的名字是testOptions. 這段指令碼代表的是TestOption class呼叫,TestOption class裡有三個屬性:reportDir、resultsDir 和unitTests. 而reportDir就是測試報告最後儲存的位置,我們現在就來改一下這個地方.

android {
......
    testOptions {
        reportDir "$rootDir/test_reports"
    }
}

在這裡,我使用了”$rootDir/test_reports”作為測試結果的儲存位置, $root 指向的就是專案的根目錄.現在如果我通過命令列執行
./gradlew connectedCheck. gradle就會進行一系列的測試程式並且將測試報告儲存在專案根目錄下的test_reports檔案中.

注意一點的是,這個關於測試的小例子,別用在你真是的生產環境中,儘量保持你專案結構的”清潔”

相關文章