重要-作為Android開發(fā)者必須了解的Gradle知識

在Android開發(fā)中箱残,很多時候我們不需要修改 *.gradle 文件太多止吁,我們添加依賴敬惦、修改target compile俄删、最低支持API level畴椰,或者修改簽名配置和build類型抓艳。其它更復(fù)雜一些邏輯玷或,我們最后可能就是從Stack Overflow中copy了一些自己也不太懂的代碼。本文中我們將一步一步介紹Android工程中用到的gradle文件及其背后的原理蔬胯。

1. Groovy

1.1 語法

Gradle文件其實是用Groovy腳本寫的笔宿,我們都會寫java泼橘,所以入門Groovy非常簡單炬灭。首先我們需要了解一下幾點:

**1. ** 調(diào)用至少包含一個參數(shù)的方法時不需要使用括號:

def printAge(String name, int age) {
    print("$name is $age years old")
}

def printEmptyLine() {
    println()
}

def callClosure(Closure closure) {
    closure()
}

printAge "John", 24 // Will print "John is 24 years old"
printEmptyLine() // Will, well, print empty line
callClosure { println("From closure") } // Will print "From closure"

** 2. ** 如果方法的最后一個參數(shù)是閉包(或者說是lambda表達式),可以寫在括號外(注:這個特性很重要厦凤,gradle文件中的很多配置其實都是參數(shù)為閉包的方法):

def callWithParam(String param, Closure<String> closure) {
    closure(param)
}

callWithParam("param", { println it }) // Will print "param"
callWithParam("param") { println it } // Will print "param"
callWithParam "param", { println it } // Will print "param"

**3. ** 對于Groovy方法中命名過的參數(shù)椎木,會被轉(zhuǎn)移到一個map中做為方法的第一個參數(shù)博烂,那些沒有命名的參數(shù)則加在參數(shù)列表之后:

def printPersonInfo(Map<String, Object> person) {
    println("${person.name} is ${person.age} years old")
}

def printJobInfo(Map<String, Object> job, String employeeName) {
    println("$employeeName works as ${job.title} at ${job.company}")
}

printPersonInfo name: "John", age: 24
printJobInfo "John", title: "Android developer", company: "Tooploox"

這段程序會打印“John is 24 years old”和“John works as Android developer at Tooploox”畜伐,方法調(diào)用的參數(shù)可以是亂序的躺率,map會被作為第一個參數(shù)傳入悼吱!這里的方法調(diào)用也省略了括號舆绎。

1.2 閉包

閉包是一個非常重要的特性吕朵,需要解釋一下努溃。閉包可以理解為lambada梧税。他們是一段可以被執(zhí)行的代碼,可以有參數(shù)列表和返回值哮塞。我們可以改變一個閉包的委托:

class WriterOne {
    def printText(str) {
        println "Printed in One: $str"
    }
}

class WriterTwo {
    def printText(str) {
        println "Printed in Two: $str"
    }
}

def printClosure = {
    printText "I come from a closure"
}

printClosure.delegate = new WriterOne()
printClosure() // will print "Printed in One: I come from a closure
printClosure.delegate = new WriterTwo()
printClosure() // will print "Printed in Two: I come from a closure

我們可以看到printClosure調(diào)用了不同委托的printText方法,之后會解析這個特性在gradle中的重要性尸执。

2. Gradle

2.1 腳本文件

有三個主要的gradle腳本如失,每個都是一個代碼塊褪贵。

  • build.gradle 文件竭鞍,針對Project對象
  • settings.gradle文件偎快,針對Settings對象
  • 全局配置的初始化gradle腳本晒夹,針對Gradle實例

2.2 Projects

gradle 構(gòu)建一般包含多個Project(在Android中每個module對應(yīng)這里的project)丐怯,project中包含tasks读跷。一般至少有一個root project,包含很多subprojects荡短,subproject也可以嵌套project(注:Android 中對應(yīng)每個library module還可以依賴其它library module)掘托。

3. 構(gòu)建基于Gradle的Android工程

Android工程中我們一般有如下的結(jié)構(gòu):

1是root project的setting文件,被Settings執(zhí)行
2是root project的build配置
3是App project的屬性文件辱士,會被注入到 App的Settings
4是App project的build配置

3.1 創(chuàng)建gradle工程

我們新建一個文件夾识补,命名為example凭涂,cd進入后執(zhí)行gradle projects命令,之后就已經(jīng)擁有一個gradle project了:

$ gradle projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
No sub-projects
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :tasks
BUILD SUCCESSFUL
Total time: 0.741 secs

3.2 配置projects層級

如果我們要建立一個默認(rèn)的Android project(空的root project和一個包含Application的app project),我們就需要配置settings.gradle澎胡, the documentation 中介紹settings.gradle

聲明需要實例化的配置和build的project的層級體系配置

我們通過void include(String[] projectPaths)方法來添加projects:

這里的冒號:用于分隔子project稚伍,可以參考這里 here个曙。因此我們在這里寫:app垦搬, 而不是直接寫app猴贰。

settings.gradle中寫rootProject.name = <<name>>也是一個比較好的實踐米绕。如果沒有寫义郑,那么root project 的默認(rèn)名字就是project所在文件夾的名字。

3.3 配置Android 子project

我們已經(jīng)配置了root project的build.gradle雏赦,現(xiàn)在來看看如何配置Android project星岗。

user guide可以知道我們首先要為app project配置com.android.application插件允华,我們來看看apply方法:

void apply(Closure closure)
void apply(Map<String, ?> options)
void apply(Action<? super ObjectConfigurationAction> action)

盡管第三個方法很重要靴寂,我們通常使用是第二個方法百炬,它用到我們之前提到的特性剖踊,通過map來傳遞參數(shù)德澈。通過文檔我們可以查看可以使用哪些參數(shù):

void apply(Map(<String, ?> options)

以下是可用的參數(shù):
from: 可以引入一個腳本apply(...)梆造,如apply from: "bintray.gradle"從而導(dǎo)入一個可用腳本澳窑。
plugin: apply的plugin的id或者實現(xiàn)類
to: 委托目標(biāo)

我們知道需要傳遞一個id值作為plugin的參數(shù)摊聋,可以寫作:apply(plugin:'com.android.application')麻裁,這里的括號也可以省略色迂,我們在app的build.gradle中配置:


命令行中運行:

報錯了,找不到com.android.application的定義诈悍,這不奇怪侥钳,我們并沒有配置,但是gradle是如何查找Android的plugin jar包呢给猾?在user guide可以找到答案耙册,我們需要配置plugin的路徑详拙。

現(xiàn)在我們可以在root project或者app的build.gradle中配置路徑饶辙,但是因為buildscript閉包是ScriptHandler執(zhí)行的,其它子project也需要使用斑粱,因此最好配置在root project的build.gradle中:

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0-beta2'
    }
}

如果我們在上邊的代碼中添加括號弃揽,那么就會發(fā)現(xiàn)其實都是帶有閉包參數(shù)的方法調(diào)用。如果我們研究下 文檔则北,我們就可以知道是有哪些對象執(zhí)行這些閉包的矿微,總結(jié)如下:

buildscript(Closure)Project 實例中調(diào)用的,傳遞的閉包的由ScriptHandler 執(zhí)行

repositories(Closure) 是在 ScriptHandler實例中調(diào)用尚揣,傳遞的閉包由 RepositoryHandler 執(zhí)行

dependencies(Closure) 是在 ScriptHandler 實例中調(diào)用涌矢,傳遞的閉包由 DependencyHandler 執(zhí)行。

也就是說 jcenter() 是由 RepositoryHandler 調(diào)用
classpath(String) 是由 DependencyHandler(*) 調(diào)用

譯者注:如果這里看不懂的同學(xué)快骗,可以再回頭看看groovy的語法部分名秀,其實這里上邊的代碼都是方法,如buildscript是Project的方法,我們知道groovy語法中如果最后一個參數(shù)是閉包的話惠猿,可以不寫括號。

如果查看DependencyHandler的代碼扼鞋,我們會發(fā)現(xiàn)其實沒有classpath這個方法溃槐,這是一種特殊的調(diào)用,我們在稍后討論。

3.4 配置Android subproject

如果我們現(xiàn)在執(zhí)行Gradle task猖闪,依然有錯誤:


顯然表鳍,我們還沒有設(shè)置Android相關(guān)的配置,但是我們的Android plugin已經(jīng)可以被正確apply了绳姨,我們增加一些配置:

android {
   buildToolsVersion "25.0.1"
   compileSdkVersion 25
}

到這里我們知道,android方法被加入到了Project實例中,閉包傳遞給了delegate(這里是AppExtension)廓旬,定義了buildToolsVersioncompileSdkVersion方法春霍,Android plugin使用這種方式接收所有的配置莲趣,包括default configuration翁逞,flavors等等浊竟。

想要執(zhí)行g(shù)radle task,還需要兩個文件:AndroidManifest.xmllocal.propertieslocal.properties中配置sdk.dir芋浮,(或者在系統(tǒng)環(huán)境中配置ANDROID_HOME),指向Android SDK的位置梯啤。

3.5 擴展

android方法是如何出現(xiàn)在Project實例中的呢,還有我們的build.gradle是怎樣被執(zhí)行的先舷?簡單的說,Android plugin 用android這個名字注冊AppExtension類為extension氮兵。這個超出了本文的范圍,但是我們要知道Gradle可以為每一個注冊過的 plugin增加閉包配置定血。

3.6 依賴

還有一個重要的部分既们,dependencies還沒有討論:

dependencies {
    compile 'io.reactivex.rxjava2:rxjava:2.0.4'
    testCompile 'junit:junit:4.12'
    annotationProcessor 'org.parceler:parceler:1.1.6'
}

為什么這里特殊呢斯棒,因為如果查看DependencyHandler穗酥,也就是執(zhí)行這個閉包的委托节吮,它是沒有compile ,testCompile等方法的请毛。這個問題是有意義的委粉,如果我們隨意增加一個freeCompile 'somelib',可以嗎扁藕?DependencyHandler不會定義所有的方法沮峡,其實這里涉及到Groory語音的另一個特性:methodMissing,這允許在運行時catch對于未定義方法的調(diào)用纹磺。

實際上Gradle使用了MethodMixIn中聲明的methodMissing 帖烘,類似的機制在為定義的屬性中也是一樣的。

相關(guān)的dependency操作可以在 這里找到橄杨,它的行為如下:
如果未定義方法的調(diào)用方有至少一個參數(shù)秘症,如果存在configuration()與被調(diào)用方法有相同的名字,那么就根據(jù)參數(shù)的類型和數(shù)量式矫,調(diào)用具有相關(guān)參數(shù)的doAdd方法乡摹。

每個plugin都可以增進configuration到dependencies handler中,如Android插件增加了compile, compileClasspath, testCompile和一些其它配置here采转,Android 插件還增加了annotationProcessor配置聪廉,根據(jù)不同build類型和產(chǎn)品形式還有<variant>Compile, <variant>TestCompile等等。

由于doAdd是私有方法故慈,一次這里調(diào)用的是公有的add方法板熊,我們可以重寫上邊的代碼,但最后不要這樣做:

dependencies {
    add('compile', 'io.reactivex.rxjava2:rxjava:2.0.4')
    add('testCompile', 'junit:junit:4.12')
    add('annotationProcessor', 'org.parceler:parceler:1.1.6')
}

3.7 Flavors, build types, signing configs

我們看以下代碼:

productFlavors {
    prod {

    }

    dev {
        minSdkVersion 21
        multiDexEnabled true
    }
}

如果我們查看源碼察绷,可以發(fā)現(xiàn)productFlavors是這樣聲明的:

void productFlavors(Action<? super 
NamedDomainObjectContainer<ProductFlavorDsl>> action) {
    action.execute(productFlavors)    
}

Action<T>是Gradle中定義的由T執(zhí)行的閉包
所有這里我們有了NamedDomainObjectContainer干签,NamedDomainObjectContainer可以創(chuàng)建和配置多個ProductFlavorDsl類型的對象,并根據(jù)ProductFlavorDsl的名字保存ProductFlavorDsl拆撼。

這個容器可以使用動態(tài)方法創(chuàng)建指定類型的對象(這里的ProductFlavorDsl)容劳,并和名字一起存放在容器中,所以當(dāng)我們使用{}參數(shù)調(diào)用prod 方法時闸度,他被productFlavors實例執(zhí)行竭贩,執(zhí)行說明如下:

NamedDomainObjectContainer 獲取到被調(diào)用方法的名字,生成ProductFlavorDsl對象莺禁,配置給定的閉包留量,保存方法名字到新的配置ProductFlavorDsl的映射。

Android plugin可以從productFlavors中獲取ProductFlavorDsl,我們可以把它作為屬性進行訪問:productFlavors.dev肪获,這樣我們就可以拿到名字為devProductFlavorDsl寝凌,這也是我們可以寫signingConfig signingConfigs.debug的原因。

4. 總結(jié)

對于Android開發(fā)者來說孝赫,Gradle文件是非常常用的较木,并不是什么黑魔法。但是Gradle有很多約定青柄,而且使用Groovy語言也增加了一些復(fù)雜性伐债,知道這兩點,Gradle并不是什么魔法致开。希望了解通過這篇文章介紹的內(nèi)容峰锁,即使是從stackoverflow中粘貼代碼,也能知道它背后的意義双戳。

這是一篇譯文虹蒋,原文作者對Android的gradle進行了比較深入的介紹,希望各位同學(xué)可以真正了解我們常用的gradle文件背后的原理飒货,而不僅僅是簡單地配置gralde魄衅。文中有些不太容易理解的地方,可以根據(jù)文中給出的鏈接了解更多內(nèi)容塘辅。
原文地址https://medium.com/@wasyl/understanding-android-gradle-build-files-e4b45b73cc4c#.svvmjs12o

推薦閱讀:

重要-作為Android開發(fā)者必須了解的Gradle知識

編寫高效的Android代碼(譯)

Android中使用gradient的一條建議

尋找卓越的(Android)軟件工程師

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晃虫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扣墩,更是在濱河造成了極大的恐慌哲银,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呻惕,死亡現(xiàn)場離奇詭異荆责,居然都是意外死亡,警方通過查閱死者的電腦和手機亚脆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門草巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人型酥,你說我怎么就攤上這事〔槠梗” “怎么了弥喉?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玛迄。 經(jīng)常有香客問我由境,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任虏杰,我火速辦了婚禮讥蟆,結(jié)果婚禮上遍膜,老公的妹妹穿的比我還像新娘席纽。我一直安慰自己,他們只是感情好徊哑,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布笛钝。 她就那樣靜靜地躺著质况,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玻靡。 梳的紋絲不亂的頭發(fā)上结榄,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音囤捻,去河邊找鬼臼朗。 笑死,一個胖子當(dāng)著我的面吹牛蝎土,可吹牛的內(nèi)容都是我干的视哑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瘟则,長吁一口氣:“原來是場噩夢啊……” “哼黎炉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起醋拧,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤慷嗜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丹壕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庆械,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年菌赖,在試婚紗的時候發(fā)現(xiàn)自己被綠了缭乘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡琉用,死狀恐怖堕绩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邑时,我是刑警寧澤奴紧,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站晶丘,受9級特大地震影響黍氮,放射性物質(zhì)發(fā)生泄漏唐含。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一沫浆、第九天 我趴在偏房一處隱蔽的房頂上張望捷枯。 院中可真熱鬧,春花似錦专执、人聲如沸淮捆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽争剿。三九已至,卻和暖如春痊末,著一層夾襖步出監(jiān)牢的瞬間蚕苇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工凿叠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涩笤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓盒件,卻偏偏與公主長得像蹬碧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炒刁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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