Gradle之Groovy基础
Gradle系列之从init.gradle说起 - CSDN博客
Android | 飞雪无情的博客
Gradle完全解析 - Android 同学会 - KM平台
Gradle脚本对Java程序员来说非常简单易懂,我们甚至可以直接在Gradle脚本中写Groovy代码甚至Java代码。但不了解Groovy和Gradle DSL的话,你会发现Gradle脚本其实地方让人很疑惑。
关于Gradle脚本的疑问
1 | apply plugin: 'com.android.application' |
这是一个典型的Gradle脚本。就这个脚本而言,我有两个疑问:
- 第一,
apply plugin: 'com.android.application'
的语法形式怪怪的,这里有个”:”。Gradle脚本中什么时候会出现”:”,什么时候又不需要”:”? - 第二,
defaultConfig {}
中minSdkVersion 15
和vectorDrawables.useSupportLibrary = true
的语法形式并不一致。Gradle脚本中什么时候会出现”=”,什么时候又不需要”=”?
要清楚地答这两个问题,需要从Groovy语言和Gradle DSL入手。
Groovy基础
掌握Gradle需要了解的Groovy基础知识如下:
Groovy基础一文对Groovy有一个非常简明的介绍,我画了一张图对其内容进行总结。
下面结合上图以及我的问题,有针对性的了解一些Groovy基础内容,包括
- Groovy的GString
String与GString
GString给我们提供了强大的格式化能力。有了GString,完全可以避免使用Java的String.format()
来格式化输出。看看示例。
1 | def version = 2.4 |
通过以上例子不难明白普通字符串与GString的区别。现在不妨看看这段Gradle脚本中buildConfigField
这一行,考查下自己是否掌握GString的用法。
1 | apply plugin: 'com.android.library' |
分析如下:
- 首先,这个字符串是双引号且有未被转义的$
- 其次,${gitSha()}将调用
gitSha()
方法并将其结果作为字符串的值。{}
中可以是任意合法的groovy表达式
Groovy方法
调用Groovy方法时可以省略括号,并且Groovy方法支持命名参数。
省略括号
省略括号可以让代码更简短,比如output()
方法:
1 | def output(a) { |
如果方法有两个或两个以上的参数呢?
1 | def add(a, b) { |
注意:add 1,2
的确是add(1,2)
省略括号的写法, 1,2
表示两个整形参数, 而不是一个包含两个整数的列表或数组,或别的什么东西。
命名参数
Groovy方法支持命名参数。当然,跟Python中的命名参数不同,Groovy命名参数并不是真正意义上的命名参数。实际上,Groovy仅仅是在让方法的map参数的key可以在方法中直接使用而已(以map.key这种方式来访问参数)。
1 | def add(map) { |
Groovy集合
集合不是我们关注重点,这里只简单提一下。字面量形式的list和map使用非常方便。另外list和map均提供each()方法用于便捷访问。
1 | def list = [1, 2, 3] |
Groovy闭包
为方便对上述第二个问题展开讨论,我们必须Groovy闭包。闭包是Groovy最强大的特性。
闭包介绍
什么是闭包呢?简单来讲,Groovy的闭包可以理解为”代码块”,它本质也是一个对象,所以也可以被引用、被传递以及被执行。(可以类比C中的函数指针以及Java 8的方法引用)Groovy中,”代码块”对象具体来说是Closure的对象。
为什么需要闭包呢?我们经常遇到单方法接口,比如Java中的FileFilter
或Android中的OnClickListener
,前者只定义了一个方法accept(File)
,后者只定义了一个方法onClick(View)
。这种代码组织方式对于单方法接口非常繁琐,我们不得不为一些很简单的操作定义类,导致类数量膨胀。闭包能在一定程度上解决这个问题。
还是上代码,看代码可能会更直观地了解闭包。
1 | [1, 2, 3].forEach(new Consumer<Integer>() { |
代码的功能是逐个打印列表中的元素。分别使用单方法接口的方式和闭包的方式来完成功能。显而易见后一个方法更可读,尤其当你习惯这种写法之后。该语句的各个元素如下:
each()
方法接收一个闭包(Closure)作为参数。Closure定义如下:
1 | public abstract class Closure<V> { |
Closure表示Groovy中的闭包对象,它是一个普通的Java类。Groovy允许Closure以如下形式被调用:
1 | def a = 1 |
简单来说,一个闭包是被包装为一个对象的代码块。下面的代码
1 | log = '' |
闭包委托
Groovy闭包的强大之处在于它支持闭包方法的委托。Groovy的闭包有thisObject、owner、delegate三个属性,当你在闭包内调用方法时,由他们来确定使用哪个对象来处理。默认情况下delegate和owner是相等的,但是delegate是可以被修改的,这个功能是非常强大的,Gradle中的很闭包的很多功能都是通过修改delegate实现的 来源
了解闭包之后再来看本文开头出现的Gradle脚本,这里只选取关键部分
1 | android { |
分析如下
android {}
其实是一个方法调用,方法形式为android(Closure)
(还记得方法括号可省略,方法最后一个参数如果是闭包,闭包可以放到括号外面的规则吗?)minSdkVersion 26
也是一个方法调用,方法形式为minSdkVersion(int)
,所以它的完整形式其实是minSdkVersion(26)
(仍然是方法括号可省略的规则)vectorDrawables.useSupportLibrary = true
是对属性赋值
不难使用闭包委托实现自己的DSL。
1 | def android(Closure<Android> closure) { |
至此你应该明白为什么defaultConfig {}
中minSdkVersion 15
和vectorDrawables.useSupportLibrary = true
,一个有”=”一个没有”=”了吧。
Gradle解惑
上面代码中的add a: 100
不仅不会怪怪的,还很眼熟,对吧。嗯,apply plugin: 'com.android.library'
就是这么来的。当然apply()
方法远比我们这里的add()
方法要复杂,apply()
方法的完整形式是Project.apply(map)
。这里不展开说明,后面的文章中我会详细讨论。
总结
回头来看Groovy中关于方法的一些规则其实是有意为之,这些小规则再加上Groovy的闭包委托功能,可以非常方便地实现Gradle DSL。要点总结如下:
- 闭包的委托优先模式
- 方法调用可以省略括号;方法的最后一个参数是闭包,则闭包可以放在括号外面