前言
引用 Gradle 官方一段對Gradle的介紹:Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL.翻譯過來就是:Gradle 是一個開源的自動化構建工具,專注于靈活性和性能。Gradle 構建腳本是使用 Groovy 或 Kotlin DSL 編寫的。 之前官網(wǎng)的介紹是說 Gradle 是基于 Groovy 的 DSL,為啥現(xiàn)在又多了個 Kotlin 呢?因為 Gradle 從5.0開始,開始支持了 Kotlin DSL,現(xiàn)在已經(jīng)發(fā)展到了6.8.3,因此我們可以使用 Groovy 或者 Kotlin 來編寫 Gradle腳本。Kotlin 現(xiàn)作為 Android 第一開發(fā)語言,重要性不言而喻,作為一個 Android開發(fā)者,Kotlin 是必學的,后續(xù)我也會出個 Kotlin 系列文章。今天我們的重點是介紹一些 Gradle 的相關概念,以及對 Groovy 語言的學習
一、問題
我學習知識喜歡以問題為導向,這樣可以讓我明確學習的目的,提高學習效率,下面也是我在學習 Gradle 的過程中,由淺入深所產(chǎn)生的一些疑問,我們都知道,Android 應用是用 Gradle 構建的,在剛開發(fā) Android 的時候我會想:
1、什么是自動化構建工具?
2、Gradle 是什么?
3、什么是 DSL?
4、什么是 Groovy?
5、Gradle 和 Groovy 有什么區(qū)別?
6、靜態(tài)編程語言和動態(tài)編程語言有什么區(qū)別?
帶著這些疑問,我們繼續(xù)學習
1、自動化構建工具
在 Android 上的體現(xiàn),簡單的說就是自動化的編譯、打包程序
在上大學學習Java那會,老師為了讓我們深刻的體驗擼碼的魅力,都是通過文本直接敲代碼的,敲完之后把擴展名改成.java后綴,然后通過javac命令編譯,編譯通過后,在執(zhí)行java命令去運行,那么這種文件一多,我們每次都得手動去操作,效率會大大的降低,這個時候就出現(xiàn)了自動化編譯工具,我們只需要在編譯工具中,點擊編譯按鈕,編譯完成后,無需其他手動操作,程序就可以直接運行了,自動化編譯工具就是最早的自動化構建工具。那么隨著業(yè)務功能的不斷擴展,我們的產(chǎn)品需要加入多媒體資源,需要打不同的渠道包發(fā)布到不同的渠道,那就必須依靠自動化構建工具,要能支持平臺、需求等方面的差異、能添加自定義任務、專門的用來打包生成最終產(chǎn)品的一個程序、工具,這個就是自動化構建工具。自動化構建工具本質上還是一段代碼程序。這就是自動化構建工具的一個發(fā)展歷程,自動化構建工具在這個過程中不斷的發(fā)展和優(yōu)化
2、Gradle 是什么?
理解了自動化構建工具,那么理解 Gradle 就比較簡單了,還是引用官方的那一段話:
Gradle 是一個開源的自動化構建工具,專注于靈活性和性能。Gradle 構建腳本是使用 Groovy 或 Kotlin DSL 編寫的。
Gradle 是 Android 的默認構建工具,Android 項目這么多東西,既有我們自己寫的 java、kotlin、C++、Dart 代碼,也有系統(tǒng)自己的 java、C,C++ 代碼,還有引入的第三方代碼,還有多媒體資源,這么多代碼、資源打包成 APK 文件肯定要有一個規(guī)范,干這個活的就是我們熟悉的 gradle 了,總而言之,Gradle就是一個幫我們打包 APK 的工具

3、什么是DSL?
DSL英文全稱:domain specific language,中文翻譯即領域特定語言,例如:HTML,XML等 DSL 語言
特點
- 解決特定領域的專有問題
- 它與系統(tǒng)編程語言走的是兩個極端,系統(tǒng)編程語言是希望解決所有的問題,比如 Java 語言希望能做 Android 開發(fā),又希望能做后臺開發(fā),它具有橫向擴展的特性。而 DSL 具有縱向深入解決特定領域專有問題的特性。
總的來說,DSL 的核心思想就是:“求專不求全,解決特定領域的問題”。
4、什么是 Groovy?
Groovy 是基于 JVM 的腳本語言,它是基于Java擴展的動態(tài)語言
基于 JVM 的語言有很多種,如:Groovy,Kotlin,Java,Scala等等,他們都擁有一個共同的特性:最終都會編譯生成 Java 字節(jié)碼文件并在 JVM 上運行。
因為 Groovy 就是對 Java 的擴展,所以,我們可以用學習 Java 的方式去學習 Groovy 。 學習成本相對來說還是比較低的,即使開發(fā)過程中忘記 Groovy 語法,也可以用 Java 語法繼續(xù)編碼
5、Gradle 和 Groovy 有什么區(qū)別?
Gradle是基于 Groovy 的一種自動化構建工具,是運行在JVM上的一個程序,Groovy是基于JVM的一種語言,Gradle 和 Groovy 的關系就像 Android 和 Java 的關系一樣
6、靜態(tài)編程語言和動態(tài)編程語言有什么區(qū)別?
靜態(tài)編程語言是在編譯期就要確定變量的數(shù)據(jù)類型,而動態(tài)編程語言則是在運行期確定變量的數(shù)據(jù)類型。就像靜態(tài)代理和動態(tài)代理一樣,一個強調的是編譯期,一個強調的是運行期,常見的靜態(tài)編程語言有Java,Kotlin等等,動態(tài)編程語言有Groovy,Python等語言。
二、Groovy 開發(fā)環(huán)境搭建與工程創(chuàng)建
1、到官網(wǎng)下載JDK安裝,并配置好 JDK 環(huán)境
2、到官網(wǎng)下載好 Groovy SDK,并解壓到合適的位置
3、配置 Groovy 環(huán)境變量
4、到官網(wǎng)下載 IntelliJ IDEA 開發(fā)工具并安裝
5、創(chuàng)建 Groovy 工程即可
完成了上述4個步驟,我們開始創(chuàng)建一個 Groovy 工程:
按照上述圖片中步驟即可完成一個 Groovy 工程創(chuàng)建,下面就可以使用 IntelliJ IDEA 這個工具來學習 Groovy 了,我下面所有代碼都是在 IntelliJ IDEA 上跑的
小技巧: 作為 Android 開發(fā)者,我們一般都是使用 AndroidStudio 進行開發(fā)的,但是 AndroidStudio 對于 Groovy 支持不是很友好,各種沒有提示,涉及到閉包,你也不知道閉包的參數(shù)是啥?因此這個時候,你就可以使用 IntelliJ IDEA 先弄好,在復制過去,IntelliJ IDEA 對Groovy 的支持還是很友好的
三、Groovy 基礎語法
再次強調 Groovy 是基于 java 擴展的動態(tài)語言,直接寫 java 代碼是沒問題的,既然如此,Groovy 的優(yōu)勢在哪里呢?
在于 Groovy 提供了更加靈活簡單的語法,大量的語法糖以及閉包特性可以讓你用更少的代碼來實現(xiàn)和Java同樣的功能。比如解析xml文件,Groovy 就非常方便,只需要幾行代碼就能搞定,而如果用 Java 則需要幾十行代碼。
1、支持動態(tài)類型,使用 def 關鍵字來定義一個變量
在 Groovy 中可以使用 def 關鍵字定義一個變量,當然 Java 里面定義數(shù)據(jù)類型的方式,在 Groovy 中都能用
//Java 中,我們一般會這么定義
int age = 16
String name = "erdai"
//Groovy 中,我們可以這樣定義,在變量賦值后,Groovy 編譯器會推斷出變量的實際類型
def age = 16
def name = 'erdai'
2、不用寫 ; 號
現(xiàn)在比較新的語言都不用寫,如 Kotlin
def age = 16
def name = 'erdai'
3、沒有基本數(shù)據(jù)類型了,全是引用類型
上面說到,定義一個變量使用 def 關鍵字,但是 Groovy 是基于 Java 擴展的,因此我們也可以使用 Java 里面的類型,如 Java 中8大基本類型:byte , short , int , long , float , double ,char,boolean
//定義8大基本類型
byte mByte = 1
short mShort = 2
int mInt = 3
long mLong = 4
float mFloat = 5
double mDouble = 6
char mChar = 'a'
boolean mBoolean = true
//對類型進行打印
println(mByte.class)
println(mShort.class)
println(mInt.class)
println(mLong.class)
println(mFloat.class)
println(mDouble.class)
println(mChar.class)
println(mBoolean.class)
//打印結果如下:
class java.lang.Byte
class java.lang.Short
class java.lang.Integer
class java.lang.Long
class java.lang.Float
class java.lang.Double
class java.lang.Character
class java.lang.Boolean
因此我們可以得出結論:Groovy中沒有基本數(shù)據(jù)類型,全是引用類型,即使定義了基礎類型,也會被轉換成對應的包裝類
4、方法變化
1、使用 def 關鍵字定義一個方法,方法不需要指定返回值類型,參數(shù)類型,方法體內的最后一行會自動作為返回值,而不需要return關鍵字
2、方法調用可以不寫 () ,最好還是加上 () 的好,不然可讀性不好
3、定義方法時,如果參數(shù)沒有返回值類型,我們可以省略 def,使用 void 即可
4、實際上不管有沒有返回值,Groovy 中返回的都是 Object 類型
5、類的構造方法,避免添加 def 關鍵字
def sum(a,b){
a + b
}
def sum = sum(1,2) //還可以寫成這樣,但是可讀性不好 def sum = sum 1,2
println(sum)
//打印結果
3
//如果方法沒有返回值,我們可以這樣寫:
void doSomething(param1, param2) {
}
//類的構造方法,避免添加 def 關鍵字
class MyClass {
MyClass() {
}
}
5、字符串變化
在 Groovy 中有三種常用的字符串定義方式,如下所示:
這里先解釋一下可擴展字符串的含義,可擴展字符串就是字符串里面可以引用變量,表達式等等
1 、單引號 '' 定義的字符串為不可擴展字符串
2 、雙引號 "" 定義的字符串為可擴展字符串,可擴展字符串里面可以使用 ${} 引用變量值,當 {} 里面只有一個變量,非表達式時,{}也可以去掉
3 、三引號 ''' ''' 定義的字符串為輸出帶格式的不可擴展字符串
def age = 16
def name = 'erdai'
//定義一個不可擴展字符串,和我門在Java中使用差不多
def str1 = 'hello ' + name
//定義可擴展字符串,字符串里面可以引用變量值,當 {} 里面只有一個變量時,{}也可以去掉
def str2 = "hello $name ${name + age}"
//定義帶輸出格式的不可擴展字符串 使用 \ 字符來分行
def str3 = '''
\
hello
name
'''
//打印類型和值 下面代碼我省略了 println 方法的(),上面有講到這種語法也是允許的
println 'str1類型: ' + str1.class
println 'str1輸出值: ' + str1
println 'str2類型: ' + str2.class
println 'str2輸出值: ' + str2
println 'str3類型: ' + str3.class
println 'str3輸出值: ' + str3
//打印結果
str1類型: class java.lang.String
str1輸出值: hello erdai
str2類型: class org.codehaus.groovy.runtime.GStringImpl
str2輸出值: hello erdai erdai16
str3類型: class java.lang.String
str3輸出值:
hello
name
從上面代碼我們可以看到,str2 是 GStringImpl 類型的,而 str1 和 str3 是 String 類型的,那么這里我就會有個疑問,這兩種類型在相互賦值的情況下是否需要強轉呢?我們做個實驗在測試下:
//定義一個 String 類型的變量接收 GStringImpl 類型的變量,并沒有強轉
String str4 = str2
println 'str4類型: ' + str4.class
println 'str4輸出值: ' + str4
//打印類型和值
str4類型: class java.lang.String
str4輸出值: hello erdai erdai16
因此我們可以得出結論:編碼的過程中,不需要特別關注 String 和 GString 的區(qū)別,編譯器會幫助我們自動轉換類型。
6. 不用寫 get 和 set 方法
1、在我們創(chuàng)建屬性的時候,Groovy會幫我們自動創(chuàng)建 get 和 set 方法
2、當我們只定義了一個屬性的 get 方法,而沒有定義這個屬性,默認這個屬性只讀
3、我們在使?對象 object.field 來獲取值或者使用 object.field = value 來賦值的時候,實際上會自動轉而調? object.getField() 和 object.setField(value) 方法,如果我們不想調用這個特殊的 get 方法時則可以使用 .@ 直接域訪問操作符訪問屬性本身
我們來模擬1,2,3這三種情況
//情況1:在我們創(chuàng)建屬性的時候,Groovy會幫我們自動創(chuàng)建 get 和 set 方法
class People{
def name
def age
}
def people = new People()
people.name = 'erdai'
people.age = 19
println "姓名: $people.name 年齡: $people.age"
//打印結果
姓名: erdai 年齡: 19
//情況2 當我們定義了一個屬性的 get 方法,而沒有定義這個屬性,默認這個屬性只讀
//我們修改一下People類
class People{
def name
def getAge(){
12
}
}
def people = new People()
people.name = 'erdai'
people.age = 19
println "姓名: $people.name 年齡: $people.age"
//運行一下代碼 打印結果報錯了,如下:
Caught: groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: age for class: variable.People
//大概錯誤意思就是我們不能修改一個只讀的屬性
//情況3: 如果我們不想調用這個特殊的 get 方法時則可以使用 .@ 直接域訪問操作符訪問屬性本身
class People{
def name
def age
def getName(){
"My name is $name"
}
}
//這里使用了命名的參數(shù)初始化和默認的構造器創(chuàng)建people對象,后面會講到
def people = new People(name: 'erdai666')
people.age = 19
def myName = people.@name
//打印值
println myName
println "姓名: $people.name 年齡: $people.age"
//打印結果
erdai666
姓名: My name is erdai666 年齡: 19
//看到區(qū)別了嗎?使用 people.name 則會去調用這個屬性的get方法,而 people.@name 則會訪問這個屬性本身
7、Class 是一等公民,所有的 Class 類型可以省略 .Class
//定義一個Test類
class Test{
}
//定義一個測試class的方法,從前面的語法我們知道,方法的參數(shù)類型是可以省略的
def testClass(myClass){
}
//測試
testClass(Test.class)
testClass(Test)
8、== 和 equals
在 Groovy 中,== 就相當于 Java 的 equals,如果需要比較兩個對象是否是同一個,需要使用 .is()
class People{
def name
def age
}
def people1 = new People(name: 'erdai666')
def people2 = new People(name: 'erdai666')
println("people1.name == people2.name is: " + (people1.name == people2.name))
println("people1 is people2 is: " + people1.is(people2))
//打印結果
people1.name == people2.name is: true
people1 is people2 is: false
9、使用 assert 來設置斷言,當斷言的條件為 false 時,程序將會拋出異常
assert 2 ** 4 == 15
//運行程序,報錯了,結果如下:
Caught: Assertion failed:
assert 2 ** 4 == 15
| |
16 false
10、支持 ** 次方運算符
assert 2 ** 4 == 16
11、簡潔的三元表達式
//在java中,我們會這么寫
String str = obj != null ? obj : ""
//在Groovy中,我們可以這樣寫,?: 操作符表示如果左邊結果不為空則取左邊的值,否則取右邊的值
String str = obj ?: ""
12、簡潔的非空判斷
//在java中,我們可能會這么寫
if(obj != null){
if(obj.group != null){
if(obj.group.artifact != null){
//do something
}
}
}
//在Groovy中,我們可以這樣寫 ?. 操作符表示如果當前調用對象為空就不執(zhí)行了
obj?.group?.artifact
13、強大的 Switch
在 Groovy 中,switch 方法變得更加靈活,強大,可以同時支持更多的參數(shù)類型,比在 Java 中增強了很多
def result = 'erdai666'
switch (result){
case [1,2,'erdai666']:
println "匹配到了result"
break
default:
println 'default'
break
}
//打印結果
匹配到了result
14、判斷是否為 null 和 非運算符
在 Groovy 中,所有類型都能轉成布爾值,比如 null 就相當于0或者相當于false,其他則相當于true
//在 Java 中,我們會這么用
if (name != null && name.length > 0) {
}
//在 Groovy 中,可以這么用,如果name為 null 或 0 則返回 false,否則返回true
if(name){
}
//非運算符 erdai 這個字符串為非 null ,因此為true,而 !erdai 則為false
assert (!'erdai') = false
15、可以使用 Number 類去替代 float、double 等類型,省去考慮精度的麻煩
16、默認是 public 權限
默認情況下,Groovy 的 class 和 方法都是 public 權限,所以我們可以省略 public 關鍵字,除非我們想使用 private 修飾符
class Server {
String toString() { "a server" }
}
17、使用命名的參數(shù)初始化和默認的構造器
Groovy中,我們在創(chuàng)建一個對象實例的時候,可以直接在構造方法中通過 key value 的形式給屬性賦值,而不需要去寫構造方法,說的有點抽象,上代碼感受一下:
//定義一個people
class People{
def name
def age
}
//我們可以通過以下幾種方式去實例化一個對象,注意我們People類里面沒有寫任何一個構造方法哦
def people1 = new People()
def people1 = new People(age: 15)
def people2 = new People(name: 'erdai')
def people3 = new People(age: 15,name: 'erdai')
18、使用 with 函數(shù)操作同一個對象的多個屬性和方法
with 函數(shù)接收一個閉包,閉包下面會講,閉包的參數(shù)就是當前調用的對象
class People{
def name
def age
void running(){
println '跑步'
}
}
//定義一個 people 對象
def people = new People()
//調用 with 函數(shù) 閉包參數(shù)即為peopeo 如果閉包不寫參數(shù),默認會有一個 it 參數(shù)
people.with{
name = "erdai"
age = 19
println "$name $age"
running()
}
//打印結果
erdai 19
跑步
19、異常捕獲
如果你實在不想關心 try 塊里拋出何種異常,你可以簡單的捕獲所有異常,并且可以省略異常類型:
//在 java 中我們會這樣寫
try {
// ...
} catch (Exception e) {
// do something
}
//在 Groovy 中,我們可以這樣寫
try {
// ...
} catch (any) {
// do something
}
上面 Groovy 的寫法其實就是省略了參數(shù)類型,實際上 any 的參數(shù)類型也是 Exception, 并不包括 Throwable ,如果你想捕獲所有的異常,你可以明確捕獲異常的參數(shù)類型
四、Groovy 閉包
在 Groovy 中,閉包非常的重要,因此單獨用一個模塊來講
1、閉包定義
引用 Groovy 官方對閉包的定義:A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. 翻譯過來就是:Groovy 中的閉包是一個開放的、匿名的代碼塊,它可以接受參數(shù)、返回值并將值賦給變量。 通俗的講,閉包可以作為方法的參數(shù)和返回值,也可以作為一個變量而存在,閉包本質上就是一段代碼塊,下面我們就由淺入深的來學習閉包
2、閉包聲明
1、閉包基本的語法結構:外面一對大括號,接著是申明參數(shù),參數(shù)類型可省略,在是一個 -> 箭頭號,最后就是閉包體里面的內容
2、閉包也可以不定義參數(shù),如果閉包沒定義參數(shù)的話,則隱含有一個參數(shù),這個參數(shù)名字叫 it
//1
{ params ->
//do something
}
//2
{
//do something
}
3、閉包調用
1、閉包可以通過 .call 方法來調用
2、閉包可以直接用括號+參數(shù)來調用
//定義一個閉包賦值給 closure 變量
def closure = { params1,params2 ->
params1 + params2
}
//閉包調用方式1: 閉包可以通過 .call 方法來調用
def result1 = closure('erdai ','666')
//閉包調用方式2: 閉包可以直接用括號+參數(shù)來調用
def result2 = closure.call('erdai ','777')
//打印值
println result1
println result2
//打印結果
erdai 666
erdai 777
//定義一個無參閉包
def closure1 = {
println('無定義參數(shù)閉包')
}
closure1() //或者調用 closure1.call()
//打印結果
無定義參數(shù)閉包
4、閉包進階
1)、閉包中的關鍵變量
每個閉包中都含有 this、owner 和 delegate 這三個內置對象,那么這三個三個內置對象有啥區(qū)別呢?我們用代碼去驗證一下
注意:
1、getThisObject() 方法 和 thisObject 屬性等同于 this
2、getOwner() 方法 等同于 owner
3、getDelegate() 方法 等同于 delegate
這些去看閉包的源碼你就會有深刻的體會
1、我們在 GroovyGrammar.groovy 這個腳本類中定義一個閉包打印這三者的值看一下:
//定義一個閉包
def outerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
//調用閉包
outerClosure.call()
//打印結果
this: variable.GroovyGrammar@39dcf4b0
owner: variable.GroovyGrammar@39dcf4b0
delegate: variable.GroovyGrammar@39dcf4b0
//證明當前三者都指向了GroovyGrammar這個腳本類對象
2、我們在這個 GroovyGrammar.groovy 這個腳本類中定義一個類,類中定義一個閉包,打印看下結果:
//定義一個 OuterClass 類
class OuterClass {
//定義一個閉包
def outerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
}
def outerClass = new OuterClass()
outerClass.outerClosure.call()
//打印結果如下:
this: variable.OuterClass@1992eaf4
owner: variable.OuterClass@1992eaf4
delegate: variable.OuterClass@1992eaf4
//結果證明這三者都指向了當前 OuterClass 類對象
3、我們在 GroovyGrammar.groovy 這個腳本類中,定義一個閉包,閉包中在定義一個閉包,打印看下結果:
def outerClosure = {
def innerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
innerClosure.call()
}
println outerClosure
outerClosure.call()
//打印結果如下
variable.GroovyGrammar$_run_closure4@64beebb7
this: variable.GroovyGrammar@5b58ed3c
owner: variable.GroovyGrammar$_run_closure4@64beebb7
delegate: variable.GroovyGrammar$_run_closure4@64beebb7
//結果證明 this 指向了當前GroovyGrammar這個腳本類對象 owner 和 delegate 都指向了 outerClosure 閉包對象
我們梳理一下上面的三種情況:
1、閉包定義在GroovyGrammar.groovy 這個腳本類中 this owner delegate 就指向這個腳本類對象
2、我在這個腳本類中創(chuàng)建了一個 OuterClass 類,并在他里面定義了一個閉包,那么此時 this owner delegate 就指向了 OuterClass 這個類對象
3、我在 GroovyGrammar.groovy 這個腳本類中定義了一個閉包,閉包中又定義了一個閉包,this 指向了當前GroovyGrammar這個腳本類對象, owner 和 delegate 都指向了 outerClosure 閉包對象
因此我們可以得到結論:
1、this 永遠指向定義該閉包最近的類對象,就近原則,定義閉包時,哪個類離的最近就指向哪個,我這里的離得近是指定義閉包的這個類,包含內部類
2、owner 永遠指向定義該閉包的類對象或者閉包對象,顧名思義,閉包只能定義在類中或者閉包中
3、delegate 和 owner 是一樣的,我們在閉包的源碼中可以看到,owner 會把自己的值賦給 delegate,但同時 delegate 也可以賦其他值
注意:在我們使用 this , owner , 和 delegate 的時候, this 和 owner 默認是只讀的,我們外部修改不了它,這點在源碼中也有體現(xiàn),但是可以對 delegate 進行操作
2)、閉包委托策略
下面我們就來對修改閉包的 delegate 進行實操:
//創(chuàng)建一個香蕉類
class Banana{
def name
}
//創(chuàng)建一個橘子類
class Orange{
def name
}
//定義一個香蕉對象
def banana = new Orange(name: '香蕉')
//定義一個橘子對象
def orange = new Orange(name: '橘子')
//定義一個閉包對象
def closure = {
//打印值
println delegate.name
}
//調用閉包
closure.call()
//運行一下,發(fā)現(xiàn)結果報錯了,如下
Caught: groovy.lang.MissingPropertyException: No such property: name for class: variable.GroovyGrammar
//大致意思就是GroovyGrammar這個腳本類對象沒有這個 name 對象
我們來分析下報錯的原因原因,分析之前我們要明白一個知識點:
閉包的默認委托策略是 OWNER_FIRST,也就是閉包會先從 owner 上尋找屬性或方法,找不到則在 delegate 上尋找
1、closure 這個閉包是生明在 GroovyGrammar 這個腳本類當中
2、根據(jù)我們之前學的知識,在不改變 delegate 的情況下 delegate 和 owner 是一樣的,都會指向 GroovyGrammar 這個腳本類對象
3、GroovyGrammar 這個腳本類對象,根據(jù)閉包默認委托策略,找不到 name 這個屬性
因此報錯了,知道了報錯原因,那我們就修改一下閉包的 delegate , 還是上面那段代碼,添加如下這句代碼:
//修改閉包的delegate
closure.delegate = orange
//我們在運行一下,打印結果:
橘子
此時閉包的 delegate 指向了 orange ,因此會打印 orange 這個對象的 name ,那么我們把 closure 的 delegate 改為 banana,肯定就會打印香蕉了
//修改閉包的delegate
closure.delegate = banana
//我們在運行一下,打印結果:
香蕉
3)、深入閉包委托策略
//定義一個 ClosureDepth 類
class ClosureDepth{
//定義一個變量 str1 賦值為 erdai666
def str1 = 'erdai666'
//定義一個閉包
def outerClosure = {
//定義一個變量 str2 賦值為 erdai777
def str2 = 'erdai777'
//打印str1 分析1
println str1
//閉包中在定義一個閉包
def innerClosure = {
//分析2
println str1
println str2
}
//調用內部這個閉包
innerClosure.call()
}
}
//創(chuàng)建 ClosureDepth 對象
def closureDepth = new ClosureDepth()
//調用外部閉包
closureDepth.outerClosure.call()
//運行程序,打印結果如下
erdai666
erdai666
erdai777
上面代碼注釋寫的很清楚,現(xiàn)在我們來重點分析下分析1和分析2處的打印值:
分析1:
分析1處打印了 str1 , 它處于 outerClosure 這個閉包中,此時 outerClosure 這個閉包的 owner , delegate 都指向了 ClosureDepth 這個類對象,因此 ClosureDepth 這個類對象的屬性和方法我們就都能調用到,因此分析1處會打印 erdai666
分析2:
分析2處打印了 str1和 str2,它處于 innerClosure 這個閉包中,此時 innerClosure 這個閉包的 owner 和 delegate 會指向 outerClosure 這個閉包對象,我們會發(fā)現(xiàn) outerClosure 有 str2 這個屬性,但是并沒有 str1 這個屬性,因此 outerClosure 這個閉包會向它的 owner 去尋找,因此會找到 ClosureDepth 這個類對象的 str1 屬性,因此打印的 str1 是ClosureDepth 這個類對象中的屬性,打印的 str2 是outerClosure 這個閉包中的屬性,所以分析2處的打印結果分別是 erdai666 erdai777
上面的例子中沒有顯式的給 delegate 設置一個接收者,但是無論哪層閉包都能成功訪問到 str1、str2 值,這是因為默認的解析委托策略在發(fā)揮作用,Groovy 閉包的委托策略有如下幾種:
- OWNER_FIRST:默認策略,首先從 owner 上尋找屬性或方法,找不到則在 delegate 上尋找
- DELEGATE_FIRST:和上面相反,首先從 delegate 上尋找屬性或者方法,找不到則在 owner 上尋找
- OWNER_ONLY:只在 owner 上尋找,delegate 被忽略
- DELEGATE_ONLY:和上面相反,只在 delegate 上尋找,owner 被忽略
- TO_SELF:高級選項,讓開發(fā)者自定義策略,必須要自定義實現(xiàn)一個 Closure 類,一般我們這種玩家用不到
下面我們就來修改一下閉包的委托策略,加深理解:
class People1{
def name = '我是People1'
def action(){
println '吃飯'
}
def closure = {
println name
action()
}
}
class People2{
def name = '我是People2'
def action(){
println '睡覺'
}
}
def people1 = new People1()
def people2 = new People2()
people1.closure.delegate = people2
people1.closure.call()
//運行下程序,打印結果如下:
我是People1
吃飯
what? 這是啥情況,我不是修改了 delegate 為 people2 了,怎么打印結果還是 people1 的?那是因為我們忽略了一個點,沒有修改閉包委托策略,他默認是 OWNER_FIRST ,因此我們修改一下就好了,還是上面這段代碼,添加一句代碼如下:
people1.closure.resolveStrategy = Closure.DELEGATE_FIRST
//運行下程序,打印結果如下:
我是People2
睡覺
到這里,相信你對閉包了解的差不多了,下面我們在看下閉包的源碼就完美了
4)、閉包 Closure 類源碼
僅貼出關鍵源碼
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
/**
* 熟悉的一堆閉包委托代理策略
*/
public static final int OWNER_FIRST = 0;
public static final int DELEGATE_FIRST = 1;
public static final int OWNER_ONLY = 2;
public static final int DELEGATE_ONLY = 3;
public static final int TO_SELF = 4;
/**
* 閉包對應的三個委托對象 thisObject 對應的就是 this 屬性,都是用 private 修飾的,外界訪問不到
*/
private Object delegate;
private Object owner;
private Object thisObject;
/**
* 閉包委托策略
*/
private int resolveStrategy;
/**
* 在閉包的構造方法中:
* 1、將 resolveStrategy 賦值為0,也是就默認委托策略OWNER_FIRST
* 2、thisObject ,owner ,delegate都會被賦值,delegate 賦的是 owner的值
*/
public Closure(Object owner, Object thisObject) {
this.resolveStrategy = 0;
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
CachedClosureClass cachedClass = (CachedClosureClass)ReflectionCache.getCachedClass(this.getClass());
this.parameterTypes = cachedClass.getParameterTypes();
this.maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters();
}
/**
* thisObject 只提供了 get 方法,且 thisObject 是用 private 修飾的,因此 thisObject 即 this 只讀
*/
public Object getThisObject() {
return this.thisObject;
}
/**
* owner 只提供了 get 方法,且 owner 是用 private 修飾的,因此 owner 只讀
*/
public Object getOwner() {
return this.owner;
}
/**
* delegate 提供了 get 和 set 方法,因此 delegate 可讀寫
*/
public Object getDelegate() {
return this.delegate;
}
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
/**
* 熟悉的委托策略設置
*/
public void setResolveStrategy(int resolveStrategy) {
this.resolveStrategy = resolveStrategy;
}
public int getResolveStrategy() {
return resolveStrategy;
}
}
到這里閉包相關的知識點就都講完了,但是,但是,但是,重要的事情說三遍:我們使用閉包的時候,如何去確定閉包的參數(shù)呢?,這個真的很蛋疼,作為 Android 開發(fā)者,在使用 AndroidStudio 進行 Gradle 腳本編寫的時候,真的是非常不友好,上面我講了可以使用一個小技巧去解決這個問題,但是這種情況是在你知道要使用一個 Api 的情況下,比如你知道 Map 的 each 方法可以遍歷,但是你不知道參數(shù),這個時候就可以去使用。那如果你連 Api 都不知道使用,那就更加不知道閉包的參數(shù)了,因此要解決這種情況,我們就必須去查閱 Groovy 官方文檔:
http://www.groovy-lang.org/api.html
http://docs.groovy-lang.org/latest/html/groovy-jdk/index-all.html
五、Groovy數(shù)據(jù)結構
通過這個模塊的學習,我會結合具體的例子來說明如何查閱文檔來確定閉包中的參數(shù),在講 Map 的時候我會講到
Groovy 常用的數(shù)據(jù)結構有如下 四種:
- 1)、數(shù)組
- 2)、List
- 3)、Map
- 4)、Range
1、數(shù)組
在 Groovy 中使用 [ ] 表示的是一個 List 集合,如果要定義 Array 數(shù)組,我們就必須強制指定為一個數(shù)組的類型
//在 Java 中,我們一般會這樣去定義一個數(shù)組
String[] javaArray = ["Java", "Groovy", "Android"]
//在 Groovy 中,我們一般會使用 as 關鍵字定義數(shù)組
def groovyArray = ["Java", "Groovy", "Android"] as String[]
2、List
1)、列表集合定義
1、List 即列表集合,對應 Java 中的 List 接口,一般用 ArrayList 作為真正的實現(xiàn)類
2、定義一個列表集合的方式有點像 Java 中定義數(shù)組一樣
3、集合元素可以接收任意的數(shù)據(jù)類型
//在 Groovy 中定義的集合默認就是對應于 Java 中 ArrayList 集合
def list1 = [1,2,3]
//打印 list 類型
print list1.class
//打印結果
class java.util.ArrayList
//集合元素可以接收任意的數(shù)據(jù)類型
def list2 = ['erdai666', 1, true]
那么問題來了,如果我想定義一個 LinkedList 集合,要怎么做呢?有兩種方式:
1、通過 Java 的強類型方式去定義
2、通過 as 關鍵字來指定
//方式1:通過 Java 的強類型方式去定義
LinkedList list3 = [4, 5, 6]
//方式2:通過 as 關鍵字來指定
def list4 = [1, 2, 3] as LinkedList
2)、列表集合增刪改查
def list = [1,2,3]
//-------------------------- 增加元素 ---------------------------------
//有以下幾種方式
list.add(20)
list.leftShift(20)
list << 20
//-------------------------- 刪除元素 ---------------------------------
//根據(jù)下標移除元素
list.remove(0)
//-------------------------- 修改元素 ---------------------------------
//根據(jù)下標修改元素
list[0] = 100
//-------------------------- 查詢元素 ---------------------------------
//調用閉包的 find 方法,方法中接收一個閉包,閉包的參數(shù)就是 list 中的元素
list.find {
println it
}
列表集合 Api 挺多的,對于一些其他Api,使用到的時候自行查閱文檔就好了,我會在下面講 Map 的時候演示查閱 Api 文檔確定閉包的參數(shù)
3、Map
1)、定義
1、Map 表示鍵-值表,其底層對應 Java 中的 LinkedHashMap
2、Map 變量由[:]定義,冒號左邊是 key,右邊是 Value。key 必須是字符串,value 可以是任何對象
3、Map 的 key 可以用 '' 或 "" 或 ''' '''包起來,也可以不用引號包起來
def map = [a: 1, 'b': true, "c" : "Groovy", '''d''' : '''ddd''']
2)、Map 常用操作
這里列舉一些 Map 的常用操作,一些其他的 Api 使用到的時候自行查閱文檔就好了
//---------------------------- Map 中元素訪問操作 ----------------
/**
* 有如下三種方式:
* 1、map.key
* 2、map[key]
* 3、map.get(ket)
*/
println map.a
println map['b']
println map.get('c')
//打印結果
1
true
Groovy
//---------------------------- Map 中添加和修改元素 -------------------
//如果當前 key 在 map 中不存在,則添加該元素,如果存在則修改該元素
map.put('key','value')
map['key'] = "value"
3)、Map 遍歷,演示查閱官方文檔
現(xiàn)在我要去遍歷 map 中的元素,但是我不知道它的 Api 是啥,那這個時候就要去查官方 Api 文檔了:
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html

我們查到 Map 有 each 和 eachWithIndex 這兩個 Api 可以去執(zhí)行遍歷操作,接著點進去看一眼


通過官方文檔我們可以發(fā)現(xiàn): each 和 eachWithIndex 的閉包參數(shù)還是不確定的,如果我們使用 each 方法,如果傳遞給閉包是一個參數(shù),那么它就把 entry 作為參數(shù),如果我們傳遞給閉包是兩個參數(shù),那么它就把 key 和 value 作為參數(shù),eachWithIndex 比 each 多了個 index 下標而已.
那么我們現(xiàn)在就使用以下這兩個 Api :
//下面為了打印輸出的格式清晰,做了一些額外的操作
def map = [a: 1, 'b': true, "c" : "Groovy", '''d''' : '''ddd''']
map.each {
print "$it.key $it.value \t"
}
println()
map.each {key,value ->
print "$key $value \t"
}
println()
map.eachWithIndex {entry,index ->
print "$entry.key $entry.value $index \t"
}
println()
map.eachWithIndex { key,value,index ->
print "$key $value $index \t"
}
//打印結果
a 1 b true c Groovy d ddd
a 1 b true c Groovy d ddd
a 1 0 b true 1 c Groovy 2 d ddd 3
a 1 0 b true 1 c Groovy 2 d ddd 3
4、Range
Range 表示范圍,它其實是 List 的一種拓展。其由 begin 值 + 兩個點 + end 值表示。如果不想包含最后一個元素,則 begin 值 + 兩個點 + < + end 表示。我們可以通過 aRange.from 與 aRange.to 來獲對應的邊界元素,實際操作感受一下:
//定義一個兩端都是閉區(qū)間的范圍
def range = 1..10
range.each {
print it + " "
}
//打印值
1 2 3 4 5 6 7 8 9 10
//如果不想包含最后一個元素
def range1 = 1..<10
range1.each {
print it + " "
}
//打印結果
1 2 3 4 5 6 7 8 9
//打印頭尾邊界元素
println "$range1.from $range1.to"
//打印結果
1 9
六、Groovy 文件處理
1、IO
一些 IO 常用的 Api 操作,我們直接上代碼看效果,代碼會寫上清晰的注釋:
準備工作:我在當前腳本文件的同級目錄下創(chuàng)建一個 testFile.txt 文件,里面隨便先寫入一些字符串,如下圖:

下面我們開始來操作這個文件,為了閉包的可讀性,我會在閉包上加上類型和參數(shù):
//-------------------------------1、文件定位 --------------------------------
def file = new File('testFile.txt')
//-----------------------2、使用 eachLine Api 每次讀取一行, 閉包參數(shù)是每一行的字符串------------
file.eachLine { String line ->
println line
}
//打印結果
erdai666
erdai777
erdai888
//------------------------3、獲取輸入流,輸出流讀文件和寫文件---------------------------------
//獲取輸入流讀取文件的每一行
//1
file.withInputStream { InputStream inputStream ->
inputStream.eachLine { String it ->
println it
}
}
//2
file.withReader { BufferedReader it ->
it.readLines().each { String it ->
println it
}
}
//打印結果
erdai666
erdai777
erdai888
//獲取輸出流將字符串寫入文件 下面這兩種方式寫入的文件內容會把之前的內容給覆蓋
//1
file.withOutputStream { OutputStream outputStream ->
outputStream.write("erdai999".getBytes())
}
//2
file.withWriter { BufferedWriter it ->
it.write('erdai999')
}
//------------------------4、通過輸入輸出流實現(xiàn)文件拷貝功能---------------------------------
//1、通過 withOutputStream withInputStream 實現(xiàn)文件拷貝
def targetFile = new File('testFile1.txt')
targetFile.withOutputStream { OutputStream outputStream ->
file.withInputStream { InputStream inputStream ->
outputStream << inputStream
}
}
//2、通過 withReader、withWriter 實現(xiàn)文件拷貝
targetFile.withWriter {BufferedWriter bufferedWriter ->
file.withReader {BufferedReader bufferedReader ->
bufferedReader.eachLine {String line ->
bufferedWriter.write(line + "\r\n")
}
}
}
2、XML 文件操作
1)、解析 XML 文件
//定義一個帶格式的 xml 字符串
def xml = '''
<response>
<value>
<books id="1" classification="android">
<book available="14" id="2">
<title>第一行代碼</title>
<author id="2">郭霖</author>
</book>
<book available="13" id="3">
<title>Android開發(fā)藝術探索</title>
<author id="3">任玉剛</author>
</book>
</books>
</value>
</response>
'''
//創(chuàng)建 XmlSlurper 類對象,解析 XML 文件主要借助 XmlSlurper 這個類
def xmlSlurper = new XmlSlurper()
//解析 mxl 返回 response 根結點對象
def response = xmlSlurper.parseText(xml)
//打印一些結果
println response.value.books[0].book[0].title.text()
println response.value.books[0].book[0].author.text()
//打印結果
第一行代碼
郭霖
//1、使用迭代器解析
response.value.books.each{ books ->
books.book.each{ book ->
println book.title
println book.author
}
}
//打印結果
第一行代碼
郭霖
Android開發(fā)藝術探索
任玉剛
//2、深度遍歷 XML 數(shù)據(jù)
def str1 = response.depthFirst().findAll { book ->
return book.author == '郭霖'
}
println str1
//打印結果
[第一行代碼郭霖]
//3、廣度遍歷 XML 數(shù)據(jù)
def str2 = response.value.books.children().findAll{ node ->
node.name() == 'book' && node.@id == '2'
}.collect { node ->
"$node.title $node.author"
}
println str2
//打印結果
[第一行代碼 郭霖]
2)、生成 XML 文件
上面我們使用 XmlSlurper 這個類解析了 XML,現(xiàn)在我們借助 MarkupBuilder 來生成 XML ,代碼如下:
/**
* <response>
* <value>
* <books id="1" classification="android">
* <book available="14" id="2">
* <title>第一行代碼</title>
* <author id="2">郭霖</author>
* </book>
* <book available="13" id="3">
* <title>Android開發(fā)藝術探索</title>
* <author id="3">任玉剛</author>
* </book>
* </books>
* </value>
* </response>
*/
//方式1:通過下面這種方式 就可以實現(xiàn)上面的效果,但是這種方式有個弊端,數(shù)據(jù)都是寫死的
def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw)
xmlBuilder.response{
value{
books(id: '1',classification: 'android'){
book(available: '14',id: '2'){
title('第一行代碼')
author(id: '2' ,'郭霖')
}
book(available: '13',id: '3'){
title('Android開發(fā)藝術探索')
author(id: '3' ,'任玉剛')
}
}
}
}
println sw
//方式2:將 XML 數(shù)據(jù)對應創(chuàng)建相應的數(shù)據(jù)模型,就像我們解析 Json 創(chuàng)建相應的數(shù)據(jù)模型是一樣的
//創(chuàng)建 XML 對應數(shù)據(jù)模型
class Response {
def value = new Value()
class Value {
def books = new Books(id: '1', classification: 'android')
class Books {
def id
def classification
def book = [new Book(available: '14', id: '2', title: '第一行代碼', authorId: 2, author: '郭霖'),
new Book(available: '13', id: '3', title: 'Android開發(fā)藝術探索', authorId: 3, author: '任玉剛')]
class Book {
def available
def id
def title
def authorId
def author
}
}
}
}
//創(chuàng)建 response 對象
def response = new Response()
//構建 XML
xmlBuilder.response{
value{
books(id: response.value.books.id,classification: response.value.books.classification){
response.value.books.book.each{
def book1 = it
book(available: it.available,id: it.id){
title(book1.title)
author(authorId: book1.authorId,book1.author)
}
}
}
}
}
println sw
3、Json 解析
Json解析主要是通過 JsonSlurper 這個類實現(xiàn)的,這樣我們在寫插件的時候就不需要額外引入第三方的 Json 解析庫了,其示例代碼如下所示:
//發(fā)送請求獲取服務器響應的數(shù)據(jù)
def response = getNetWorkData("https://www.wanandroid.com/banner/json")
println response.data[0].desc
println response.data[0].imagePath
def getNetWorkData(String url){
def connect = new URL(url).openConnection()
connect.setRequestMethod("GET")
//這個會阻塞線程 在Android中不能這樣操作 但是在桌面程序是可以的
connect.connect()
def response = connect.content.text
//json轉實體對象
def jsonSlurper = new JsonSlurper()
jsonSlurper.parseText(response)
}
//打印結果
扔物線
https://wanandroid.com/blogimgs/8a0131ac-05b7-4b6c-a8d0-f438678834ba.png
7、總結
在本篇文章中,我們主要介紹了以下幾個部分:
1、一些關于 Gradle ,Groovy 的問題
2、搭建 Groovy 開發(fā)環(huán)境,創(chuàng)建一個 Groovy 工程
3、講解了 Groovy 的一些基礎語法
4、對閉包進行了深入的講解
5、講解了 Groovy 中的數(shù)據(jù)結構和常用 Api 使用,并以 Map 舉例,查閱官方文檔去確定 Api 的使用和閉包的參數(shù)
6、講解了 Groovy 文件相關的處理
學習了 Groovy ,對于我們后續(xù)自定義 Gradle 插件邁出了關鍵的一步。其次如果你學習過 Kotlin ,你會發(fā)現(xiàn),它們的語法非常的類似,因此對于后續(xù)學習 Kotlin 我們也可以快速去上手。
參考和推薦
深度探索 Gradle 自動化構建技術(二、Groovy 筑基篇
Gradle 爬坑指南 -- 概念初解、Grovvy 語法、常見 API
慕課網(wǎng)之Gradle3.0自動化項目構建技術精講+實戰(zhàn)
全文到此,原創(chuàng)不易,歡迎點贊,收藏,評論和轉發(fā),你的認可是我創(chuàng)作的動力