參考:
深入理解Android之Gradle
Groovy與Java的區(qū)別
Groovy語法
Groovy基礎(chǔ)——Closure(閉包)詳解
def varAndMethod() {
def a = 1 //不顯式聲明變量類型
a = "abc" //運行時改變類型
println a //結(jié)尾無需 茉唉;
a = 4 //最后一行作為返回值
}
def ret = varAndMethod()
println ret //輸出4
Groovy是一種動態(tài)語言。對自己的定義就是:Groovy是在 java平臺上的佩憾、 具有像Python掩蛤, Ruby 和 Smalltalk 語言特性的靈活動態(tài)語言枉昏。
除了語言和Java相通外,Groovy有時候又像一種腳本語言揍鸟。
下面我們將介紹Groovy兄裂。由于此文的主要目的是Gradle,所以我們不會過多討論Groovy中細枝末節(jié)的東西蜈亩,而是把知識點集中在以后和Gradle打交道時一些常用的地方上懦窘。
一 前提知識
1.Groovy注釋標記和Java一樣,支持//或者/**/
2.Groovy語句可以不用分號結(jié)尾稚配。Groovy為了盡量減少代碼的輸入畅涂,確實煞費苦心
3.Groovy中支持動態(tài)類型,即定義變量的時候可以不指定其類型道川。
4.Groovy中午衰,變量定義可以使用關(guān)鍵字def。注意冒萄,雖然def不是必須的臊岸,但是為了代碼清晰,
建議還是使用def關(guān)鍵字
def i = 1 //可以不使用分號結(jié)尾
def ss = "I ama person"
def int x = 1 //變量定義時尊流,也可以直接指定類型
5.函數(shù)定義時帅戒,參數(shù)的類型也可以不指定。比如
String testFunction(arg1,arg2){ //無需指定參數(shù)類型
...
}
6.除了變量定義可以不指定類型外崖技,Groovy中函數(shù)的返回值也可以是無類型的逻住。比如:
//無類型的函數(shù)定義钟哥,必須使用def關(guān)鍵字
def nonReturnTypeFunc(){
last_line //最后一行代碼的執(zhí)行結(jié)果就是本函數(shù)的返回值
}
//如果指定了函數(shù)返回類型,則可不必加def關(guān)鍵字來定義函數(shù)
String getString(){
return"I am a string"
}
7.函數(shù)返回值:Groovy的函數(shù)里瞎访,可以不使用returnxxx來設(shè)置xxx為函數(shù)返回值腻贰。
如果不使用return語句的話,則函數(shù)里最后一句代碼的執(zhí)行結(jié)果被設(shè)置成返回值扒秸。比如
//下面這個函數(shù)的返回值是字符串"getSomething return value"
def getSomething(){
"I am a return value" //最后一行代碼播演,返回類型為String
}
注意,如果函數(shù)定義時候指明了返回值類型的話伴奥,函數(shù)中則必須返回正確的數(shù)據(jù)類型写烤。
8.最后,除了每行代碼不用加分號外渔伯,Groovy中函數(shù)調(diào)用的時候還可以不加括號顶霞。比如:
println("test") ---> println"test"
注意,雖然寫代碼的時候锣吼,對于函數(shù)調(diào)用可以不帶括號选浑,但是Groovy經(jīng)常把屬性和函數(shù)混淆。比如
def method(){
"hello"
}
method //如果不加括號的話玄叠,Groovy會誤認為method是一個變量古徒。找不到就會報錯:
所以調(diào)用函數(shù)最好帶上括號,也好理解一些读恃,如果這個函數(shù)是Groovy API
或者Gradle API中比較常用的隧膘,比如println,就可以不帶括號寺惫。
Groovy中的字符串
Groovy對字符串支持相當(dāng)強大疹吃,充分吸收了一些腳本語言的優(yōu)點:
1 單引號''中的內(nèi)容嚴格對應(yīng)Java中的String,不對$符號進行轉(zhuǎn)義
defsingleQuote='I am $ dolloar' //輸出就是I am $ dolloar
2 雙引號""的內(nèi)容則和腳本語言的處理有點像西雀,如果字符中有$號的話萨驶,則它會$表達式先求值。
def s1 = "I am one dollar" //輸出 I am one dollar
def x = 1
def s2 = "I am $x dolloar" //輸出I am 1 dolloar
3 三個引號'''xxx'''中的字符串支持隨意換行 比如
defmultieLines = ''' begin
line 1
line 2
end '''
二 Groovy中的數(shù)據(jù)類型
主要有3種:
- 一個是Java中的基本數(shù)據(jù)類型艇肴。
- 另外一個是Groovy中的容器類腔呜。
- 最后一個非常重要的是閉包。
基本數(shù)據(jù)類型
- 作為動態(tài)語言再悼,Groovy世界中的所有事物都是對象核畴。所以,int冲九,boolean這些Java中的基本數(shù)據(jù)類型谤草,在Groovy代碼中其實對應(yīng)的是它們的包裝數(shù)據(jù)類型,比如int對應(yīng)為Integer,boolean對應(yīng)為Boolean,因此可以對變量直接調(diào)用方法:
def int x = 4
y = 3
assert x.plus(y) == 7
閉包
閉包,英文叫Closure咖刃,是Groovy中非常重要的一個數(shù)據(jù)類型或者說一種概念了泳炉。閉包的歷史來源憾筏,種種好處我就不說了嚎杨。我們直接看怎么使用它!
- 閉包氧腰,是一種數(shù)據(jù)類型枫浙,它代表了一段可執(zhí)行的代碼。其外形如下:
def aClosure = { //閉包是一段代碼古拴,用花括號括起來..
String param1, int param2 -> //這個箭頭很關(guān)鍵箩帚。
//箭頭前面是參數(shù)定義,箭頭后面是代碼塊
println"this is code" //這是代碼塊黄痪,最后一句是返回值紧帕,
//也可以使用return,和Groovy中普通函數(shù)一樣
}
簡而言之桅打,Closure的定義格式是:
def xxx = {paramters -> code} //或者
def xxx = {無參數(shù)純code} 這種case不需要->符號
閉包定義好后是嗜,要調(diào)用它的方法就是:
--閉包對象.call(參數(shù)) 或者:
--閉包對象(參數(shù))
aClosure.call("this is string",100) 或者
aClosure("this is string", 100)
上面就是一個閉包的定義和使用。
- 在閉包中挺尾,還需要注意一點:
如果閉包沒定義參數(shù)的話鹅搪,則隱含有一個參數(shù),這個參數(shù)名字叫it遭铺,和this的作用類似丽柿。it代表閉包的參數(shù)。比如:
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
等同于:
def greeting = { it -> "Hello, $it!"}
assert greeting('Patrick') == 'Hello, Patrick!'
但是魂挂,如果在閉包定義時甫题,采用下面這種寫法,則表示閉包沒有參數(shù)涂召!
def noParamClosure = { -> true }
這個時候坠非,我們就不能給noParamClosure傳參數(shù)了!
noParamClosure ("test") <==報錯喔芹扭!
- 省略圓括號
Groovy中麻顶,當(dāng)函數(shù)的最后一個參數(shù)是閉包的話,可以省略圓括號舱卡。
閉包在Groovy中大量使用辅肾,比如很多類都定義了一些函數(shù),這些函數(shù)最后一個參數(shù)都是一個閉包轮锥。比如:
public static <T> List<T>each(List<T> self, Closure closure)
//上面這個函數(shù)表示針對List的每一個元素都會調(diào)用closure做一些處理矫钓。
//這里的closure,就有點回調(diào)函數(shù)的感覺。但是新娜,在使用這個each函數(shù)的時候赵辕,
//我們傳遞一個怎樣的Closure進去呢?比如:
def iamList = [1,2,3,4,5] //定義一個List
iamList.each{ //調(diào)用它的each概龄,這段代碼的格式看不懂了吧还惠?each是個函數(shù),圓括號去哪了私杜?
println it
}
上面代碼有兩個知識點:
1) each函數(shù)調(diào)用的圓括號不見了蚕键!原來,Groovy中衰粹,當(dāng)函數(shù)的最后一個參數(shù)是閉包的話锣光,可以省略圓括號。比如
def testClosure(int a1,String b1, Closure closure){
//dosomething
closure() //調(diào)用閉包
}
那么調(diào)用的時候铝耻,就可以免括號誊爹!
testClosure (4, "test", {
println"i am in closure"
} ) //括號可以不寫..
注意,這個特點非常關(guān)鍵瓢捉,因為以后在Gradle中經(jīng)常會出現(xiàn)圖7這樣的代碼:
經(jīng)常碰見這樣的沒有圓括號的代碼频丘。省略圓括號雖然使得代碼簡潔,但是卻使代碼很難理解泊柬,完整的代碼應(yīng)該按下面這種寫法:
doLast({
println'Hello world!'
})
有了圓括號椎镣,你會知道 doLast只是把一個Closure對象傳了進去。很明顯兽赁,它不代表這段腳本解析到doLast的時候就會調(diào)用println ‘Hello world!’ 状答。但是把圓括號去掉后,就感覺好像println ‘Hello world!’立即就會被調(diào)用一樣刀崖!
容器類
Groovy中的容器類很簡單惊科,就三種:
- List:鏈表,其底層對應(yīng)Java中的List接口亮钦,一般用ArrayList作為真正的實現(xiàn)類馆截。
- Map:鍵-值表,其底層對應(yīng)Java中的LinkedHashMap蜂莉。
- Range:范圍蜡娶,它其實是List的一種拓展。
對容器而言映穗,我們最重要的是了解它們的用法窖张。下面是一些簡單的例子:
- List類
變量定義:List變量由[]定義,比如
def aList = [5,'string',true] //List由[]定義蚁滋,其元素可以是任何對象
//變量存人藿印:可以直接通過索引存取赘淮,而且不用擔(dān)心索引越界。
//如果索引超過當(dāng)前鏈表長度睦霎,List會自動
往該索引添加元素
assert aList[1] == 'string'
assert aList[5] == null //第6個元素為空
aList[100] = 100 //設(shè)置第101個元素的值為10
assert aList[100] == 100
那么梢卸,aList到現(xiàn)在為止有多少個元素呢?
println aList.size ===>結(jié)果是101
- Map類
容器變量定義
1.變量定義:Map變量由[:]定義副女,比如
def aMap = ['key1':'value1','key2':true]
注意其中的冒號蛤高。冒號左邊是key,右邊是Value肮塞。
2.key必須是字符串襟齿,value可以是任何對象。
另外枕赵,key可以用''或""包起來,也可以不用引號包起來位隶。比如
def aNewMap = [key1:"value",key2:true]
其中的key1和key2默認被處理成字符串"key1"和"key2"拷窜,
但最好是用""引起來,避免引起混淆涧黄。比如:
def key1="wowo"
def aConfusedMap=[key1:"who am i?"]
aConfuseMap中的key1到底是"key1"還是變量key1的值“wow”篮昧?
顯然,答案是字符串"key1"笋妥。如果要是"wowo"的話懊昨,則aConfusedMap的定義必須設(shè)置成:
def aConfusedMap=[(key1):"who am i?"]
3.Map中元素的存取更加方便,它支持多種方法:
println aMap.keyName <==這種表達方法好像key就是aMap的一個成員變量一樣
println aMap['keyName'] <==這種表達方法更傳統(tǒng)一點
aMap.anotherkey = "i am map" <==為map添加新元素
- Range類
Range是Groovy對List的一種拓展春宣,變量定義和大體的使用方法如下:
def aRange = 1..5 <==Range類型的變量 由begin值+兩個點+end值表示
左邊這個aRange包含1,2,3,4,5這5個值
如果不想包含最后一個元素酵颁,則
def aRangeWithoutEnd = 1..<5 <==包含1,2,3,4這4個元素
println aRange.from
println aRange.to
多嘴一句,文檔中并沒有說明Range有from和to這兩個屬性月帝,但是卻有g(shù)etFrom和getTo這兩個函數(shù)躏惋。根據(jù)Groovy的原則,如果一個類中有名為xxyyzz這樣的屬性(其實就是成員變量)嚷辅,Groovy會自動為它添加getXxyyzz和setXxyyzz兩個函數(shù)簿姨,用于獲取和設(shè)置xxyyzz屬性值。
注意簸搞,get和set后第一個字母是大寫的
所以扁位,當(dāng)你看到Range中有g(shù)etFrom和getTo這兩個函數(shù)時候,就得知道潛規(guī)則下趁俊,Range有from和to這兩個屬性域仇。當(dāng)然,由于它們不可以被外界設(shè)置则酝,所以沒有公開setFrom和setTo函數(shù)殉簸。
三 變量的作用域
def x = 1 <==注意闰集,這個x有def(或者指明類型,比如 int x = 1)
def printx(){
println x
}
printx() <==報錯般卑,說x找不到武鲁,生成的java代碼如下:
1.printx被定義成test類的成員函數(shù)
2.def x = 1,這句話是在run方法中創(chuàng)建的蝠检。所以沐鼠,x=1從代碼上看好像是在整個腳本中定義的,但實際上printx訪問不了它叹谁。printx是test成員函數(shù)饲梭,除非x也被定義成test的成員函數(shù),否則printx不能訪問它焰檩。
那么憔涉,如何使得printx能訪問x呢?很簡單析苫,定義的時候不要加類型和def兜叨。即:
x = 1 <==注意,去掉def和類型
def printx(){
println x
}
現(xiàn)在生成的java代碼如下:
比較復(fù)雜衩侥,x也沒有被定義成test的成員變量国旷,而是在run的執(zhí)行過程中,將x作為一個屬性添加到test實例對象中了茫死。然后在printx中跪但,先獲取這個屬性。
注意峦萎,Groovy的文檔說 x = 1這種定義將使得x變成test的成員變量屡久,但從反編譯情況看,這是不對得…..
雖然printx可以訪問x變量了骨杂,但是別的腳本卻無法訪問x變量涂身。因為它不是test的成員變量。
那么如何才能定義成成員變量呢:
import groovy.transform.Field; //必須要先import
@Field x = 1 <==在x前面加上@Field標注搓蚪,這樣蛤售,x就徹徹底底是test的成員變量了。
查看編譯后的test.class文件妒潭,得到:
四 文件操作
Groovy的I/O操作是在原有Java I/O操作上進行了更為簡單方便的封裝悴能,并且使用Closure來簡化代碼編寫。主要封裝了如下一些了類:
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
讀文件
def targetFile = new File(文件名) <==File對象還是要創(chuàng)建的雳灾。
然后打開File的API看看Groovy定義的API:
讀該文件中的每一行的方法:eachLine的唯一參數(shù)是一個Closure漠酿。Closure的參數(shù)是文件每一行的內(nèi)容及行數(shù)
其內(nèi)部實現(xiàn)肯定是Groovy打開這個文件,然后讀取文件的一行谎亩,然后調(diào)用Closure…
targetFile.eachLine{
String oneLine,int lineNum ->
println oneLine
} <==是不是令人發(fā)指炒嘲?宇姚?!
不用處理關(guān)閉流之類的夫凸,Groovy已經(jīng)幫處理了
直接得到文件內(nèi)容:
targetFile.getText() 或者
targetFile.getBytes() <==文件內(nèi)容一次性讀出
流操作
1. 直接操作inputStream
def ism = targetFile.newInputStream()
//操作ism浑劳,最后記得關(guān)掉
ism.close
2. 使用閉包操作inputStream,以后在Gradle里會池舶瑁看到這種搞法
targetFile.withInputStream{ ism ->
...//操作ism. 不用close魔熏。Groovy會自動替你close
}
//file.withInputStream方法參考API可知參數(shù)
//就一個Closure,回創(chuàng)建一個本文件的輸
//入流并作為參數(shù)傳遞給Closure
寫文件
def targetFile = new File(目標文件名)
targetFile.withOutputStream{ os->
os.setBytes(內(nèi)容)
}
XML操作
除了I/O異常簡單之外,Groovy中的XML操作也極致得很鸽扁。Groovy中蒜绽,XML的解析提供了和XPath類似的方法,名為GPath桶现。這是一個類躲雅,提供相應(yīng)API。
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〖春幔現(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 = newXmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
略
-> setting.gradle文件
def initXXX(){
println"initialize ....."
......//干一些special的私活....
}
//settings.gradle加載的時候废境,會執(zhí)行initMinshengGradleEnvironment
initXXX()
include 'youku' , 'alipay' , 'baidu'
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
Properties properties = new Properties()
File propertyFile = new File(rootDir.getAbsolutePath() +"/local.properties")
properties.load(propertyFile.newDataInputStream())
//gradle就是gradle對象。它默認是Settings和Project的成員變量筒繁∝迹可直接獲取
//ext前綴,表明操作的是外置屬性毡咏。api是一個新的屬性名驮宴。前面說過,只在
//第一次定義或者設(shè)置它的時候需要ext前綴
gradle.ext.api =properties.getProperty('sdk.api')
println gradle.api //再次存取api的時候呕缭,就不需要ext前綴了
//utils.gradle中定義了一個獲取AndroidManifests.xml的versionName的方法
def getVersionNameAdvanced(){
? //下面這行代碼中的project是誰堵泽?
defxmlFile = project.file("AndroidManifest.xml")
defrootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest['@android:versionName']
}
//現(xiàn)在修己,想把這個方法輸出到各個Project碟贾。由于這個utils.gradle會被每一個Project Apply贷腕,所以
ext{
//除了ext.xxx=value這種定義方法外祸憋,還可以使用ext{}這種書寫方法泵殴。
getVersionNameAdvanced = this.&getVersionNameAdvanced
}