本文轉(zhuǎn)載自:https://juejin.im/post/58ca92192f301e007e39be9d
16小時前
作為 Android 開發(fā)者必須了解的 Gradle 知識 (譯)
在Android開發(fā)中宛逗,很多時候我們不需要修改 *.gradle 文件太多雷激,我們添加依賴、修改target compile承桥、最低支持API level快毛,或者修改簽名配置和build類型番挺。其它更復雜一些邏輯玄柏,我們最后可能就是從Stack Overflow中copy了一些自己也不太懂的代碼粪摘。本文中我們將一步一步介紹Android工程中用到的gradle文件及其背后的原理徘意。
1. Groovy
1.1 語法
Gradle文件其實是用Groovy腳本寫的轩褐,我們都會寫java把介,所以入門Groovy非常簡單。首先我們需要了解一下幾點:
1.調(diào)用至少包含一個參數(shù)的方法時不需要使用括號:
def printAge(Stringname, 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 linecallClosure { println("From closure") }// Will print "From closure"
2.如果方法的最后一個參數(shù)是閉包(或者說是lambda表達式)脚牍,可以寫在括號外(注:這個特性很重要诸狭,gradle文件中的很多配置其實都是參數(shù)為閉包的方法):
def callWithParam(Stringparam, Closure 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 person) {? ? println("${person.name} is ${person.age} years old")}def printJobInfo(Map job,StringemployeeName) {? ? println("$employeeName works as ${job.title} at ${job.company}")}printPersonInfo name:"John",age:24printJobInfo"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ù)列表和返回值讳癌。我們可以改變一個閉包的委托:
classWriterOne{? ? def printText(str) {? ? ? ? println"Printed in One: $str"}}classWriterTwo{? ? def printText(str) {? ? ? ? println"Printed in Two: $str"}}def printClosure = {? ? printText"I come from a closure"}printClosure.delegate =newWriterOne()printClosure()// will print "Printed in One: I come from a closureprintClosure.delegate =newWriterTwo()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 構建一般包含多個Project(在Android中每個module對應這里的project)境输,project中包含tasks颖系。一般至少有一個root project嘁扼,包含很多subprojects,subproject也可以嵌套project(注:Android 中對應每個library module還可以依賴其它library module)强缘。
3. 構建基于Gradle的Android工程
Android工程中我們一般有如下的結(jié)構:
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-projectsTo see a listofthe tasksofa project, run gradle :tasksFor example,tryrunning gradle :tasksBUILD SUCCESSFULTotal time:0.741secs
3.2 配置projects層級
如果我們要建立一個默認的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 = <>也是一個比較好的實踐虐块。如果沒有寫嘉蕾,那么root project 的默認名字就是project所在文件夾的名字。
3.3 配置Android 子project
我們已經(jīng)配置了root project的build.gradle儡率,現(xiàn)在來看看如何配置Android project儿普。
從user guide可以知道我們首先要為app project配置com.android.application插件眉孩,我們來看看apply方法:
voidapply(Closure closure)voidapply(Map options)voidapply(Action action)
盡管第三個方法很重要勒葱,我們通常使用是第二個方法凛虽,它用到我們之前提到的特性,通過map來傳遞參數(shù)呀潭。通過文檔我們可以查看可以使用哪些參數(shù):
voidapply(Map(options)
以下是可用的參數(shù):
from: 可以引入一個腳本apply(...)钠署,如apply from: "bintray.gradle"從而導入一個可用腳本谐鼎。
plugin: apply的plugin的id或者實現(xiàn)類
to: 委托目標
我們知道需要傳遞一個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)用
譯者注:如果這里看不懂的同學耳胎,可以再回頭看看groovy的語法部分,其實這里上邊的代碼都是方法怕午,如buildscript是Project的方法郁惜,我們知道groovy語法中如果最后一個參數(shù)是閉包的話扳炬,可以不寫括號。
如果查看DependencyHandler的代碼恨樟,我們會發(fā)現(xiàn)其實沒有classpath這個方法劝术,這是一種特殊的調(diào)用养晋,我們在稍后討論绳泉。
3.4 配置Android subproject
如果我們現(xiàn)在執(zhí)行Gradle task姆泻,依然有錯誤:
顯然,我們還沒有設置Android相關的配置四苇,但是我們的Android plugin已經(jīng)可以被正確apply了方咆,我們增加一些配置:
android {? buildToolsVersion"25.0.1"compileSdkVersion25}
到這里我們知道,android方法被加入到了Project實例中片拍,閉包傳遞給了delegate(這里是AppExtension)妓肢,定義了buildToolsVersion和compileSdkVersion方法,Android plugin使用這種方式接收所有的配置所禀,包括default configuration色徘,flavors等等操禀。
想要執(zhí)行gradle task,還需要兩個文件:AndroidManifest.xml和local.properties斤寂,local.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盼理,類似的機制在為定義的屬性中也是一樣的宏怔。
相關的dependency操作可以在這里找到臊诊,它的行為如下:
如果未定義方法的調(diào)用方有至少一個參數(shù)抓艳,如果存在configuration()與被調(diào)用方法有相同的名字,那么就根據(jù)參數(shù)的類型和數(shù)量片任,調(diào)用具有相關參數(shù)的doAdd方法对供。
每個plugin都可以增進configuration到dependencies handler中产场,如Android插件增加了compile, compileClasspath, testCompile和一些其它配置here舞竿,Android 插件還增加了annotationProcessor配置炬灭,根據(jù)不同build類型和產(chǎn)品形式還有Compile, 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 {? ? ? ? minSdkVersion21multiDexEnabledtrue}}
如果我們查看源碼漱竖,可以發(fā)現(xiàn)productFlavors是這樣聲明的:
voidproductFlavors(Action> action) {? ? action.execute(productFlavors)? ? }
Action是Gradle中定義的由T執(zhí)行的閉包
所有這里我們有了NamedDomainObjectContainer馍惹,NamedDomainObjectContainer可以創(chuàng)建和配置多個ProductFlavorDsl類型的對象玛界,并根據(jù)ProductFlavorDsl的名字保存ProductFlavorDsl万矾。
這個容器可以使用動態(tài)方法創(chuàng)建指定類型的對象(這里的ProductFlavorDsl),并和名字一起存放在容器中慎框,所以當我們使用{}參數(shù)調(diào)用prod方法時良狈,他被productFlavors實例執(zhí)行,執(zhí)行說明如下:
NamedDomainObjectContainer獲取到被調(diào)用方法的名字笨枯,生成ProductFlavorDsl對象薪丁,配置給定的閉包遇西,保存方法名字到新的配置ProductFlavorDsl的映射。
Android plugin可以從productFlavors中獲取ProductFlavorDsl窥突,我們可以把它作為屬性進行訪問:productFlavors.dev努溃,這樣我們就可以拿到名字為dev的ProductFlavorDsl硫嘶,這也是我們可以寫signingConfigsigningConfigs.debug的原因。
4. 總結(jié)
對于Android開發(fā)者來說沦疾,Gradle文件是非常常用的,并不是什么黑魔法刨秆。但是Gradle有很多約定,而且使用Groovy語言也增加了一些復雜性,知道這兩點送粱,Gradle并不是什么魔法。希望了解通過這篇文章介紹的內(nèi)容,即使是從stackoverflow中粘貼代碼胰蝠,也能知道它背后的意義喷好。
這是一篇譯文禾唁,原文作者對Android的gradle進行了比較深入的介紹,希望各位同學可以真正了解我們常用的gradle文件背后的原理瘦锹,而不僅僅是簡單地配置gralde泪掀。文中有些不太容易理解的地方,可以根據(jù)文中給出的鏈接了解更多內(nèi)容。
原文地址https://medium.com/@wasyl/understanding-android-gradle-build-files-e4b45b73cc4c#.svvmjs12o
推薦閱讀: