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,仍是应该使用 Java 中的那种强类型定义方式,由于使用强类型的定义方式,它不能动态转换为其它类型,它可以保证外界传递进来的值必定是正确的。若是你这个变量要被外界使用,而你却使用了 def 类型来定义它,那外界须要传递给你什么才是正确的呢?这样会使调用方很疑惑。
若是此时咱们在后面的代码中改变上图中 x1 的值为 String 类型,那么 x1 又会被编译器推断为 String 类型,因而咱们能够猜想到,其实使用 def 关键字定义出来的变量就是 Obejct 类型。
Groovy 中的字符串与 Java 中的字符串有比较大的不一样,因此这里咱们须要着重了解一下。
Groovy 中的字符串除了继承了 Java 中传统 String 的使用方式以前,还 新增 了一个 GString 类型,它的使用方式至少有7、八种,可是经常使用的有三种定义方式。此外,在 GString 中新增了一系列的操做符,这可以让咱们对 String 类型的变量有 更便捷的操做。最后,在 GString 中还 新增 了一系列好用的 API,咱们也须要着重学习一下。
在 Groovy 中有 三种经常使用 的字符串定义方式,以下所示:
首先,须要说明的是,'无论是单引号、双引号仍是三引号,它们的类型都是 java.lang.String'。
既生瑜何生亮,其实否则。当咱们编写的单引号字符串中有转义字符的时候,须要添加 '',而且,当字符串须要具有多行格式的时候,强行将单引号字符串分红多行格式会变成由 '+' 号组成的字符串拼接格式。
双引号不一样与单、三引号,它定义的是一个可扩展的变量。这里咱们先看看两种双引号的使用方式,以下图所示:
在上图中,第一个定义的 name 字符串就是常规的 String 类型的字符串,而下面定义的 sayHello 字符串就是可扩展的字符串,由于它里面使用了 '${name}' 的方式引用了 name 变量的内容。并且,从其最后的类型输出能够看到,可扩展的类型就是 'org.codehaus.groovy.runtime.GStringImpl' 类型的。
须要注意的是,可扩展的字符串是能够扩展成为任意的表达式,例如数学运算,如上图中的 sum 变量。
有了 Groovy 的这种可扩展的字符串,咱们就能够 避免 Java 中字符串的拼接操做,提高 Java 程序运行时的性能。
不须要,编译器能够帮咱们自动在 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 会自动往该索引添加元素。下面,咱们看看 List 最常使用的几个操做。
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》