Android Gradle構(gòu)建-理解DSL語(yǔ)言以及運(yùn)行機(jī)制

前言

這篇文章可能跟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里有配置了repositoriesdependencies,而repositoriesdependencies里面又可以配置各自的一些屬性。可以看出通過(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.gradlebuild.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)做形參的bd可以作為一個(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)解析一下這段腳本含義,下面拆分下這些步驟吧:

  1. 首先email {...}這段被執(zhí)行,其執(zhí)行方式等效于email({...}), Groovy調(diào)用email方法,然后將{...}這個(gè)閉包(Closure)作為參數(shù)傳遞進(jìn)去;
  2. from 'dsl-guru@mycompany.com'等效于from('dsl-guru@mycompany.com')解析執(zhí)行;
  3. subject 'The pope has resigned!'等效于subject('The pope has resigned!')解析執(zhí)行;
  4. body {...}同步驟1一樣,{...}這個(gè)閉包作為body方法的參數(shù),等效于body({...})解釋執(zhí)行;
  5. 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閉包里所有方法比如fromto等等的一個(gè)類(Class),通過(guò)rehydrate方法將cl拷貝成一份新的實(shí)例(Instance)并賦值給codecode實(shí)例(Instance)通過(guò)rehydrate方法中設(shè)置delegateownerthisObject的三個(gè)屬性將clemail兩者關(guān)聯(lián)起來(lái)被賦予了一種委托關(guān)系,這種委托關(guān)系可以這樣理解:cl閉包中的fromto等方法會(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)buildscriptallprojectstask都指向了Project類,由此可以看出Project類是整個(gè)build.gradle腳本文件的委托類,其中必然有一個(gè)Project的實(shí)例(Instance)在管理這些類,當(dāng)我們執(zhí)行諸如biuldscriptallprojectstask這些方法的時(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->C2dependencies->C3all projects->C4repositories->C5,最后一個(gè)task...這一部分閉包(Closure)就不定義了,至于原因,你可以猜下~接下來(lái)按照步驟來(lái)說(shuō)吧:

  1. 執(zhí)行buildscript方法,并把C1作為形參傳遞進(jìn)去,進(jìn)行構(gòu)建腳本的一些配置,此時(shí)C1的委托者(The delegate of closure)是Project類中的ScriptHandler的實(shí)例(Instance);
  2. 執(zhí)行C1中的方法,此時(shí)執(zhí)行repositories方法并以C2作為形參,配置倉(cāng)庫(kù)地址,C2的委托者(The delegate of closure)是RepositoryHandler類的實(shí)例(Instance),負(fù)責(zé)相關(guān)倉(cāng)庫(kù)的配置;
  3. 執(zhí)行C2中的方法,由于C2的委托者(The delegate of closure)是RepositoryHandler的實(shí)例(Instance),因此執(zhí)行了RepositoryHandlerjcenter方法,將它配置成我們項(xiàng)目的遠(yuǎn)程倉(cāng)庫(kù);
  4. 執(zhí)行dependencies方法并將C3作為形參,配置一些相關(guān)的構(gòu)建依賴,C3的委托者(The delegate of closure)是DependencyHandler類的實(shí)例(Instance);
  5. 執(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è)探索下;
  6. 同上面原理一樣,執(zhí)行all projectsC4repositoriesC5等這類方法,配置了所有項(xiàng)目工程的倉(cāng)庫(kù)為jcenter,這里不再贅述;
  7. 接下來(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)建,由于指定了typeDelete,所以{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~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評(píng)論 18 139
  • 導(dǎo)語(yǔ): 隨著技術(shù)的發(fā)展,不管是前端開發(fā)、服務(wù)端開發(fā)或者是移動(dòng)端開發(fā)(移動(dòng)也是前端的一個(gè)分支)中都會(huì)用到自動(dòng)化構(gòu)建工...
    伊始雨深閱讀 3,050評(píng)論 0 4
  • Gradle是基于Groovy的動(dòng)態(tài)DSL,而Groovy是基于JVM的,Groovy的語(yǔ)法和Java很類似。 C...
    HoooChan閱讀 7,504評(píng)論 0 7
  • Gradle對(duì)于很多開發(fā)者來(lái)說(shuō)有一種既熟悉又陌生的感覺(jué),他是離我們那么近,以至于我每天做項(xiàng)目都需要他,但是他又是離...
    阿_希爸閱讀 9,593評(píng)論 10 199
  • Android Studio作為Android應(yīng)用開發(fā)的官方IDE,默認(rèn)使用Gradle作為構(gòu)建工具,所以對(duì)于An...
    feil0n9wan9閱讀 1,691評(píng)論 1 6