在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…
推荐阅读: