前言
這篇文章可能跟Android的關(guān)系不是很深,主要介紹Groovy是如何一步步解析Android的DSL語(yǔ)言,這樣你在配置一些Gradle文件的時(shí)候可以更加得心應(yīng)手。閱讀本文之前你需要具有一點(diǎn)Android基礎(chǔ),并且需要了解一些Groovy語(yǔ)言的基本特性,例如Closure
、[]
, def
等含義。Groovy是一種運(yùn)行在JVM虛擬機(jī)上的腳本語(yǔ)言,能夠與Java語(yǔ)言無(wú)縫結(jié)合,如果想了解Groovy可以查看IBM-DeveloperWorks-精通Groovy。
DSL的好處
我們打開Android的build.gradle
文件,會(huì)看到類似下面的一些語(yǔ)法:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
這是一個(gè)簡(jiǎn)單的build.gradle
配置文件,我們可以看到buildscript
里有配置了repositories
和dependencies
,而repositories
和dependencies
里面又可以配置各自的一些屬性。可以看出通過(guò)這種形式的配置,我們可以層次分明的看出整個(gè)項(xiàng)目構(gòu)建的一些定制,又由于Android也遵循約定大于配置的設(shè)計(jì)思想,因此我們僅僅只需修改需要自定義的部分即可輕松個(gè)性化構(gòu)建流程。
Gradle下的Groovy腳本-build.gradle
在Groovy下,我們可以像Python這類腳本語(yǔ)言一樣寫個(gè)腳本文件直接執(zhí)行而無(wú)需像Java那樣既要寫好Class又要定義main()
函數(shù),因?yàn)镚roovy本身就是一門腳本語(yǔ)言,而Gradle是基于Groovy語(yǔ)言的構(gòu)建工具,自然也可以輕松通過(guò)腳本來(lái)執(zhí)行構(gòu)建整個(gè)項(xiàng)目。作為一個(gè)基于Gradle的項(xiàng)目工程,項(xiàng)目結(jié)構(gòu)中的settings.gradle
和build.gradle
這類xxx.gradle
可以理解成是Gradle構(gòu)建該工程的執(zhí)行腳本,當(dāng)我們?cè)阪I盤上敲出gradle clean aDebug
這類命令的時(shí)候,Gradle就會(huì)去尋找這類文件并按照規(guī)則先后讀取這些gradle文件并使用Groovy去解析執(zhí)行。
讓DSL"活起來(lái)"-Groovy的魔法
要理解build.gradle
文件中的這些DSL是如何被解析執(zhí)行的,需要介紹Groovy的一些語(yǔ)法特點(diǎn)以及一些高級(jí)特性,官方有一篇關(guān)于DSL特性的描述,如果你追求原味直接看這個(gè)即可。 Domain-Specific-Languages
下面將介紹一下比較重要的幾個(gè)特點(diǎn)。
Command chains - 鏈?zhǔn)矫?/h3>
Groovy的腳本具有鏈?zhǔn)矫?Command chains)的特性,根據(jù)這個(gè)特性,當(dāng)你在Groovy腳本中寫出a b c d
的時(shí)候,Groovy會(huì)翻譯成a(b).c(d)
執(zhí)行,也就是將b
作為a
函數(shù)的形參調(diào)用,然后將d
作為形參再次調(diào)用返回的實(shí)例(Instance)中的c
方法。其中當(dāng)做形參的b
和d
可以作為一個(gè)閉包(Closure)傳遞過(guò)去。
下面是一些簡(jiǎn)單的實(shí)例:
// equivalent to: turn(left).then(right)
turn left then right
// equivalent to: take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours
// equivalent to: paint(wall).with(red, green).and(yellow)
paint wall with red, green and yellow
// with named parameters too
// equivalent to: check(that: margarita).tastes(good)
check that: margarita tastes good
// with closures as parameters
// equivalent to: given({}).when({}).then({})
given { } when { } then { }
Groovy也支持某個(gè)方法傳入空參數(shù),但需要為該空參數(shù)的方法加上圓括號(hào)。
// equivalent to: select(all).unique().from(names)
select all unique() from names
如果鏈?zhǔn)矫?Command chains)的參數(shù)是奇數(shù),則最后一個(gè)參數(shù)會(huì)被當(dāng)成屬性值(Property)訪問(wèn)。
// equivalent to: take(3).cookies
// and also this: take(3).getCookies()
take 3 cookies
Operator overloading - 操作符重載
有了Groovy的操作符重載(Operator overloading),==
會(huì)被Groovy轉(zhuǎn)換成equals
方法,這樣你就可以放心大膽地使用==
來(lái)比較兩個(gè)字符串是否相等了,在我們編寫gradle腳本的時(shí)候也可以盡情使用。關(guān)于Groovy的所有操作符重載(Operator overloading)可以查閱Operator overloading。
DelegatesTo - 委托
委托(DelegatesTo)可以說(shuō)是Gradle選擇Groovy作為DSL執(zhí)行平臺(tái)的一個(gè)重要因素了。通過(guò)委托(DelegatesTo)可以很簡(jiǎn)單的定制一個(gè)控制結(jié)構(gòu)體(Custom control structures),比如你可以寫如下這段代碼:
email {
from 'dsl-guru@mycompany.com'
to 'john.doe@waitaminute.com'
subject 'The pope has resigned!'
body {
p 'Really, the pope has resigned!'
}
}
上面這段代碼便是我們自己定義的DSL語(yǔ)言了,當(dāng)然這也是一段腳本,我們可以結(jié)合上文講到的Groovy的鏈?zhǔn)矫?Command chains)來(lái)手動(dòng)解析一下這段腳本含義,下面拆分下這些步驟吧:
- 首先
email {...}
這段被執(zhí)行,其執(zhí)行方式等效于email({...})
, Groovy調(diào)用email
方法,然后將{...}
這個(gè)閉包(Closure)作為參數(shù)傳遞進(jìn)去; -
from 'dsl-guru@mycompany.com'
等效于from('dsl-guru@mycompany.com')
解析執(zhí)行; -
subject 'The pope has resigned!'
等效于subject('The pope has resigned!')
解析執(zhí)行; -
body {...}
同步驟1一樣,{...}
這個(gè)閉包作為body
方法的參數(shù),等效于body({...})
解釋執(zhí)行; -
p 'Really, the pope has resigned!'
等效于p('Really, the pope has resigned!')
解釋執(zhí)行。
當(dāng)然,有個(gè)問(wèn)題我們需要清楚,當(dāng)我們調(diào)用email {...}
這種方法的時(shí)候,{...}
閉包中的方法比如from 'dsl-guru@mycompany.com'
等不是Groovy Shell自動(dòng)去調(diào)用執(zhí)行的,而是通過(guò)Groovy的委托(DelegatesTo)方式來(lái)完成,這塊后文會(huì)講到。
接下來(lái)我們可以看下解析上述DSL語(yǔ)言的代碼:
def email(Closure cl) {
def email = new EmailSpec()
def code = cl.rehydrate(email, this, this)
code.resolveStrategy = Closure.DELEGATE_ONLY
code()
}
我們先定義了一個(gè)email(Closure)
的方法,當(dāng)執(zhí)行上述步驟1的時(shí)候就會(huì)進(jìn)入該方法內(nèi)執(zhí)行,EmailSpec
是一個(gè)繼承了參數(shù)中cl
閉包里所有方法比如from
、to
等等的一個(gè)類(Class),通過(guò)rehydrate
方法將cl
拷貝成一份新的實(shí)例(Instance)并賦值給code
,code
實(shí)例(Instance)通過(guò)rehydrate
方法中設(shè)置delegate
、owner
和thisObject
的三個(gè)屬性將cl
和email
兩者關(guān)聯(lián)起來(lái)被賦予了一種委托關(guān)系,這種委托關(guān)系可以這樣理解:cl
閉包中的from
、to
等方法會(huì)調(diào)用到email
委托類實(shí)例(Instance)中的方法,并可以訪問(wèn)到email
中的實(shí)例變量(Field)。DELEGATE_ONLY
表示閉包(Closure)方法調(diào)用只會(huì)委托給它的委托者(The delegate of closure),最后使用code()
開始執(zhí)行閉包中的方法。
當(dāng)然,Groovy提供了很多靈活的委托(DelegatesTo)方式,這塊可以通過(guò)閱讀官方文檔了解。
Android DSL解讀
下面我們直接開始解讀上文提供的build.gradle
這個(gè)文件,讓我們來(lái)看看Groovy是如何讓這些DSL發(fā)揮了作用。
build.gradle:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
可以看到這份build.gradle
依次執(zhí)行了buildscript({...})
、all projects{...}
、all projects{...}
和task...
方法。通過(guò)Android Studio
點(diǎn)擊某個(gè)方法我們可以發(fā)現(xiàn)buildscript
、allprojects
和task
都指向了Project
類,由此可以看出Project
類是整個(gè)build.gradle
腳本文件的委托類,其中必然有一個(gè)Project
的實(shí)例(Instance)在管理這些類,當(dāng)我們執(zhí)行諸如biuldscript
、allprojects
和task
這些方法的時(shí)候,就能夠?qū)@個(gè)Project
實(shí)例進(jìn)行配置。由此最后Gradle基于Project
類的實(shí)例(Instance)進(jìn)行整個(gè)項(xiàng)目的構(gòu)建流程。
接下來(lái)描述下這份grade腳本文件的執(zhí)行步驟,為了描述方便,我將buildscript
方法中的閉包(Closure)稱為C1
,然后其他閉包(Closure)對(duì)應(yīng)關(guān)系依次為repositories
->C2
、dependencies
->C3
、all projects
->C4
,repositories
->C5
,最后一個(gè)task...
這一部分閉包(Closure)就不定義了,至于原因,你可以猜下~接下來(lái)按照步驟來(lái)說(shuō)吧:
- 執(zhí)行
buildscript
方法,并把C1
作為形參傳遞進(jìn)去,進(jìn)行構(gòu)建腳本的一些配置,此時(shí)C1
的委托者(The delegate of closure)是Project
類中的ScriptHandler
的實(shí)例(Instance); - 執(zhí)行
C1
中的方法,此時(shí)執(zhí)行repositories
方法并以C2
作為形參,配置倉(cāng)庫(kù)地址,C2
的委托者(The delegate of closure)是RepositoryHandler
類的實(shí)例(Instance),負(fù)責(zé)相關(guān)倉(cāng)庫(kù)的配置; - 執(zhí)行
C2
中的方法,由于C2
的委托者(The delegate of closure)是RepositoryHandler
的實(shí)例(Instance),因此執(zhí)行了RepositoryHandler
的jcenter
方法,將它配置成我們項(xiàng)目的遠(yuǎn)程倉(cāng)庫(kù); - 執(zhí)行
dependencies
方法并將C3
作為形參,配置一些相關(guān)的構(gòu)建依賴,C3
的委托者(The delegate of closure)是DependencyHandler
類的實(shí)例(Instance); - 執(zhí)行
C3
中的方法,同步驟3一樣,調(diào)用委托者(The delegate of closure)DependencyHandler
的方法classpath
并把相關(guān)依賴作為形參傳遞過(guò)去,不過(guò)這里你會(huì)發(fā)現(xiàn)用IDE進(jìn)去卻是對(duì)應(yīng)add(String configurationName, Object dependencyNotation)
這個(gè)方法,這里一定有玄機(jī),感興趣的朋友可以自個(gè)探索下; - 同上面原理一樣,執(zhí)行
all projects
、C4
、repositories
和C5
等這類方法,配置了所有項(xiàng)目工程的倉(cāng)庫(kù)為jcenter
,這里不再贅述; - 接下來(lái)是
task clean ...
這部分DSL了,這塊的邏輯存在一個(gè)比較奇怪的問(wèn)題,根據(jù)Groovy的鏈?zhǔn)矫?Command chains),此處執(zhí)行的順序應(yīng)該是clean([type: Delete], {delete rootProject.buildDir})
->task(...)
,然而實(shí)際上并非如此,其實(shí)際執(zhí)行應(yīng)該是task([type: Delete], 'clean', {delte rootProject.buildDir})
(此處僅個(gè)人理解,感謝@花京院典明 指正,之后有時(shí)間把這塊 DSL 解析過(guò)程完善下),由此完成一個(gè)Task的創(chuàng)建,由于指定了type
為Delete
,所以{delete rootProject.buildDir}
這個(gè)閉包(Closure)的委托者(The delegate of closure)就是Delete
類的實(shí)例(Instance),具體實(shí)現(xiàn)方式可以參考Gradle的源碼。
結(jié)語(yǔ)
至此,你應(yīng)該對(duì)于Android DSL有了一個(gè)大概的了解吧。由于本人水平有限,如果其中有錯(cuò)誤之處還望指出,233333~