使用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底層的實現可能會有些出入。