Android Gradle構建-理解DSL語言以及運行機制

前言

這篇文章可能跟Android的關系不是很深,主要介紹Groovy是如何一步步解析Android的DSL語言,這樣你在配置一些Gradle文件的時候可以更加得心應手。閱讀本文之前你需要具有一點Android基礎,并且需要了解一些Groovy語言的基本特性,例如Closure、[], def等含義。Groovy是一種運行在JVM虛擬機上的腳本語言,能夠與Java語言無縫結合,如果想了解Groovy可以查看IBM-DeveloperWorks-精通Groovy

DSL的好處

我們打開Android的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配置文件,我們可以看到buildscript里有配置了repositoriesdependencies,而repositoriesdependencies里面又可以配置各自的一些屬性??梢钥闯鐾ㄟ^這種形式的配置,我們可以層次分明的看出整個項目構建的一些定制,又由于Android也遵循約定大于配置的設計思想,因此我們僅僅只需修改需要自定義的部分即可輕松個性化構建流程。

Gradle下的Groovy腳本-build.gradle

在Groovy下,我們可以像Python這類腳本語言一樣寫個腳本文件直接執(zhí)行而無需像Java那樣既要寫好Class又要定義main()函數(shù),因為Groovy本身就是一門腳本語言,而Gradle是基于Groovy語言的構建工具,自然也可以輕松通過腳本來執(zhí)行構建整個項目。作為一個基于Gradle的項目工程,項目結構中的settings.gradlebuild.gradle這類xxx.gradle可以理解成是Gradle構建該工程的執(zhí)行腳本,當我們在鍵盤上敲出gradle clean aDebug這類命令的時候,Gradle就會去尋找這類文件并按照規(guī)則先后讀取這些gradle文件并使用Groovy去解析執(zhí)行。

讓DSL"活起來"-Groovy的魔法

要理解build.gradle文件中的這些DSL是如何被解析執(zhí)行的,需要介紹Groovy的一些語法特點以及一些高級特性,官方有一篇關于DSL特性的描述,如果你追求原味直接看這個即可。 Domain-Specific-Languages
下面將介紹一下比較重要的幾個特點。

Command chains - 鏈式命令

Groovy的腳本具有鏈式命令(Command chains)的特性,根據(jù)這個特性,當你在Groovy腳本中寫出a b c d的時候,Groovy會翻譯成a(b).c(d)執(zhí)行,也就是將b作為a函數(shù)的形參調用,然后將d作為形參再次調用返回的實例(Instance)中的c方法。其中當做形參的bd可以作為一個閉包(Closure)傳遞過去。
下面是一些簡單的實例:

// 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也支持某個方法傳入空參數(shù),但需要為該空參數(shù)的方法加上圓括號。

// equivalent to: select(all).unique().from(names)
select all unique() from names

如果鏈式命令(Command chains)的參數(shù)是奇數(shù),則最后一個參數(shù)會被當成屬性值(Property)訪問。

// equivalent to: take(3).cookies
// and also this: take(3).getCookies()
take 3 cookies

Operator overloading - 操作符重載

有了Groovy的操作符重載(Operator overloading),==會被Groovy轉換成equals方法,這樣你就可以放心大膽地使用==來比較兩個字符串是否相等了,在我們編寫gradle腳本的時候也可以盡情使用。關于Groovy的所有操作符重載(Operator overloading)可以查閱Operator overloading。

DelegatesTo - 委托

委托(DelegatesTo)可以說是Gradle選擇Groovy作為DSL執(zhí)行平臺的一個重要因素了。通過委托(DelegatesTo)可以很簡單的定制一個控制結構體(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語言了,當然這也是一段腳本,我們可以結合上文講到的Groovy的鏈式命令(Command chains)來手動解析一下這段腳本含義,下面拆分下這些步驟吧:

  1. 首先email {...}這段被執(zhí)行,其執(zhí)行方式等效于email({...}), Groovy調用email方法,然后將{...}這個閉包(Closure)作為參數(shù)傳遞進去;
  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一樣,{...}這個閉包作為body方法的參數(shù),等效于body({...})解釋執(zhí)行;
  5. p 'Really, the pope has resigned!'等效于p('Really, the pope has resigned!')解釋執(zhí)行。
    當然,有個問題我們需要清楚,當我們調用email {...}這種方法的時候,{...}閉包中的方法比如from 'dsl-guru@mycompany.com'等不是Groovy Shell自動去調用執(zhí)行的,而是通過Groovy的委托(DelegatesTo)方式來完成,這塊后文會講到。

接下來我們可以看下解析上述DSL語言的代碼:

def email(Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

我們先定義了一個email(Closure)的方法,當執(zhí)行上述步驟1的時候就會進入該方法內執(zhí)行,EmailSpec是一個繼承了參數(shù)中cl閉包里所有方法比如from、to等等的一個類(Class),通過rehydrate方法將cl拷貝成一份新的實例(Instance)并賦值給code,code實例(Instance)通過rehydrate方法中設置delegate、ownerthisObject的三個屬性將clemail兩者關聯(lián)起來被賦予了一種委托關系,這種委托關系可以這樣理解:cl閉包中的from、to等方法會調用到email委托類實例(Instance)中的方法,并可以訪問到email中的實例變量(Field)。DELEGATE_ONLY表示閉包(Closure)方法調用只會委托給它的委托者(The delegate of closure),最后使用code()開始執(zhí)行閉包中的方法。
當然,Groovy提供了很多靈活的委托(DelegatesTo)方式,這塊可以通過閱讀官方文檔了解。

Android DSL解讀

下面我們直接開始解讀上文提供的build.gradle這個文件,讓我們來看看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...方法。通過Android Studio點擊某個方法我們可以發(fā)現(xiàn)buildscript、allprojectstask都指向了Project類,由此可以看出Project類是整個build.gradle腳本文件的委托類,其中必然有一個Project的實例(Instance)在管理這些類,當我們執(zhí)行諸如biuldscriptallprojectstask這些方法的時候,就能夠對這個Project實例進行配置。由此最后Gradle基于Project類的實例(Instance)進行整個項目的構建流程。
接下來描述下這份grade腳本文件的執(zhí)行步驟,為了描述方便,我將buildscript方法中的閉包(Closure)稱為C1,然后其他閉包(Closure)對應關系依次為repositories->C2dependencies->C3、all projects->C4,repositories->C5,最后一個task...這一部分閉包(Closure)就不定義了,至于原因,你可以猜下~接下來按照步驟來說吧:

  1. 執(zhí)行buildscript方法,并把C1作為形參傳遞進去,進行構建腳本的一些配置,此時C1的委托者(The delegate of closure)是Project類中的ScriptHandler的實例(Instance);
  2. 執(zhí)行C1中的方法,此時執(zhí)行repositories方法并以C2作為形參,配置倉庫地址,C2的委托者(The delegate of closure)是RepositoryHandler類的實例(Instance),負責相關倉庫的配置;
  3. 執(zhí)行C2中的方法,由于C2的委托者(The delegate of closure)是RepositoryHandler的實例(Instance),因此執(zhí)行了RepositoryHandlerjcenter方法,將它配置成我們項目的遠程倉庫;
  4. 執(zhí)行dependencies方法并將C3作為形參,配置一些相關的構建依賴,C3的委托者(The delegate of closure)是DependencyHandler類的實例(Instance);
  5. 執(zhí)行C3中的方法,同步驟3一樣,調用委托者(The delegate of closure)DependencyHandler的方法classpath并把相關依賴作為形參傳遞過去,不過這里你會發(fā)現(xiàn)用IDE進去卻是對應add(String configurationName, Object dependencyNotation)這個方法,這里一定有玄機,感興趣的朋友可以自個探索下;
  6. 同上面原理一樣,執(zhí)行all projects、C4、repositoriesC5等這類方法,配置了所有項目工程的倉庫為jcenter,這里不再贅述;
  7. 接下來是task clean ...這部分DSL了,這塊的邏輯存在一個比較奇怪的問題,根據(jù)Groovy的鏈式命令(Command chains),此處執(zhí)行的順序應該是clean([type: Delete], {delete rootProject.buildDir}) -> task(...),然而實際上并非如此,其實際執(zhí)行應該是task([type: Delete], 'clean', {delte rootProject.buildDir})(此處僅個人理解,感謝@花京院典明 指正,之后有時間把這塊 DSL 解析過程完善下),由此完成一個Task的創(chuàng)建,由于指定了typeDelete,所以{delete rootProject.buildDir}這個閉包(Closure)的委托者(The delegate of closure)就是Delete類的實例(Instance),具體實現(xiàn)方式可以參考Gradle的源碼。

結語

至此,你應該對于Android DSL有了一個大概的了解吧。由于本人水平有限,如果其中有錯誤之處還望指出,233333~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

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

友情鏈接更多精彩內容