最后,我們來看一下 Groovy 中比較高級的用法。
1.腳本中 import 其他類
Groovy 中可以像 Java 那樣寫 package,然后寫類。比如在文件夾 com/cmbc/groovy/目錄中放一個文件,叫 Test.groovy,如圖 10 所示:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/11.jpg" alt="" />
你看,圖 10 中的 Test.groovy 和 Java 類就很相似了。當(dāng)然,如果不聲明 public/private 等訪問權(quán)限的話,Groovy 中類及其變量默認(rèn)都是 public 的。
現(xiàn)在,我們在測試的根目錄下建立一個 test.groovy 文件。其代碼如下所示:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/12.jpg" alt="" />
你看,test.groovy 先 import 了 com.cmbc.groovy.Test 類,然后創(chuàng)建了一個 Test 類型的對象,接著調(diào)用它的 print 函數(shù)。
這兩個 groovy 文件的目錄結(jié)構(gòu)如圖 12 所示:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/13.jpg" alt="" />
在 groovy 中,系統(tǒng)自帶會加載當(dāng)前目錄/子目錄下的 xxx.groovy 文件。所以,當(dāng)執(zhí)行 groovy test.groovy 的時候,test.groovy import 的 Test 類能被自動搜索并加載到。
2.腳本到底是什么
Java 中,我們最熟悉的是類。但是我們在 Java 的一個源碼文件中,不能不寫 class(interface 或者其他....),而 Groovy 可以像寫腳本一樣,把要做的事情都寫在 xxx.groovy 中,而且可以通過 groovy xxx.groovy 直接執(zhí)行這個腳本。這到底是怎么搞的?
既然是基于 Java 的,Groovy 會先把 xxx.groovy 中的內(nèi)容轉(zhuǎn)換成一個 Java 類。比如:
test.groovy 的代碼是:
println 'Groovy world!'
Groovy 把它轉(zhuǎn)換成這樣的 Java 類:
執(zhí)行 groovyc -d classes test.groovy
groovyc 是 groovy 的編譯命令,-d classes 用于將編譯得到的 class 文件拷貝到 classes 文件夾下
圖 13 是 test.groovy 腳本轉(zhuǎn)換得到的 java class。用 jd-gui 反編譯它的代碼:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/14.jpg" alt="" />
圖 13 中:
groovyc 是一個比較好的命令,讀者要掌握它的用法。然后利用 jd-gui 來查看對應(yīng) class 的 Java 源碼。
3.腳本中的變量和作用域
前面說了,xxx.groovy 只要不是和 Java 那樣的 class,那么它就是一個腳本。而且腳本的代碼其實(shí)都會被放到 run 函數(shù)中去執(zhí)行。那么,在 Groovy 的腳本中,很重要的一點(diǎn)就是腳本中定義的變量和它的作用域。舉例:
def x = 1 <==注意,這個 x 有 def(或者指明類型,比如 int x = 1)
def printx(){
println x
}
printx() <==報錯,說 x 找不到
為什么?繼續(xù)來看反編譯后的 class 文件。
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/15.jpg" alt="" />
圖 14 中:
那么,如何使得 printx 能訪問 x 呢?很簡單,定義的時候不要加類型和 def。即:
x = 1 <==注意,去掉 def 或者類型
def printx(){
println x
}
printx() <==OK
這次 Java 源碼又變成什么樣了呢?
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/16.jpg" alt="" />
圖 15 中,x 也沒有被定義成 test 的成員函數(shù),而是在 run 的執(zhí)行過程中,將 x 作為一個屬性添加到 test 實(shí)例對象中了。然后在 printx 中,先獲取這個屬性。
注意,Groovy 的文檔說 x = 1 這種定義將使得 x 變成 test 的成員變量,但從反編譯情況看,這是不對得.....
雖然 printx 可以訪問 x 變量了,但是假如有其他腳本卻無法訪問 x 變量。因?yàn)樗皇?test 的成員變量。
比如,我在測試目錄下創(chuàng)建一個新的名為 test1.groovy。這個 test1 將訪問 test.groovy 中定義的 printx 函數(shù):
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/17.jpg" alt="" />
這種方法使得我們可以將代碼分成模塊來編寫,比如將公共的功能放到 test.groovy 中,然后使用公共功能的代碼放到 test1.groovy 中。
執(zhí)行 groovy test1.groovy,報錯。說 x 找不到。這是因?yàn)?x 是在 test 的 run 函數(shù)動態(tài)加進(jìn)去的。怎么辦?
import groovy.transform.Field; //必須要先 import
@Field x = 1 <==在 x 前面加上@Field 標(biāo)注,這樣,x 就徹徹底底是 test 的成員變量了。
查看編譯后的 test.class 文件,得到:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/18.jpg" alt="" />
這個時候,test.groovy 中的 x 就成了 test 類的成員函數(shù)了。如此,我們可以在 script 中定義那些需要輸出給外部腳本或類使用的變量了!
本節(jié)介紹下 Groovy 的文件 I/O 操作。直接來看例子吧,雖然比 Java 看起來簡單,但要理解起來其實(shí)比較難。尤其是當(dāng)你要自己查 SDK 并編寫代碼的時候。
整體說來,Groovy 的 I/O 操作是在原有 Java I/O 操作上進(jìn)行了更為簡單方便的封裝,并且使用 Closure 來簡化代碼編寫。主要封裝了如下一些了類:
1.讀文件
Groovy 中,文件讀操作簡單到令人發(fā)指:
def targetFile = new File(文件名) <==File 對象還是要創(chuàng)建的。
然后打開 http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html 看看 Groovy 定義的 API:
1 讀該文件中的每一行:eachLine 的唯一參數(shù)是一個 Closure。Closure 的參數(shù)是文件每一行的內(nèi)容
其內(nèi)部實(shí)現(xiàn)肯定是 Groovy 打開這個文件,然后讀取文件的一行,然后調(diào)用 Closure...
targetFile.eachLine{
String oneLine ->
println oneLine
} <==是不是令人發(fā)指??!
2 直接得到文件內(nèi)容
targetFile.getBytes() <==文件內(nèi)容一次性讀出,返回類型為 byte[] 注意前面提到的 getter 和 setter 函數(shù),這里可以直接使用 targetFile.bytes //....
3 使用 InputStream.InputStream 的 SDK 在 http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
def ism = targetFile.newInputStream()
//操作 ism,最后記得關(guān)掉
ism.close
4 使用閉包操作 inputStream,以后在 Gradle 里會??吹竭@種搞法
targetFile.withInputStream{ ism ->
操作 ism. 不用 close。Groovy 會自動替你 close
}
確實(shí)夠簡單,令人發(fā)指。我當(dāng)年死活也沒找到 withInputStream 是個啥意思。所以,請各位開發(fā)者牢記 Groovy I/O 操作相關(guān)類的 SDK 地址:
java.io.File: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
java.io.InputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
java.io.OutputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
java.io.Reader: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html
java.io.Writer: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html
java.nio.file.Path: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html
2.寫文件
和讀文件差不多。不再啰嗦。這里給個例子,告訴大家如何 copy 文件。
def srcFile = new File(源文件名)
def targetFile = new File(目標(biāo)文件名)
targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
os << ins //利用 OutputStream 的<<操作符重載,完成從 inputstream 到 OutputStream
//的輸出
}
}
尼瑪....關(guān)于 OutputStream 的<<操作符重載,查看 SDK 文檔后可知:
http://wiki.jikexueyuan.com/project/deep-android-gradle/images/20.jpg" alt="" />
再一次向極致簡單致敬。但是,SDK 恐怕是離不開手了...
除了 I/O 異常簡單之外,Groovy 中的 XML 操作也極致得很。Groovy 中,XML 的解析提供了和 XPath 類似的方法,名為 GPath。這是一個類,提供相應(yīng) API。關(guān)于 XPath,請腦補(bǔ) https://en.wikipedia.org/wiki/XPath。
GPath 功能包括:給個例子好了,來自 Groovy 官方文檔。
test.xml 文件:
<response version-api="2.0">
<value>
<books>
<book available="20" id="1">
<title>Don Xijote</title>
<author id="1">Manuel De Cervantes</author>
</book>
<book available="14" id="2">
<title>Catcher in the Rye</title>
<author id="2">JD Salinger</author>
</book>
<book available="13" id="3">
<title>Alice in Wonderland</title>
<author id="3">Lewis Carroll</author>
</book>
<book available="5" id="4">
<title>Don Xijote</title>
<author id="4">Manuel De Cervantes</author>
</book>
</books>
</value>
</response>
//第一步,創(chuàng)建 XmlSlurper 類
def xparser = new XmlSlurper()
def targetFile = new File("test.xml")
//轟轟的 GPath 出場
GPathResult gpathResult = xparser.parse(targetFile)
//開始玩 test.xml?,F(xiàn)在我要訪問 id=4 的 book 元素。
//下面這種搞法,gpathResult 代表根元素 response。通過 e1.e2.e3 這種
//格式就能訪問到各級子元素....
def book4 = gpathResult.value.books.book[3]
//得到 book4 的 author 元素
def author = book4.author
//再來獲取元素的屬性和 textvalue
assert author.text() == ' Manuel De Cervantes '
獲取屬性更直觀
author.@id == '4' 或者 author['@id'] == '4'
屬性一般是字符串,可通過 toInteger 轉(zhuǎn)換成整數(shù)
author.@id.toInteger() == 4
好了。GPath 就說到這。再看個例子。我在使用 Gradle 的時候有個需求,就是獲取 AndroidManifest.xml 版本號(versionName)。有了 GPath,一行代碼搞定,請看:
def androidManifest = new XmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'