鍍金池/ 教程/ Android/ 腳本類、文件 I/O 和 XML 操作
Groovy 介紹
腳本類、文件 I/O 和 XML 操作
更多
一些前提知識
Gradle 工作流程
基本組件
題外話
總結(jié)
Gradle 編程模型及 API 實(shí)例詳解
閉包
Gradle 介紹
閑言構(gòu)建
Groovy 中的數(shù)據(jù)類型

腳本類、文件 I/O 和 XML 操作

腳本類、文件 I/O 和 XML 操作

最后,我們來看一下 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 中:

  • test.groovy 被轉(zhuǎn)換成了一個 test 類,它從 script 派生。
  • 每一個腳本都會生成一個 static main 函數(shù)。這樣,當(dāng)我們 groovy test.groovy 的時候,其實(shí)就是用 java 去執(zhí)行這個 main 函數(shù)
  • 腳本中的所有代碼都會放到 run 函數(shù)中。比如,println 'Groovy world',這句代碼實(shí)際上是包含在 run 函數(shù)里的。
  • 如果腳本中定義了函數(shù),則函數(shù)會被定義在 test 類中。

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 被定義成 test 類的成員函數(shù)
  • def x = 1,這句話是在 run 中創(chuàng)建的。所以,x=1 從代碼上看好像是在整個腳本中定義的,但實(shí)際上 printx 訪問不了它。printx 是 test 成員函數(shù),除非 x 也被定義成 test 的成員函數(shù),否則 printx 不能訪問它。

那么,如何使得 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 中定義那些需要輸出給外部腳本或類使用的變量了!

文件 I/O 操作

本節(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 恐怕是離不開手了...

XML 操作

除了 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>
  • 現(xiàn)在來看怎么玩轉(zhuǎn) GPath:
//第一步,創(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'
上一篇:Gradle 工作流程下一篇:基本組件