前言

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

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

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

安装

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

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

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

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

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

基础语法

变量

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

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

// 自动推导
def name = "groovy"
// 指定变量类型
int age = 3

println name.class

println age.class

输出结果如下

class java.lang.String
class java.lang.Integer

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

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

def pi = 3.14

println pi.class

输出如下所示

class java.math.BigDecimal

字符串

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

def str1 = 'str1'
def str2 = "str2"
def str3 = '''str3 line1
str3 line2
str3 line3'''

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

输出如下所示

str1
str2
str3 line1
str3 line2
str3 line3

str4 line2
str4 line3
str4 line4

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

def name = "groovy"
def age = 3

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

println info
println info.class

输出如下所示

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

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

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

闭包

简单使用

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

def closure = {
    println "Hello Groovy Closure"
}

closure()

closure.call()

输出如下所示

Hello Groovy Closure
Hello Groovy Closure

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

带有参数的闭包

// 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)

输出结果如下

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 。闭包的委托策略是其独有的,再了解委托策略之前我们先来了解一下闭包中的 this 、 owner 、 delegate

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

先来看个例子

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()

输出结果如下

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

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

既然在嵌套的时候 this 与 owner 不同,那什么时候 owner 与 delegate 不同?

class Person {

}

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

def p = new Person()

closure.delegate = p

closure()

输出如下所示

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

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

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()

输出结果如下

My name is Groovy
My name is Groovy

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

我们可以通过更改 delegate 的 resolveStrategy 为 Closure.DELEGATE_FIRST 来使其生效

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()

输出结果如下

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

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]}"

输出结果如下

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

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"
}

输出结果如下

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 中主要的变化就是使用 : 来区分键值对。

文件操作

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
}

输出结果如下

println 'Hello Groovy'
println 'Hello Groovy'
[println 'Hello Groovy']
println 'Hello Groovy'

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

参考