在Android開發中,不少時候咱們不須要修改 *.gradle 文件太多,咱們添加依賴、修改target compile、最低支持API level,或者修改簽名配置和build類型。其它更復雜一些邏輯,咱們最後可能就是從Stack Overflow中copy了一些本身也不太懂的代碼。本文中咱們將一步一步介紹Android工程中用到的gradle文件及其背後的原理。javascript
Gradle文件實際上是用Groovy腳本寫的,咱們都會寫java,因此入門Groovy很是簡單。首先咱們須要瞭解一下幾點:html
1. 調用至少包含一個參數的方法時不須要使用括號:java
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. 若是方法的最後一個參數是閉包(或者說是lambda表達式),能夠寫在括號外(注:這個特性很重要,gradle文件中的不少配置其實都是參數爲閉包的方法):react
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方法中命名過的參數,會被轉移到一個map中作爲方法的第一個參數,那些沒有命名的參數則加在參數列表以後:android
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」,方法調用的參數能夠是亂序的,map會被做爲第一個參數傳入!這裏的方法調用也省略了括號。git
閉包是一個很是重要的特性,須要解釋一下。閉包能夠理解爲lambada。他們是一段能夠被執行的代碼,能夠有參數列表和返回值。咱們能夠改變一個閉包的委託:github
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
調用了不一樣委託的printText
方法,以後會解析這個特性在gradle中的重要性。c#
有三個主要的gradle腳本,每一個都是一個代碼塊。api
gradle 構建通常包含多個Project(在Android中每一個module對應這裏的project),project中包含tasks。通常至少有一個root project,包含不少subprojects,subproject也能夠嵌套project(注:Android 中對應每一個library module還能夠依賴其它library module)。閉包
Android工程中咱們通常有以下的結構:
1是root project的setting文件,被Settings
執行
2是root project的build配置
3是App project的屬性文件,會被注入到 App的Settings
中
4是App project的build配置
咱們新建一個文件夾,命名爲example
,cd
進入後執行gradle projects
命令,以後就已經擁有一個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複製代碼
若是咱們要創建一個默認的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 的默認名字就是project所在文件夾的名字。
咱們已經配置了root project的build.gradle
,如今來看看如何配置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來傳遞參數。經過文檔咱們能夠查看可使用哪些參數:
void apply(Map(<String, ?> options)複製代碼
如下是可用的參數:
from: 能夠引入一個腳本apply(...),如apply from: "bintray.gradle"
從而導入一個可用腳本。
plugin: apply的plugin的id或者實現類
to: 委託目標
咱們知道須要傳遞一個id值做爲plugin
的參數,能夠寫做:apply(plugin:'com.android.application')
,這裏的括號也能夠省略,咱們在app的build.gradle
中配置:
報錯了,找不到com.android.application
的定義,這不奇怪,咱們並無配置,可是gradle是如何查找Android的plugin jar包呢?在user guide能夠找到答案,咱們須要配置plugin的路徑。
如今咱們能夠在root project或者app的build.gradle
中配置路徑,可是由於buildscript
閉包是ScriptHandler
執行的,其它子project也須要使用,所以最好配置在root project的build.gradle
中:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0-beta2'
}
}複製代碼
若是咱們在上邊的代碼中添加括號,那麼就會發現其實都是帶有閉包參數的方法調用。若是咱們研究下 文檔,咱們就能夠知道是有哪些對象執行這些閉包的,總結以下:
buildscript(Closure)
是 Project
實例中調用的,傳遞的閉包的由ScriptHandler
執行
repositories(Closure)
是在 ScriptHandler
實例中調用,傳遞的閉包由 RepositoryHandler
執行
dependencies(Closure)
是在 ScriptHandler
實例中調用,傳遞的閉包由 DependencyHandler
執行。
也就是說 jcenter()
是由 RepositoryHandler
調用
classpath(String)
是由 DependencyHandler(*)
調用
譯者注:若是這裏看不懂的同窗,能夠再回頭看看groovy的語法部分,其實這裏上邊的代碼都是方法,如buildscript是Project的方法,咱們知道groovy語法中若是最後一個參數是閉包的話,能夠不寫括號。
若是查看DependencyHandler
的代碼,咱們會發現其實沒有classpath
這個方法,這是一種特殊的調用,咱們在稍後討論。
若是咱們如今執行Gradle task,依然有錯誤:
顯然,咱們尚未設置Android相關的配置,可是咱們的Android plugin已經能夠被正確apply了,咱們增長一些配置:
android {
buildToolsVersion "25.0.1"
compileSdkVersion 25
}複製代碼
到這裏咱們知道,android方法被加入到了Project
實例中,閉包傳遞給了delegate(這裏是AppExtension),定義了buildToolsVersion
和 compileSdkVersion
方法,Android plugin使用這種方式接收全部的配置,包括default configuration,flavors等等。
想要執行gradle task,還須要兩個文件:AndroidManifest.xml
和 local.properties
,local.properties
中配置sdk.dir
,(或者在系統環境中配置ANDROID_HOME
),指向Android SDK的位置。
android
方法是如何出如今Project
實例中的呢,還有咱們的build.gradle是怎樣被執行的?簡單的說,Android plugin 用android這個名字註冊AppExtension
類爲extension
。這個超出了本文的範圍,可是咱們要知道Gradle能夠爲每個註冊過的 plugin增長閉包配置。
還有一個重要的部分,dependencies尚未討論:
dependencies {
compile 'io.reactivex.rxjava2:rxjava:2.0.4'
testCompile 'junit:junit:4.12'
annotationProcessor 'org.parceler:parceler:1.1.6'
}複製代碼
爲何這裏特殊呢,由於若是查看DependencyHandler,也就是執行這個閉包的委託,它是沒有compile
,testCompile
等方法的。這個問題是有意義的,若是咱們隨意增長一個freeCompile 'somelib'
,能夠嗎?DependencyHandler
不會定義全部的方法,其實這裏涉及到Groory語音的另外一個特性:methodMissing,這容許在運行時catch對於未定義方法的調用。
實際上Gradle使用了MethodMixIn中聲明的methodMissing
,相似的機制在爲定義的屬性中也是同樣的。
相關的dependency操做能夠在 這裏找到,它的行爲以下:
若是未定義方法的調用方有至少一個參數,若是存在configuration()
與被調用方法有相同的名字,那麼就根據參數的類型和數量,調用具備相關參數的doAdd
方法。
每一個plugin均可以增進configuration到dependencies handler中,如Android插件增長了compile, compileClasspath, testCompile
和一些其它配置here,Android 插件還增長了annotationProcessor
配置,根據不一樣build類型和產品形式還有<variant>Compile, <variant>TestCompile
等等。
因爲doAdd
是私有方法,一次這裏調用的是公有的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')
}複製代碼
咱們看如下代碼:
productFlavors {
prod {
}
dev {
minSdkVersion 21
multiDexEnabled true
}
}複製代碼
若是咱們查看源碼,能夠發現productFlavors是這樣聲明的:
void productFlavors(Action<? super
NamedDomainObjectContainer<ProductFlavorDsl>> action) {
action.execute(productFlavors)
}複製代碼
Action<T>
是Gradle中定義的由T
執行的閉包
全部這裏咱們有了NamedDomainObjectContainer
,NamedDomainObjectContainer
能夠建立和配置多個ProductFlavorDsl
類型的對象,並根據ProductFlavorDsl
的名字保存ProductFlavorDsl
。
這個容器可使用動態方法建立指定類型的對象(這裏的ProductFlavorDsl),並和名字一塊兒存放在容器中,因此當咱們使用{}
參數調用prod
方法時,他被productFlavors
實例執行,執行說明以下:
NamedDomainObjectContainer
獲取到被調用方法的名字,生成ProductFlavorDsl
對象,配置給定的閉包,保存方法名字到新的配置ProductFlavorDsl
的映射。
Android plugin能夠從productFlavors
中獲取ProductFlavorDsl
,咱們能夠把它做爲屬性進行訪問:productFlavors.dev
,這樣咱們就能夠拿到名字爲dev
的ProductFlavorDsl
,這也是咱們能夠寫signingConfig
signingConfigs.debug
的緣由。
對於Android開發者來講,Gradle文件是很是經常使用的,並非什麼黑魔法。可是Gradle有不少約定,並且使用Groovy語言也增長了一些複雜性,知道這兩點,Gradle並非什麼魔法。但願瞭解經過這篇文章介紹的內容,即便是從stackoverflow中粘貼代碼,也能知道它背後的意義。
這是一篇譯文,原文做者對Android的gradle進行了比較深刻的介紹,但願各位同窗能夠真正瞭解咱們經常使用的gradle文件背後的原理,而不只僅是簡單地配置gralde。文中有些不太容易理解的地方,能夠根據文中給出的連接瞭解更多內容。
原文地址medium.com/@wasyl/unde…
推薦閱讀: