深度探索 Gradle 自動化構建技術(二、Groovy 築基篇)

前言

成爲一名優秀的Android開發,需要一份完備的 知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

Groovy 作爲 Gradle 這一強大構建工具的核心語言,其重要性不言而喻,但是 Groovy 本身是十分複雜的,要想全面地掌握它,我想幾十篇萬字長文也無法將其徹底描述。所幸的是,在 Gradle 領域中涉及的 Groovy 知識都是非常基礎的,因此,本篇文章的目的是爲了在後續深入探索 Gradle 時做好一定的基礎儲備。

一、DSL 初識

DSL(domain specific language),即領域特定語言,例如:Matliba、UML、HTML、XML 等等 DSL 語言。可以這樣理解,Groovy 就是 DSL 的一個分支。

特點

  • 1)、解決特定領域的專有問題。
  • 2)、它與系統編程語言走的是兩個極端,系統編程語言是希望解決所有的問題,比如 Java 語言希望能做 Android 開發,又希望能做服務器開發,它具有橫向擴展的特性。而 DSL 具有縱向深入解決特定領域專有問題的特性。

總的來說,DSL 的 核心思想 就是:“求專不求全,解決特定領域的問題”

二、Groovy 初識

1、Groovy 的特點

Groovy 的特點具有如下 三點:

  • 1)、Groovy 是一種基於 JVM 的敏捷開發語言。
  • 2)、Groovy 結合了 Python、Ruby 和 Smalltalk 衆多腳本語言的許多強大的特性。
  • 3)、Groovy 可以與 Java 完美結合,而且可以使用 Java 所有的庫。

那麼,在已經有了其它腳本語言的前提下,爲什麼還要製造出 Grvooy 語言呢?

因爲 Groovy 語言相較其它編程語言而言,其 入門的學習成本是非常低的,因爲它的語法就是對 Java 的擴展,所以,我們可以用學習 Java 的方式去學習 Groovy。

2、Groovy 語言本身的特性

其特性主要有如下 三種:

  • 1)、語法上支持動態類型,閉包等新一代語言特性。並且,Groovy 語言的閉包比其它所有語言類型的閉包都要強大。
  • 2)、它可以無縫集成所有已經存在的 Java 類庫,因爲它是基於 JVM 的。
  • 3)、它即可以支持面向對象編程(基於 Java 的擴展),也可以支持面向過程編程(基於衆多腳本語言的結合)。

需要注意的是,在我們使用 Groovy 進行 Gradle 腳本編寫的時候,都是使用的面向過程進行編程的

3、Groovy 的優勢

Groovy 的優勢有如下 四種:

  • 1)、它是一種更加敏捷的編程語言:在語法上構建除了非常多的語法糖,許多在 Java 層需要寫的代碼,在 Groovy 中是可以省略的。因此,我們可以用更少的代碼實現更多的功能。
  • 2)、入門簡單,但功能非常強大。
  • 3)、既可以作爲編程語言也可以作爲腳本語言
  • 4)、熟悉掌握 Java 的同學會非常容易掌握 Groovy。

4、Groovy 包的結構

Groovy 官方網址

從官網下載好 Groovy 文件之後,我們就可以看到 Groovy 的目錄結構,其中我們需要 重點關注 bin 和 doc 這個兩個文件夾

bin 文件夾

bin 文件夾的內容如下所示:

這裏我們瞭解下三個重要的可執行命令文件,如下所示:

  • 1)、groovy 命令類似於 Java 中的 java 命令,用於執行 groovy Class 字節碼文件。
  • 2)、groovyc 命令類似於 Java 中的 javac 命令,用於將 groovy 源文件編譯成 groovy 字節碼文件。
  • 3)、groovysh 命令是用來解釋執行 groovy 腳本文件的。

doc 文件夾

doc 文件夾的下面有一個 html 文件,其中的內容如下所示:

這裏的 api 和 documentation 是我們需要重點關注的,其作用分別如下所示:

  • api:groovy 中爲我們提供的一系列 API 及其 說明文檔。
  • documentation:groovy 官方爲我們提供的一些教程。

5、Groovy 中的關鍵字

下面是 Groovy 中所有的關鍵字,命名時尤其需要注意,如下所示:

asassertbreakcasecatchclassconstcontinuedefdefault
doelseenumextendsfalsefinallyforgotoifimplements
importininstanceofinterfacenewnullpackagereturnsuper
switchthisthrowthrowstraittruetrywhile
複製代碼

6、Groovy && Java 差異學習

1)、getter / setter

對於每一個 field,Groovy 都會⾃動創建其與之對應的 getter 與 setter 方法,從外部可以直接調用它,並且 在使⽤ object.fieldA 來獲取值或者使用 object.fieldA = value 來賦值的時候,實際上會自動轉而調⽤ object.getFieldA() 和 object.setFieldA(value) 方法

如果我們不想調用這個特殊的 getter 方法時則可以使用 .@ 直接域訪問操作符

2)、除了每行代碼不用加分號外,Groovy 中函數調用的時候還可以不加括號。

需要注意的是,我們在使用的時候,如果當前這個函數是 Groovy API 或者 Gradle API 中比較常用的,比如 println,就可以不帶括號。否則還是帶括號。不然,Groovy 可能會把屬性和函數調用混淆

3)、Groovy 語句可以不用分號結尾。

4)、函數定義時,參數的類型也可以不指定。

5)、Groovy 中函數的返回值也可以是無類型的,並且無返回類型的函數,其內部都是按返回 Object 類型來處理的。

6)、當前函數如果沒有使用 return 關鍵字返回值,則會默認返回 null,但此時必須使用 def 關鍵字。

7)、在 Groovy 中,所有的 Class 類型,都可以省略 .class。

8)、在 Groovy 中,== 相當於 Java 的 equals,,如果需要比較兩個對象是否是同一個,需要使用 .is()。

9)、Groovy 非運算符如下:

assert (!"android") == false                      
複製代碼

10)、Groovy 支持 ** 次方運算符,代碼如下所示:

assert  2 ** 3 == 8
複製代碼

11)、判斷是否爲真可以更簡潔:

    if (android) {}
複製代碼

12)、三元表達式可以更加簡潔:

// 省略了name
def result = name ?: "Unknown"
複製代碼

13)、簡潔的非空判斷

println order?.customer?.address
複製代碼

14)、使用 assert 來設置斷言,當斷言的條件爲 false 時,程序將會拋出異常。

15)、可以使用 Number 類去替代 float、double 等類型,省去考慮精度的麻煩。

16)、switch 方法可以同時支持更多的參數類型。

注意,swctch 可以匹配列表當中任一元素,示例代碼如下所示:

// 輸出 ok
def num = 5.21
switch (num) {
   case [5.214"list"]:
       return "ok"
       break
   default:
       break
}
複製代碼

三、Groovy 基礎語法

Groovy 的基礎語法主要可以分爲以下 四個部分:

  • 1)、Groovy 核心基礎語法。
  • 2)、Groovy 閉包。
  • 3)、Groovy 數據結構。
  • 4)、Groovy 面向對象

1、Groovy 核心基礎語法

Groovy 中的變量

變量類型

Groovy 中的類型同 Java 一樣,也是分爲如下 兩種:

  • 1)、基本類型。
  • 2)、對象類型。

但是,其實 Groovy 中並沒有基本類型,Groovy 作爲動態語言, 在它的世界中,所有事物都是對象,就如 Python、Kotlin 一樣:所有的基本類型都是屬於對象類型。爲了驗證這個 Case,我們可以新建一個 groovy 文件,創建一個 int 類型的變量並輸出它,結果如下圖所示:

可以看到,上面的輸出結果爲 'class java.lang.Integer',因此可以驗證我們的想法是正確的。實際上,Groovy 的編譯器會將所有的基本類型都包裝成對象類型

變量定義

groovy 變量的定義與 Java 中的方式有比較大的差異,對於 groovy 來說,它有 兩種定義方式,如下所示:

  • 1)、強類型定義方式:groovy 像 Java 一樣,可以進行強類型的定義,比如上面直接定義的 int 類型的 x,這種方式就稱爲強類型定義方式,即在聲明變量的時候定義它的類型。
  • 2)、弱類型定義方式:不需要像強類型定義方式一樣需要提前指定類型,而是通過 def 關鍵字來定義我們任何的變量,因爲編譯器會根據值的類型來爲它進行自動的賦值。

下面,我們就使用 def 關鍵字來定義一系列的變量,並輸出它們的類型,來看看是否編譯器會識別出對應的類型,其結果如下圖所示:

可以看到,編譯器的確會自動自動推斷對應的類型。

那麼,這兩種方式應該分別在什麼樣的場景中使用呢?

如果這個變量就是用於當前類或文件,而不會用於其它類或應用模塊,那麼,建議使用 def 類型,因爲在這種場景下弱類型就足夠了

但是,如果你這個類或變量要用於其它模塊的,建議不要使用 def,還是應該使用 Java 中的那種強類型定義方式,因爲使用強類型的定義方式,它不能動態轉換爲其它類型,它能夠保證外界傳遞進來的值一定是正確的。如果你這個變量要被外界使用,而你卻使用了 def 類型來定義它,那外界需要傳遞給你什麼纔是正確的呢?這樣會使調用方很疑惑。

如果此時我們在後面的代碼中改變上圖中 x1 的值爲 String 類型,那麼 x1 又會被編譯器推斷爲 String 類型,如下圖所示:

於是我們可以猜測到,其實使用 def 關鍵字定義出來的變量就是 Obejct 類型。

Groovy 中的字符串

Groovy 中的字符串與 Java 中的字符串有比較大的不同,所以這裏我們需要着重瞭解一下。

Groovy 中的字符串除了繼承了 Java 中傳統 String 的使用方式之前,還 新增 了一個 GString 類型,它的使用方式至少有七、八種,但是常用的有三種定義方式。此外,在 GString 中新增了一系列的操作符,這能夠讓我們對 String 類型的變量有 更便捷的操作。最後,在 GString 中還 新增 了一系列好用的 API,我們也需要着重學習一下。

Groovy 中常用的三種字符串定義方式

在 Groovy 中有 三種常用 的字符串定義方式,如下所示:

  • 1)、單引號 '' 定義的字符串
  • 2)、雙引號 "" 定義的字符串
  • 3)、三引號 '""' 定義的字符串

首先,需要說明的是,'不管是單引號、雙引號還是三引號,它們的類型都是 java.lang.String'。

那麼,單引號與三引號的區別是什麼呢?

既生瑜何生亮,其實不然。當我們編寫的單引號字符串中有轉義字符的時候,需要添加 '',並且,當字符串需要具備多行格式的時候,強行將單引號字符串分成多行格式會變成由 '+' 號組成的字符串拼接格式

那麼,雙引號定義的變量又與單引號、三引號有什麼區別呢?

雙引號不同與單、三引號,它定義的是一個可擴展的變量。這裏我們先看看兩種雙引號的使用方式,如下圖所示:

在上圖中,第一個定義的 author 字符串就是常規的 String 類型的字符串,而下面定義的 study 字符串就是可擴展的字符串,因爲它裏面使用了 '${author}' 的方式引用了 author 變量的內容。而且,從其最後的類型輸出可以看到,可擴展的類型就是 'org.codehaus.groovy.runtime.GStringImpl' 類型的。

需要注意的是,可擴展的字符串是可以擴展成爲任意的表達式,例如數學運算,如下圖所示:

有了 Groovy 的這種可擴展的字符串,我們就可以 避免 Java 中字符串的拼接操作,提升 Java 程序運行時的性能

那麼,既然有 String 和 GString 兩種類型的字符串,它們在相互賦值的場景下需要不需要先強轉再賦值呢?

這裏,我們可以寫一個 小栗子🌰 來看看實際的情況,如下圖所示:

可以看到,我們將 success 字符串傳入了 come 方法,但是最終得到的類型爲 result,所以,可以說明 編譯器可以幫我們自動在 String 和 GString 之間相互轉換,我們在編寫的時候並不需要太過關注它們的區別

2、Groovy 閉包(Closure)

閉包的本質其實就是一個代碼塊,閉包的核心內容可以歸結爲如下三點:

  • 1)、閉包概念
    • 定義
    • 閉包的調用
  • 2)、閉包參數
    • 普通參數
    • 隱式參數
  • 3)、閉包返回值
    • 總是有返回值

閉包的調用

clouser.call()
clouser() 
def xxx = { paramters -> code } 
def xxx = { 純 code }
複製代碼

從 C/C++ 語言的角度看,閉包和函數指針很像,閉包可以通過 .call 方法來調用,也可以直接調用其構造函數,代碼如下所示:

閉包對象.call(參數)
閉包對象(參數)
複製代碼

如果閉包沒定義參數的話,則隱含有一個參數,這個參數名字叫 it,和 this 的作用類 似。it 代表閉包的參數。表示閉包中沒有參數的示例代碼:

def noParamClosure = { -> true }
複製代碼

注意點:省略圓括號

函數最後一個參數都是一個閉包,類似於回調函數的用法,代碼如下所示:

task JsonChao {
    doLast ({
        println "love is peace~"
    }
})

// 似乎好像doLast會立即執行一樣
task JsonChao {
    doLast {
        println "love is peace~"
    }
}
複製代碼

閉包的用法

閉包的常見用法有如下 四種:

  • 1)、與基本類型的結合使用。
  • 2)、與 String 類的結合使用。
  • 3)、與數據結構的結合使用。
  • 4)、與文件等結合使用。

閉包進階

  • 1)、閉包的關鍵變量
    • this
    • owner
    • delegate
  • 2)、閉包委託策略

閉包的關鍵變量

this 與 owner、delegate

其差異代碼如下代碼所示:

def scrpitClouser = {
    // 代表閉包定義處的類
    printlin "scriptClouser" this:" + this 
    // 代表閉包定義處的類或者對象
    printlin "
scriptClouser" this:" + owner
    // 代表任意對象,默認與 ownner 一直
    printlin "scriptClouser" this:" + delegate 
}
    
// 輸出都是 scrpitClouse 對象
scrpitClouser.call()

def nestClouser = {
    def innnerClouser = {
        // 代表閉包定義處的類
        printlin "
scriptClouser" this:" + this 
        // 代表閉包定義處的類或者對象
        printlin "scriptClouser" this:" + owner
        // 代表任意對象,默認與 ownner 一直
        printlin "
scriptClouser" this:" + delegate 
    }
    innnerClouser.call()
}
    
// this 輸出的是 nestClouser 對象,而 owner 與 delegate 輸出的都是 innnerClouser 對象
nestClouser.call()
複製代碼

可以看到,如果我們直接在類、方法、變量中定義一個閉包,那麼這三種關鍵變量的值都是一樣的,但是,如果我們在閉包中又嵌套了一個閉包,那麼,this 與 owner、delegate 的值就不再一樣了。換言之,this 還會指向我們閉包定義處的類或者實例本身,而 owner、delegate 則會指向離它最近的那個閉包對象

delegate 與 this、owner 的差異

其差異代碼如下代碼所示:

def nestClouser = {
    def innnerClouser = {
        // 代表閉包定義處的類
        printlin "scriptClouser" this:" + this 
        // 代表閉包定義處的類或者對象
        printlin "
scriptClouser" this:" + owner
        // 代表任意對象,默認與 ownner 一致
        printlin "scriptClouser" this:" + delegate 
    }
    
    // 修改默認的 delegate
    innnerClouser.delegate = p 
    innnerClouser.call()
}

nestClouser.call()
複製代碼

可以看到,delegate 的值是可以修改的,並且僅僅當我們修改 delegate 的值時,delegate 的值纔會與 ownner 的值不一樣

閉包的委託策略

其示例代碼如下所示:

def stu = new Student()
def tea = new Teacher()
stu.pretty.delegate = tea
// 要想使 pretty 閉包的 delegate 修改生效,必須選擇其委託策略爲 Closure.DELEGATE_ONLY,默認是 Closure.OWNER_FIRST。
stu.pretty.resolveStrategy = Closure.DELEGATE_ONLY
println stu.toString()
複製代碼

需要注意的是,要想使上述 pretty 閉包的 delegate 修改生效,必須選擇其委託策略爲 Closure.DELEGATE_ONLY,默認是 Closure.OWNER_FIRST 的。

3、Groovy 數據結構

Groovy 常用的數據結構有如下 四種:

  • 1)、數組
  • 2)、List
  • 3)、Map
  • 4)、Range

數組的使用和 Java 語言類似,最大的區別可能就是定義方式的擴展,如下代碼所示:

// 數組定義
def array = [12345as int[]
int[] array2 = [12345]
複製代碼

下面,我們看看其它三種數據結構。

1、List

即鏈表,其底層對應 Java 中的 List 接口,一般用 ArrayList 作爲真正的實現類,List 變量由[]定義,其元素可以是任何對象

鏈表中的元素可以通過索引存取,而且 不用擔心索引越界。如果索引超過當前鏈表長度,List 會自動往該索引添加元素。下面,我們看看 Map 最常使用的幾個操作。

1)、排序

def test = [100"hello"true]
// 左移位表示向List中添加新元素
test << 200
// list 定義
def list = [12345]
// 排序
list.sort()
// 使用自己的排序規則
sortList.sort { a, b -> 
    a == b ?0 : 
            Math.abs(a) < Math.abs(b) ? 1 : -1

複製代碼

2)、添加

// 添加
list.add(6)
list.leftShift(7)
list << 8
複製代碼

3)、刪除

// 刪除
list.remove(7)
list.removeAt(7)
list.removeElement(6)
list.removeAll { return it % 2 == 0 }
複製代碼

4)、查找

// 查找
int result = findList.find { return it % 2 == 0 }
def result2 = findList.findAll { return it % 2 != 0 }
def result3 = findList.any { return it % 2 != 0 }
def result4 = findList.every { return it % 2 == 0 }
複製代碼

5)、獲取最小值、最大值

// 最小值、最大值
list.min()
list.max(return Math.abs(it))
複製代碼

6)、統計滿足條件的數量

// 統計滿足條件的數量
def num = findList.count { return it >= 2 }
複製代碼

Map

表示鍵-值表,其 底層對應 Java 中的 LinkedHashMap

Map 變量由[:]定義,冒號左邊是 key,右邊是 Value。key 必須是字符串,value 可以是任何對象。另外,key 可以用 '' 或 "" 包起來,也可以不用引號包起來。下面,我們看看 Map 最常使用的幾個操作。

1)、存取

其示例代碼如下所示:

aMap.keyName
aMap['keyName']
aMap.anotherkey = "i am map"
aMap.anotherkey = [a: 1b: 2]
複製代碼

2)、each 方法

如果我們傳遞的閉包是一個參數,那麼它就把 entry 作爲參數。如果我們傳遞的閉包是 2 個參數,那麼它就把 key 和 value 作爲參數。

def result = ""
[a:1b:2].each { key, value -> 
    result += "$key$value" 
}
    
assert result == "a1b2"
    
def socre = ""
[a:1b:2].each { entry -> 
    result += entry
}
    
assert result == "a=1b=2"
複製代碼

3)、eachWithIndex 方法

如果閉包採用兩個參數,則將傳遞 Map.Entry 和項目的索引(從零開始的計數器);否則,如果閉包採用三個參數,則將傳遞鍵,值和索引。

def result = ""
[a:1b:3].eachWithIndex { key, value, index -> result += "$index($key$value)" }
assert result == "0(a1)1(b3)"

def result = ""
[a:1b:3].eachWithIndex { entry, index -> result += "$index($entry)" }
assert result == "0(a=1)1(b=3)"
複製代碼

4)、groupBy 方法

按照閉包的條件進行分組,代碼如下所示:

def group = students.groupBy { def student ->
    return student.value.score >= 60 ? '及格' : '不及格'
}
複製代碼

5)、findAll 方法

它有兩個參數,findAll 會將 Key 和 Value 分別傳進 去。並且,如果 Closure 返回 true,表示該元素是自己想要的,如果返回 false 則表示該元素不是自己要找的。

Range

表示範圍,它其實是 List 的一種拓展。其由 begin 值 + 兩個點 + end 值表示。如果不想包含最後一個元素,則 begin 值 + 兩個點 + < + end 表示。我們可以通過 aRange.from 與 aRange.to 來獲對應的邊界元素

如果需要了解更多的數據結構操作方法,我們可以直接查 Groovy API 詳細文檔 即可。

4、Groovy 面向對象

如果不聲明 public/private 等訪問權限的話,Groovy 中類及其變量默認都是 public 的

1)、元編程(Groovy 運行時)

Groovy 運行時的邏輯處理流程圖如下所示:

爲了更好的講解元編程的用法,我們先創建一個 Person 類並調用它的 cry 方法,代碼如下所示:

// 第一個 groovy 文件中
def person = new Person(name: 'Qndroid'age: 26)
println person.cry()

// 第二個 groovy 文件中
class Person implements Serializable {

    String name

    Integer age

    def increaseAge(Integer years) {
        this.age += years
    }

     /**
      * 一個方法找不到時,調用它代替
      * @param name
      * @param args
      * @return
      */

     def invokeMethod(String name, Object args) {

        return "the method is ${name}, the params is ${args}"
    }


    def methodMissing(String name, Object args) {

        return "the method ${name} is missing"
    }
}
複製代碼

爲了實現元編程,我們需要使用 metaClass,具體的使用示例如下所示:

ExpandoMetaClass.enableGlobally()
//爲類動態的添加一個屬性
Person.metaClass.sex = 'male'
def person = new Person(name: 'Qndroid'age: 26)
println person.sex
person.sex = 'female'
println "the new sex is:" + person.sex
//爲類動態的添加方法
Person.metaClass.sexUpperCase = { -> sex.toUpperCase() }
def person2 = new Person(name: 'Qndroid'age: 26)
println person2.sexUpperCase()
//爲類動態的添加靜態方法
Person.metaClass.static.createPerson = {
    String name, int age -> new Person(name: name, age: age)
}
def person3 = Person.createPerson('renzhiqiang'26)
println person3.name + " and " + person3.age
複製代碼

需要注意的是通過類的 metaClass 來添加元素的這種方式每次使用時都需要重新添加,幸運的是,我們可以在注入前調用全局生效的處理,代碼如下所示:

ExpandoMetaClass.enableGlobally()
// 在應用程序初始化的時候我們可以爲第三方類添加方法
Person.metaClass.static.createPerson = { String name,
                                              int age ->
    new Person(name: name, age: age)
}
複製代碼

2)、腳本中的變量和作用域

對於每一個 Groovy 腳本來說,它都會生成一個 static void main 函數,main 函數中會調用一個 run 函數,腳本中的所有代碼則包含在 run 函數之中。我們可以通過如下的 groovyc 命令用於將編譯得到的 class 文件拷貝到 classes 文件夾下:

// groovyc 是 groovy 的編譯命令,-d classes 用於將編譯得到的 class 文件拷貝到 classes 文件夾 下
groovyc -d classes test.groovy
複製代碼

當我們在 Groovy 腳本中定義一個變量時,由於它實際上是在 run 函數中創建的,所以腳本中的其它方法或其他腳本是無法訪問它的。這個時候,我們需要使用 @Field 將當前變量標記爲成員變量,其示例代碼如下所示:

import groovy.transform.Field; 
    
@Field author = JsonChao
複製代碼

四、文件處理

1、常規文件處理

1)、讀文件

eachLine 方法

我們可以使用 eachLine 方法讀該文件中的每一行,它唯一的參數是一個 Closure,Closure 的參數是文件每一行的內容。示例代碼如下所示:

def file = new File(文件名)
file.eachLine{ String oneLine ->
    println oneLine

    
def text = file.getText()
def text2 = file.readLines()

file.eachLine { oneLine, lineNo ->
    println "${lineNo} ${oneLine}"
}
複製代碼

然後,我們可以使用 'targetFile.bytes' 直接得到文件的內容。

使用 InputStream

此外,我們也可以通過流的方式進行文件操作,如下代碼所示:

//操作 ism,最後記得關掉
def ism = targetFile.newInputStream() 
// do sth
ism.close
複製代碼

使用閉包操作 inputStream

利用閉包來操作 inputStream,其功能更加強大,推薦使用這種寫法,如下所示:

targetFile.withInputStream{ ism ->
    // 操作 ism,不用 close。Groovy 會自動替你 close 
}
複製代碼

2)、寫文件

關於寫文件有兩種常用的操作形式,即通過 withOutputStream/withInputStream 或 withReader/withWriter 的寫法。示例代碼如下所示:

通過 withOutputStream/、withInputStream copy 文件

def srcFile = new File(源文件名)
def targetFile = new File(目標文件名) targetFile.withOutputStream{ os->
    srcFile.withInputStream{ ins->
        os << ins //利用 OutputStream 的<<操作符重載,完成從 inputstream 到 OutputStream //的輸出
    } 
}
複製代碼

通過 withReader、withWriter copy 文件

def copy(String sourcePath, String destationPath) {
    try {
        //首先創建目標文件
        def desFile = new File(destationPath)
        if (!desFile.exists()) {
            desFile.createNewFile()
        }
    
        //開始copy
        new File(sourcePath).withReader { reader ->
            def lines = reader.readLines()
            desFile.withWriter { writer ->
                lines.each { line ->
                    writer.append(line + "\r\n")
            }
            }
        }
        return true
    } catch (Exception e) {
        e.printStackTrace()
    }
    return false
}
複製代碼

此外,我們也可以通過 withObjectOutputStream/withObjectInputStream 來保存與讀取 Object 對象。示例代碼如下所示:

保存對應的 Object 對象到文件中

def saveObject(Object object, String path) {
    try {
        //首先創建目標文件
        def desFile = new File(path)
        if (!desFile.exists()) {
            desFile.createNewFile()
        }
        desFile.withObjectOutputStream { out ->
            out.writeObject(object)
        }
    return true
    } catch (Exception e) {
    }
    return false
}
複製代碼

從文件中讀取 Object 對象

def readObject(String path) {
    def obj = null
    try {
        def file = new File(path)
        if (file == null || !file.exists()) return null
        //從文件中讀取對象
        file.withObjectInputStream { input ->
            obj = input.readObject()
        }
    } catch (Exception e) {

    }
    return obj
}
複製代碼

2、XML 文件操作

1)、獲取 XML 數據

首先,我們定義一個包含 XML 數據的字符串,如下所示:

final String xml = '''
    <response version-api="2.0">
        <value>
            <books id="1" classification="android">
                <book available="20" id="1">
                    <title>瘋狂Android講義</title>
                    <author id="1">李剛</author>
                </book>
                <book available="14" id="2">
                   <title>第一行代碼</title>
                   <author id="2">郭林</author>
               </book>
               <book available="13" id="3">
                   <title>Android開發藝術探索</title>
                   <author id="3">任玉剛</author>
               </book>
               <book available="5" id="4">
                   <title>Android源碼設計模式</title>
                   <author id="4">何紅輝</author>
               </book>
           </books>
           <books id="2" classification="web">
               <book available="10" id="1">
                   <title>Vue從入門到精通</title>
                   <author id="4">李剛</author>
               </book>
           </books>
       </value>
    </response>
'''

複製代碼

然後,我們可以 使用 XmlSlurper 來解析此 xml 數據,代碼如下所示:

def xmlSluper = new XmlSlurper()
def response = xmlSluper.parseText(xml)

// 通過指定標籤獲取特定的屬性值
println response.value.books[0].book[0].title.text()
println response.value.books[0].book[0].author.text()
println response.value.books[1].book[0].@available

def list = []
response.value.books.each { books ->
    //下面開始對書結點進行遍歷
    books.book.each { book ->
        def author = book.author.text()
        if (author.equals('李剛')) {
            list.add(book.title.text())
        }
    }
}
println list.toListString()
複製代碼

2)、獲取 XML 數據的兩種遍歷方式

獲取 XML 數據有兩種遍歷方式:深度遍歷 XML 數據 與 廣度遍歷 XML 數據,下面我們看看它們各自的用法,如下所示:

深度遍歷 XML 數據

def titles = response.depthFirst().findAll { book ->
    return book.author.text() == '李剛' ? true : false
}
println titles.toListString()
複製代碼

廣度遍歷 XML 數據

def name = response.value.books.children().findAll { node ->
    node.name() == 'book' && node.@id == '2'
}.collect { node ->
    return node.title.text()
}
複製代碼

在實際使用中,我們可以 利用 XmlSlurper 求獲取 AndroidManifest.xml 的版本號(versionName),代碼如下所示:

def androidManifest = new XmlSlurper().parse("AndroidManifest.xml") println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
複製代碼

3)、生成 XML 數據

除了使用 XmlSlurper 解析 XML 數據之外,我們也可以 使用 xmlBuilder 來創建 XML 文件,如下代碼所示:

/**
 * 生成 xml 格式數據
 * <langs type='current' count='3' mainstream='true'>
 <language flavor='static' version='1.5'>Java</language>
 <language flavor='dynamic' version='1.6.0'>Groovy</language>
 <language flavor='dynamic' version='1.9'>JavaScript</language>
 </langs>
 */

def sw = new StringWriter()
// 用來生成 xml 數據的核心類
def xmlBuilder = new MarkupBuilder(sw) 
// 根結點 langs 創建成功
xmlBuilder.langs(type: 'current'count: '3',
        mainstream: 'true') {
    //第一個 language 結點
    language(flavor: 'static'version: '1.5') {
        age('16')
    }
    language(flavor: 'dynamic'version: '1.6') {
        age('10')
    }
    language(flavor: 'dynamic'version: '1.9''JavaScript')
}
    
// println sw

def langs = new Langs()
xmlBuilder.langs(type: langs.type, count: langs.count,
        mainstream: langs.mainstream) {
    //遍歷所有的子結點
    langs.languages.each { lang ->
        language(flavor: lang.flavor,
                version: lang.version, lang.value)
    }
}

println sw
    
// 對應 xml 中的 langs 結點
class Langs {
    String type = 'current'
    int count = 3
    boolean mainstream = true
    def languages = [
            new Language(flavor: 'static',
                    version: '1.5'value: 'Java'),
            new Language(flavor: 'dynamic',
                    version: '1.3'value: 'Groovy'),
            new Language(flavor: 'dynamic',
                    version: '1.6'value: 'JavaScript')
    ]
}
//對應xml中的languang結點
class Language {
    String flavor
    String version
    String value
}
複製代碼

4)、Groovy 中的 json

我們可以 使用 Groovy 中提供的 JsonSlurper 類去替代 Gson 解析網絡響應,這樣我們在寫插件的時候可以避免引入 Gson 庫,其示例代碼如下所示:

def reponse =
        getNetworkData(
                'http://yuexibo.top/yxbApp/course_detail.json')

println reponse.data.head.name
    
def getNetworkData(String url) {
    //發送http請求
    def connection = new URL(url).openConnection()
    connection.setRequestMethod('GET')
    connection.connect()
    def response = connection.content.text
    //將 json 轉化爲實體對象
    def jsonSluper = new JsonSlurper()
    return jsonSluper.parseText(response)
}
複製代碼

五、總結

在這篇文章中,我們從以下 四個方面 學習了 Groovy 中的必備核心語法:

  • 1)、groovy 中的變量、字符串、循環等基本語法。
  • 2)、groovy 中的數據結構:列表、映射、範圍。
  • 3)、groovy 中的方法、類等面向對象、強大的運行時機制。
  • 4)、groovy 中對普通文件、XML、json 文件的處理。

在後面我們自定義 Gradle 插件的時候需要使用到這些技巧,因此,掌握好 Groovy 的重要性不言而喻,只有紮實基礎才能讓我們走的更遠。

公衆號

我的公衆號 JsonChao 開通啦,如果您想第一時間獲取最新文章和最新動態,歡迎掃描關注~

參考鏈接:

Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信羣:

由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~

About me

很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術羣,這對我意義重大。

希望我們能成爲朋友,在 Github掘金上一起分享知識。

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