前言

Android 中现在默认的构建工具是 Gradle ,而 Gradle 构建工具是使用 Groovy 语言编写的,这就使得了解 Groovy 语言变得非常重要了。

如果你不了解 Groovy 会以为 build.gralde 文件中写的就只是一些配置,其实并没有这么简单。之所以看起来项配置文件,是因为 Groovy 的闭包特性,让我们写起来简单,看起来像配置文件,这就降低了编写的门槛。使得我们可以专注于 Android 层面的业务逻辑。

随着 Android 的发展,我们不能局限于应用层,在构建的过程我们也可以进行一些优化。今天就来学习一下 Groovy 这们语言。

安装

安装很简单,我们可以从Groovy官网下载安装包,也可以通过包管理器来安装, macOS 下可以通过如下命令安装

1
2
3
brew install groovy
# 下面这条指令也可以,根据自己的系统选择一个常用的就行
curl -s get.sdkman.io | bash

安装好之后我们可以通过如下指令验证是否安装成功

1
2
groovy --version
Groovy Version: 3.0.3 JVM: 1.8.0_45 Vendor: Oracle Corporation OS: Mac OS X

可以看到输出了 groovy 的版本。

基础语法

变量

groovy 中声明变量有两种,一种是自动推导。编译器会根据你写的字面量去推导出合适的类型,自动推导的都是对象类型,不是基本数据类型。

如果你要使用基本数据类型,就只能直接指定变量的类型。如下所示

1
2
3
4
5
6
7
8
// 自动推导
def name = "groovy"
// 指定变量类型
int age = 3

println name.class

println age.class

输出结果如下

1
2
class java.lang.String
class java.lang.Integer

可以看到即便我们指定了变量的类型,最后还是会被装换成对象类型。

这里还要一个地方要注意的就是,浮点数会被推导为 BigDecimal 而不是 Double

1
2
3
def pi = 3.14

println pi.class

输出如下所示

1
class java.math.BigDecimal

字符串

groovy 中的字符串与 Java 中的不同,更像是 Python 中的字符串,支持单引号,双引号,三引号的写法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def str1 = 'str1'
def str2 = "str2"
def str3 = '''str3 line1
str3 line2
str3 line3'''

def str4 = """
str4 line2
str4 line3
str4 line4
"""

输出如下所示

1
2
3
4
5
6
7
8
9
str1
str2
str3 line1
str3 line2
str3 line3

str4 line2
str4 line3
str4 line4

其中双引号可以支持在字符串中引用其他变量,这时候的类型为 GStringImpl

1
2
3
4
5
6
7
def name = "groovy"
def age = 3

def info = "the ${name} age is: ${age}"

println info
println info.class

输出如下所示

1
2
the groovy age is: 3
class org.codehaus.groovy.runtime.GStringImpl

可以看到通过 $ 可以引用别的变量,这时候字符串的类型为 org.codehaus.groovy.runtime.GStringImpl 而不再是 String 了。它们之间可以相互赋值使用,不需要强制类型转换。

使用这种方式拼接字符串就很方便,非常直观,不用写一堆的 + 。这中方式也叫模板字符串。在 ${} 中还可以进行求值计算。

闭包

简单使用

闭包和 Java 中的 lambda 表达式类似

1
2
3
4
5
6
7
def closure = {
    println "Hello Groovy Closure"
}

closure()

closure.call()

输出如下所示

1
2
Hello Groovy Closure
Hello Groovy Closure

可以看到闭包就像一个函数一样,可以通过函数调用也可以通过 call() 来调用。

带有参数的闭包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1 带有参数的闭包
def add = { x, y ->
    x + y
}

println add(1,2)
// 参数有类型
def add1 = { int x, int y ->
    x + y
}

println add(1,2)

// 参数有返回值
def add2 = { int x, int y ->
    return x + y
}

println add2(1,2)


def increment = {
    it + 1
}

println increment(1)

输出结果如下

1
2
3
4
the add(1,2) result is: 3
the add1(1,2) result is: 3
the add2(1,2) result is: 3
the increment(1) result is: 2

闭包的参数类型可以不指定,闭包的最后一行代码的执行结果默认会作为返回值。

如果闭包只有一个参数,我们可以不指定参数名,闭包会提供一个 it 来代替。

闭包委托策略

闭包在 groovy 中也是一个对象,其类名是 groovy.lang.Closure 。闭包的委托策略是其独有的,再了解委托策略之前我们先来了解一下闭包中的 thisownerdelegate

  • this: 表示闭包在哪个类定义的
  • owner: 表示闭包直接定义的地方,可以是个闭包或者类,这种情况通常是在闭包嵌套的时候会和 this 不一样
  • delegate: 用来确定方法的调用者或者属性是哪个对象的,默认和 owner 一致。代表的是第三方对象,在这个第三方对象中你可以找到闭包中未定义的方法调用或者属性。

先来看个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def closure = {

    println "this is:" + this
    println "owner is:" + owner
    println "delegate is:" + delegate

    def innerClosure = {
        println "innerClosure this is:" + this
        println "innerClosure owner is:" + owner
        println "innerClosure delegate is:" + delegate
    }
    innerClosure()
}

closure()

输出结果如下

1
2
3
4
5
6
this is:Main@7fcbe147
owner is:Main@7fcbe147
delegate is:Main@7fcbe147
innerClosure this is:Main@7fcbe147
innerClosure owner is:Main$_run_closure1@36b0fcd5
innerClosure delegate is:Main$_run_closure1@36b0fcd5

可以看到默认它们都指向同一个对象。当闭包嵌套的时候 thisownerdelegate 不同。

既然在嵌套的时候 thisowner 不同,那什么时候 ownerdelegate 不同?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Person {

}

def closure = {
    println "this is:" + this
    println "owner is:" + owner
    println "delegate is:" + delegate
}

def p = new Person()

closure.delegate = p

closure()

输出如下所示

1
2
3
this is:Main@410ae9a3
owner is:Main@410ae9a3
delegate is:Person@25ddbbbb

delegate 指向了 Person 对象并且 delegate 是可以被修改的,而 thisowner 则不可以被修改。那 delegate 有什么用呢?来看下面的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Person {
    String name
    def pretty = { "My name is $name" }
    String toString() {
        pretty()
    }
}
class Thing {
    String name
}

def p = new Person(name: 'Groovy')
def t = new Thing(name: 'Java')

println p.toString()
p.pretty.delegate = t
println p.toString()

输出结果如下

1
2
My name is Groovy
My name is Groovy

可以看到我们改了 pretty 闭包的 delegate 但是并没有任何效果。这是因为 delegate 的默认策略是 Closure.OWNER_FIRST ,所以没有生效。

我们可以通过更改 delegateresolveStrategyClosure.DELEGATE_FIRST 来使其生效

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Person {
    String name
    def pretty = { "My name is $name" }
    String toString() {
        pretty()
    }
}
class Thing {
    String name
}

def p = new Person(name: 'Groovy')
def t = new Thing(name: 'Java')

println p.toString()
p.pretty.delegate = t
// 修改 delegate 策略
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
println p.toString()

输出结果如下

1
2
My name is Groovy
My name is Java

这次更改就生效了。 resolveStrategy 可以有如下几种策略

  • Closure.OWNER_FIRST 默认为这个策略,先从 owner 中找属性或方法,找不到再从 delegete 中寻找。
  • Closure.DELEGATE_FIRST 先从 delegate 中查找,与 OWNER_FIRST 相反。
  • Closure.OWNER_ONLY 只在 owner 中寻找,找不到就报错
  • Closure.DELEGATE_ONLY 只在 delegate 中寻找,找不到就报错
  • Closure.TO_SELF 在闭包自身中寻找。

集合

groovy 中对 Java 的集合做了不少的加强,使得使用起来更加方便。

List

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def list = [1, 2, 3, 4, 5, 6]

def list1 = [1, "a", true]

println "the list is: ${list}"
println "ths list1 is: ${list1}"
// 默认是 ArrayList
println "the list class is: ${list.class}"

// 往 list 中添加元素
list << 7
println "the list after add is: ${list}"

// 获取 list 中某个区间
println "the list[0,2] is: ${list[0..2]}"
// 获取 list 中指定位置的值
println "the list[0,2] is: ${list[0,2]}"

输出结果如下

1
2
3
4
5
6
the list is: [1, 2, 3, 4, 5, 6]
ths list1 is: [1, a, true]
the list class is: class java.util.ArrayList
the list after add is: [1, 2, 3, 4, 5, 6, 7]
the list[0,2] is: [1, 2, 3]
the list[0,2] is: [1, 3]

其中 << 是添加元素的语法糖,重载了左移操作符。其他和 ArrayList 的操作类似。

还要一个要注意的是,在 List 中可以添加任意类型的元素。

Map

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def map = [a: 1, b: 2, c: 3]
println "the a in map is: ${map['a']}"
println "the a in map is: ${map.a}"


// 添加元素
map['d'] = 4
println "after add map is: ${map}"

// 遍历
map.each { key, value ->
    println "${key}:${value}"
}

map.eachWithIndex { key, value, index ->
    println "the $index is $key : $value"
}

输出结果如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
the a in map is: 1
the a in map is: 1
after add map is: [a:1, b:2, c:3, d:4]
a:1
b:2
c:3
d:4
the 0 is a : 1
the 1 is b : 2
the 2 is c : 3
the 3 is d : 4

Map 中主要的变化就是使用 : 来区分键值对。

文件操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def file = new File('./test.groovy')

// 写文件
file.createNewFile()
file.withWriter { writer ->
    writer.append("println 'Hello Groovy'")
}

// 读文件
file.eachLine { line ->
    println line
}

println file.getText()

// 获取每一行,返回是个数组
println file.readLines()

println file.withReader { reader ->
    char[] buffer = new char[100]
    reader.read(buffer)
    return buffer
}

输出结果如下

1
2
3
4
println 'Hello Groovy'
println 'Hello Groovy'
[println 'Hello Groovy']
println 'Hello Groovy'

groovy 中操作文件比在 Java 中方便许多。

参考