Groovy 做为 Gradle 这一强大构建工具的核心语言,其重要性不言而喻,可是 Groovy 自己是十分复杂的,要想全面地掌握它,我想几十篇万字长文也没法将其完全描述。所幸的是,在 Gradle 领域中涉及的 Groovy 知识都是很是基础的,所以,本篇文章的目的是为了在后续深刻探索 Gradle 时作好必定的基础储备。html
DSL(domain specific language),即领域特定语言,例如:Matliba、UML、HTML、XML 等等 DSL 语言。能够这样理解,Groovy 就是 DSL 的一个分支。java
总的来讲,DSL 的 核心思想 就是:“求专不求全,解决特定领域的问题”。node
Groovy 的特色具备以下 三点:android
由于 Groovy 语言相较其它编程语言而言,其 入门的学习成本是很是低的,由于它的语法就是对 Java 的扩展,因此,咱们能够用学习 Java 的方式去学习 Groovy。git
其特性主要有以下 三种:github
须要注意的是,在咱们使用 Groovy 进行 Gradle 脚本编写的时候,都是使用的面向过程进行编程的。web
Groovy 的优点有以下 四种:编程
Groovy 官方网址json
从官网下载好 Groovy 文件以后,咱们就能够看到 Groovy 的目录结构,其中咱们须要 重点关注 bin 和 doc 这个两个文件夹。设计模式
bin 文件夹的内容以下所示:
这里咱们了解下三个重要的可执行命令文件,以下所示:
在 doc 文件夹的下面有一个 html 文件,其中的内容以下所示:
这里的 api 和 documentation 是咱们须要重点关注的,其做用分别以下所示:
下面是 Groovy 中全部的关键字,命名时尤为须要注意,以下所示:
as、assert、break、case、catch、class、const、continue、def、default、
do、else、enum、extends、false、finally、for、goto、if、implements、
import、in、instanceof、interface、new、null、package、return、super、
switch、this、throw、throws、trait、true、try、while
复制代码
对于每个 field,Groovy 都会⾃动建立其与之对应的 getter 与 setter 方法,从外部能够直接调用它,而且 在使⽤ object.fieldA 来获取值或者使用 object.fieldA = value 来赋值的时候,实际上会自动转而调⽤ object.getFieldA() 和 object.setFieldA(value) 方法。
若是咱们不想调用这个特殊的 getter 方法时则可使用 .@ 直接域访问操做符。
须要注意的是,咱们在使用的时候,若是当前这个函数是 Groovy API 或者 Gradle API 中比较经常使用的,好比 println,就能够不带括号。不然仍是带括号。否则,Groovy 可能会把属性和函数调用混淆。
assert (!"android") == false
复制代码
assert 2 ** 3 == 8
复制代码
if (android) {}
复制代码
// 省略了name
def result = name ?: "Unknown"
复制代码
println order?.customer?.address
复制代码
注意,swctch 能够匹配列表当中任一元素,示例代码以下所示:
// 输出 ok
def num = 5.21
switch (num) {
case [5.21, 4, "list"]:
return "ok"
break
default:
break
}
复制代码
Groovy 的基础语法主要能够分为如下 四个部分:
Groovy 中的类型同 Java 同样,也是分为以下 两种:
可是,其实 Groovy 中并无基本类型,Groovy 做为动态语言, 在它的世界中,全部事物都是对象,就如 Python、Kotlin 同样:全部的基本类型都是属于对象类型。为了验证这个 Case,咱们能够新建一个 groovy 文件,建立一个 int 类型的变量并输出它,结果以下图所示:
能够看到,上面的输出结果为 'class java.lang.Integer',所以能够验证咱们的想法是正确的。实际上,Groovy 的编译器会将全部的基本类型都包装成对象类型。
groovy 变量的定义与 Java 中的方式有比较大的差别,对于 groovy 来讲,它有 两种定义方式,以下所示:
下面,咱们就使用 def 关键字来定义一系列的变量,并输出它们的类型,来看看是否编译器会识别出对应的类型,其结果以下图所示:
能够看到,编译器的确会自动自动推断对应的类型。
若是这个变量就是用于当前类或文件,而不会用于其它类或应用模块,那么,建议使用 def 类型,由于在这种场景下弱类型就足够了。
可是,若是你这个类或变量要用于其它模块的,建议不要使用 def,仍是应该使用 Java 中的那种强类型定义方式,由于使用强类型的定义方式,它不能动态转换为其它类型,它可以保证外界传递进来的值必定是正确的。若是你这个变量要被外界使用,而你却使用了 def 类型来定义它,那外界须要传递给你什么才是正确的呢?这样会使调用方很疑惑。
若是此时咱们在后面的代码中改变上图中 x1 的值为 String 类型,那么 x1 又会被编译器推断为 String 类型,以下图所示:
因而咱们能够猜想到,其实使用 def 关键字定义出来的变量就是 Obejct 类型。
Groovy 中的字符串与 Java 中的字符串有比较大的不一样,因此这里咱们须要着重了解一下。
Groovy 中的字符串除了继承了 Java 中传统 String 的使用方式以前,还 新增 了一个 GString 类型,它的使用方式至少有7、八种,可是经常使用的有三种定义方式。此外,在 GString 中新增了一系列的操做符,这可以让咱们对 String 类型的变量有 更便捷的操做。最后,在 GString 中还 新增 了一系列好用的 API,咱们也须要着重学习一下。
在 Groovy 中有 三种经常使用 的字符串定义方式,以下所示:
首先,须要说明的是,'无论是单引号、双引号仍是三引号,它们的类型都是 java.lang.String'。
既生瑜何生亮,其实否则。当咱们编写的单引号字符串中有转义字符的时候,须要添加 '',而且,当字符串须要具有多行格式的时候,强行将单引号字符串分红多行格式会变成由 '+' 号组成的字符串拼接格式。
双引号不一样与单、三引号,它定义的是一个可扩展的变量。这里咱们先看看两种双引号的使用方式,以下图所示:
在上图中,第一个定义的 author 字符串就是常规的 String 类型的字符串,而下面定义的 study 字符串就是可扩展的字符串,由于它里面使用了 '${author}' 的方式引用了 author 变量的内容。并且,从其最后的类型输出能够看到,可扩展的类型就是 'org.codehaus.groovy.runtime.GStringImpl' 类型的。
须要注意的是,可扩展的字符串是能够扩展成为任意的表达式,例如数学运算,以下图所示:
有了 Groovy 的这种可扩展的字符串,咱们就能够 避免 Java 中字符串的拼接操做,提高 Java 程序运行时的性能。
这里,咱们能够写一个 小栗子🌰 来看看实际的状况,以下图所示:
能够看到,咱们将 success 字符串传入了 come 方法,可是最终获得的类型为 result,因此,能够说明 编译器能够帮咱们自动在 String 和 GString 之间相互转换,咱们在编写的时候并不须要太过关注它们的区别。
闭包的本质其实就是一个代码块,闭包的核心内容能够归结为以下三点:
clouser.call()
clouser()
def xxx = { paramters -> code }
def xxx = { 纯 code }
复制代码
从 C/C++ 语言的角度看,闭包和函数指针很像,闭包能够经过 .call 方法来调用,也能够直接调用其构造函数,代码以下所示:
闭包对象.call(参数)
闭包对象(参数)
复制代码
若是闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的做用类 似。it 表明闭包的参数。表示闭包中没有参数的示例代码:
def noParamClosure = { -> true }
复制代码
函数最后一个参数都是一个闭包,相似于回调函数的用法,代码以下所示:
task JsonChao {
doLast ({
println "love is peace~"
}
})
// 彷佛好像doLast会当即执行同样
task JsonChao {
doLast {
println "love is peace~"
}
}
复制代码
闭包的常见用法有以下 四种:
其差别代码以下代码所示:
def scrpitClouser = {
// 表明闭包定义处的类
printlin "scriptClouser" this:" + this
// 表明闭包定义处的类或者对象
printlin "scriptClouser" this:" + owner
// 表明任意对象,默认与 ownner 一直
printlin "scriptClouser" this:" + delegate
}
// 输出都是 scrpitClouse 对象
scrpitClouser.call()
def nestClouser = {
def innnerClouser = {
// 表明闭包定义处的类
printlin "scriptClouser" this:" + this
// 表明闭包定义处的类或者对象
printlin "scriptClouser" this:" + owner
// 表明任意对象,默认与 ownner 一直
printlin "scriptClouser" this:" + delegate
}
innnerClouser.call()
}
// this 输出的是 nestClouser 对象,而 owner 与 delegate 输出的都是 innnerClouser 对象
nestClouser.call()
复制代码
能够看到,若是咱们直接在类、方法、变量中定义一个闭包,那么这三种关键变量的值都是同样的,可是,若是咱们在闭包中又嵌套了一个闭包,那么,this 与 owner、delegate 的值就再也不同样了。换言之,this 还会指向咱们闭包定义处的类或者实例自己,而 owner、delegate 则会指向离它最近的那个闭包对象。
其差别代码以下代码所示:
def nestClouser = {
def innnerClouser = {
// 表明闭包定义处的类
printlin "scriptClouser" this:" + this
// 表明闭包定义处的类或者对象
printlin "scriptClouser" this:" + owner
// 表明任意对象,默认与 ownner 一致
printlin "scriptClouser" this:" + delegate
}
// 修改默认的 delegate
innnerClouser.delegate = p
innnerClouser.call()
}
nestClouser.call()
复制代码
能够看到,delegate 的值是能够修改的,而且仅仅当咱们修改 delegate 的值时,delegate 的值才会与 ownner 的值不同。
其示例代码以下所示:
def stu = new Student()
def tea = new Teacher()
stu.pretty.delegate = tea
// 要想使 pretty 闭包的 delegate 修改生效,必须选择其委托策略为 Closure.DELEGATE_ONLY,默认是 Closure.OWNER_FIRST。
stu.pretty.resolveStrategy = Closure.DELEGATE_ONLY
println stu.toString()
复制代码
须要注意的是,要想使上述 pretty 闭包的 delegate 修改生效,必须选择其委托策略为 Closure.DELEGATE_ONLY,默认是 Closure.OWNER_FIRST 的。
Groovy 经常使用的数据结构有以下 四种:
数组的使用和 Java 语言相似,最大的区别可能就是定义方式的扩展,以下代码所示:
// 数组定义
def array = [1, 2, 3, 4, 5] as int[]
int[] array2 = [1, 2, 3, 4, 5]
复制代码
下面,咱们看看其它三种数据结构。
即链表,其底层对应 Java 中的 List 接口,通常用 ArrayList 做为真正的实现类,List 变量由[]定义,其元素能够是任何对象。
链表中的元素能够经过索引存取,并且 不用担忧索引越界。若是索引超过当前链表长度,List 会自动往该索引添加元素。下面,咱们看看 Map 最常使用的几个操做。
def test = [100, "hello", true]
// 左移位表示向List中添加新元素
test << 200
// list 定义
def list = [1, 2, 3, 4, 5]
// 排序
list.sort()
// 使用本身的排序规则
sortList.sort { a, b ->
a == b ?0 :
Math.abs(a) < Math.abs(b) ? 1 : -1
}
复制代码
// 添加
list.add(6)
list.leftShift(7)
list << 8
复制代码
// 删除
list.remove(7)
list.removeAt(7)
list.removeElement(6)
list.removeAll { return it % 2 == 0 }
复制代码
// 查找
int result = findList.find { return it % 2 == 0 }
def result2 = findList.findAll { return it % 2 != 0 }
def result3 = findList.any { return it % 2 != 0 }
def result4 = findList.every { return it % 2 == 0 }
复制代码
// 最小值、最大值
list.min()
list.max(return Math.abs(it))
复制代码
// 统计知足条件的数量
def num = findList.count { return it >= 2 }
复制代码
表示键-值表,其 底层对应 Java 中的 LinkedHashMap。
Map 变量由[:]定义,冒号左边是 key,右边是 Value。key 必须是字符串,value 能够是任何对象。另外,key 能够用 '' 或 "" 包起来,也能够不用引号包起来。下面,咱们看看 Map 最常使用的几个操做。
其示例代码以下所示:
aMap.keyName
aMap['keyName']
aMap.anotherkey = "i am map"
aMap.anotherkey = [a: 1, b: 2]
复制代码
若是咱们传递的闭包是一个参数,那么它就把 entry 做为参数。若是咱们传递的闭包是 2 个参数,那么它就把 key 和 value 做为参数。
def result = ""
[a:1, b:2].each { key, value ->
result += "$key$value"
}
assert result == "a1b2"
def socre = ""
[a:1, b:2].each { entry ->
result += entry
}
assert result == "a=1b=2"
复制代码
若是闭包采用两个参数,则将传递 Map.Entry 和项目的索引(从零开始的计数器);不然,若是闭包采用三个参数,则将传递键,值和索引。
def result = ""
[a:1, b:3].eachWithIndex { key, value, index -> result += "$index($key$value)" }
assert result == "0(a1)1(b3)"
def result = ""
[a:1, b:3].eachWithIndex { entry, index -> result += "$index($entry)" }
assert result == "0(a=1)1(b=3)"
复制代码
按照闭包的条件进行分组,代码以下所示:
def group = students.groupBy { def student ->
return student.value.score >= 60 ? '及格' : '不及格'
}
复制代码
它有两个参数,findAll 会将 Key 和 Value 分别传进 去。而且,若是 Closure 返回 true,表示该元素是本身想要的,若是返回 false 则表示该元素不是本身要找的。
表示范围,它实际上是 List 的一种拓展。其由 begin 值 + 两个点 + end 值表示。若是不想包含最后一个元素,则 begin 值 + 两个点 + < + end 表示。咱们能够经过 aRange.from 与 aRange.to 来获对应的边界元素。
若是须要了解更多的数据结构操做方法,咱们能够直接查 Groovy API 详细文档 便可。
若是不声明 public/private 等访问权限的话,Groovy 中类及其变量默认都是 public 的。
Groovy 运行时的逻辑处理流程图以下所示:
为了更好的讲解元编程的用法,咱们先建立一个 Person 类并调用它的 cry 方法,代码以下所示:
// 第一个 groovy 文件中
def person = new Person(name: 'Qndroid', age: 26)
println person.cry()
// 第二个 groovy 文件中
class Person implements Serializable {
String name
Integer age
def increaseAge(Integer years) {
this.age += years
}
/**
* 一个方法找不到时,调用它代替
* @param name
* @param args
* @return
*/
def invokeMethod(String name, Object args) {
return "the method is ${name}, the params is ${args}"
}
def methodMissing(String name, Object args) {
return "the method ${name} is missing"
}
}
复制代码
为了实现元编程,咱们须要使用 metaClass,具体的使用示例以下所示:
ExpandoMetaClass.enableGlobally()
//为类动态的添加一个属性
Person.metaClass.sex = 'male'
def person = new Person(name: 'Qndroid', age: 26)
println person.sex
person.sex = 'female'
println "the new sex is:" + person.sex
//为类动态的添加方法
Person.metaClass.sexUpperCase = { -> sex.toUpperCase() }
def person2 = new Person(name: 'Qndroid', age: 26)
println person2.sexUpperCase()
//为类动态的添加静态方法
Person.metaClass.static.createPerson = {
String name, int age -> new Person(name: name, age: age)
}
def person3 = Person.createPerson('renzhiqiang', 26)
println person3.name + " and " + person3.age
复制代码
须要注意的是经过类的 metaClass 来添加元素的这种方式每次使用时都须要从新添加,幸运的是,咱们能够在注入前调用全局生效的处理,代码以下所示:
ExpandoMetaClass.enableGlobally()
// 在应用程序初始化的时候咱们能够为第三方类添加方法
Person.metaClass.static.createPerson = { String name,
int age ->
new Person(name: name, age: age)
}
复制代码
对于每个 Groovy 脚原本说,它都会生成一个 static void main 函数,main 函数中会调用一个 run 函数,脚本中的全部代码则包含在 run 函数之中。咱们能够经过以下的 groovyc 命令用于将编译获得的 class 文件拷贝到 classes 文件夹下:
// groovyc 是 groovy 的编译命令,-d classes 用于将编译获得的 class 文件拷贝到 classes 文件夹 下
groovyc -d classes test.groovy
复制代码
当咱们在 Groovy 脚本中定义一个变量时,因为它其实是在 run 函数中建立的,因此脚本中的其它方法或其余脚本是没法访问它的。这个时候,咱们须要使用 @Field 将当前变量标记为成员变量,其示例代码以下所示:
import groovy.transform.Field;
@Field author = JsonChao
复制代码
咱们可使用 eachLine 方法读该文件中的每一行,它惟一的参数是一个 Closure,Closure 的参数是文件每一行的内容。示例代码以下所示:
def file = new File(文件名)
file.eachLine{ String oneLine ->
println oneLine
}
def text = file.getText()
def text2 = file.readLines()
file.eachLine { oneLine, lineNo ->
println "${lineNo} ${oneLine}"
}
复制代码
而后,咱们可使用 'targetFile.bytes' 直接获得文件的内容。
此外,咱们也能够经过流的方式进行文件操做,以下代码所示:
//操做 ism,最后记得关掉
def ism = targetFile.newInputStream()
// do sth
ism.close
复制代码
利用闭包来操做 inputStream,其功能更增强大,推荐使用这种写法,以下所示:
targetFile.withInputStream{ ism ->
// 操做 ism,不用 close。Groovy 会自动替你 close
}
复制代码
关于写文件有两种经常使用的操做形式,即经过 withOutputStream/withInputStream 或 withReader/withWriter 的写法。示例代码以下所示:
def srcFile = new File(源文件名)
def targetFile = new File(目标文件名) targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
os << ins //利用 OutputStream 的<<操做符重载,完成从 inputstream 到 OutputStream //的输出
}
}
复制代码
def copy(String sourcePath, String destationPath) {
try {
//首先建立目标文件
def desFile = new File(destationPath)
if (!desFile.exists()) {
desFile.createNewFile()
}
//开始copy
new File(sourcePath).withReader { reader ->
def lines = reader.readLines()
desFile.withWriter { writer ->
lines.each { line ->
writer.append(line + "\r\n")
}
}
}
return true
} catch (Exception e) {
e.printStackTrace()
}
return false
}
复制代码
此外,咱们也能够经过 withObjectOutputStream/withObjectInputStream 来保存与读取 Object 对象。示例代码以下所示:
def saveObject(Object object, String path) {
try {
//首先建立目标文件
def desFile = new File(path)
if (!desFile.exists()) {
desFile.createNewFile()
}
desFile.withObjectOutputStream { out ->
out.writeObject(object)
}
return true
} catch (Exception e) {
}
return false
}
复制代码
def readObject(String path) {
def obj = null
try {
def file = new File(path)
if (file == null || !file.exists()) return null
//从文件中读取对象
file.withObjectInputStream { input ->
obj = input.readObject()
}
} catch (Exception e) {
}
return obj
}
复制代码
首先,咱们定义一个包含 XML 数据的字符串,以下所示:
final String xml = '''
<response version-api="2.0">
<value>
<books id="1" classification="android">
<book available="20" id="1">
<title>疯狂Android讲义</title>
<author id="1">李刚</author>
</book>
<book available="14" id="2">
<title>第一行代码</title>
<author id="2">郭林</author>
</book>
<book available="13" id="3">
<title>Android开发艺术探索</title>
<author id="3">任玉刚</author>
</book>
<book available="5" id="4">
<title>Android源码设计模式</title>
<author id="4">何红辉</author>
</book>
</books>
<books id="2" classification="web">
<book available="10" id="1">
<title>Vue从入门到精通</title>
<author id="4">李刚</author>
</book>
</books>
</value>
</response>
'''
复制代码
而后,咱们能够 使用 XmlSlurper 来解析此 xml 数据,代码以下所示:
def xmlSluper = new XmlSlurper()
def response = xmlSluper.parseText(xml)
// 经过指定标签获取特定的属性值
println response.value.books[0].book[0].title.text()
println response.value.books[0].book[0].author.text()
println response.value.books[1].book[0].@available
def list = []
response.value.books.each { books ->
//下面开始对书结点进行遍历
books.book.each { book ->
def author = book.author.text()
if (author.equals('李刚')) {
list.add(book.title.text())
}
}
}
println list.toListString()
复制代码
获取 XML 数据有两种遍历方式:深度遍历 XML 数据 与 广度遍历 XML 数据,下面咱们看看它们各自的用法,以下所示:
def titles = response.depthFirst().findAll { book ->
return book.author.text() == '李刚' ? true : false
}
println titles.toListString()
复制代码
def name = response.value.books.children().findAll { node ->
node.name() == 'book' && node.@id == '2'
}.collect { node ->
return node.title.text()
}
复制代码
在实际使用中,咱们能够 利用 XmlSlurper 求获取 AndroidManifest.xml 的版本号(versionName),代码以下所示:
def androidManifest = new XmlSlurper().parse("AndroidManifest.xml") println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
复制代码
除了使用 XmlSlurper 解析 XML 数据以外,咱们也能够 使用 xmlBuilder 来建立 XML 文件,以下代码所示:
/**
* 生成 xml 格式数据
* <langs type='current' count='3' mainstream='true'>
<language flavor='static' version='1.5'>Java</language>
<language flavor='dynamic' version='1.6.0'>Groovy</language>
<language flavor='dynamic' version='1.9'>JavaScript</language>
</langs>
*/
def sw = new StringWriter()
// 用来生成 xml 数据的核心类
def xmlBuilder = new MarkupBuilder(sw)
// 根结点 langs 建立成功
xmlBuilder.langs(type: 'current', count: '3',
mainstream: 'true') {
//第一个 language 结点
language(flavor: 'static', version: '1.5') {
age('16')
}
language(flavor: 'dynamic', version: '1.6') {
age('10')
}
language(flavor: 'dynamic', version: '1.9', 'JavaScript')
}
// println sw
def langs = new Langs()
xmlBuilder.langs(type: langs.type, count: langs.count,
mainstream: langs.mainstream) {
//遍历全部的子结点
langs.languages.each { lang ->
language(flavor: lang.flavor,
version: lang.version, lang.value)
}
}
println sw
// 对应 xml 中的 langs 结点
class Langs {
String type = 'current'
int count = 3
boolean mainstream = true
def languages = [
new Language(flavor: 'static',
version: '1.5', value: 'Java'),
new Language(flavor: 'dynamic',
version: '1.3', value: 'Groovy'),
new Language(flavor: 'dynamic',
version: '1.6', value: 'JavaScript')
]
}
//对应xml中的languang结点
class Language {
String flavor
String version
String value
}
复制代码
咱们能够 使用 Groovy 中提供的 JsonSlurper 类去替代 Gson 解析网络响应,这样咱们在写插件的时候能够避免引入 Gson 库,其示例代码以下所示:
def reponse =
getNetworkData(
'http://yuexibo.top/yxbApp/course_detail.json')
println reponse.data.head.name
def getNetworkData(String url) {
//发送http请求
def connection = new URL(url).openConnection()
connection.setRequestMethod('GET')
connection.connect()
def response = connection.content.text
//将 json 转化为实体对象
def jsonSluper = new JsonSlurper()
return jsonSluper.parseText(response)
}
复制代码
在这篇文章中,咱们从如下 四个方面 学习了 Groovy 中的必备核心语法:
在后面咱们自定义 Gradle 插件的时候须要使用到这些技巧,所以,掌握好 Groovy 的重要性不言而喻,只有扎实基础才能让咱们走的更远。
二、《慕课网之Gradle3.0自动化项目构建技术精讲+实战》1 - 5章
三、《深刻理解 Android 之 Gradle》
欢迎关注个人微信:
bcce5360
因为微信群已超过 200 人,麻烦你们想进微信群的朋友们,加我微信拉你进群。
2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎你们加入~