写点什么

深度探索 Gradle 自动化构建技术(二、Groovy 筑基篇)

用户头像
Android架构
关注
发布于: 29 分钟前

闭包对象.call(参数)闭包对象(参数)


如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。表示闭包中没有参数的示例代码:


def noParamClosure = { -> true }

注意点:省略圆

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


括号


函数最后一个参数都是一个闭包,类似于回调函数的用法,代码如下所示:


task JsonChao {doLast ({println "love is peace~"}})


// 似乎好像 doLast 会立即执行一样 task JsonChao {doLast {println "love is peace~"}}

闭包的用法

闭包的常见用法有如下 四种:


  • 1)、与基本类型的结合使用。

  • 2)、与 String 类的结合使用。

  • 3)、与数据结构的结合使用。

  • 4)、与文件等结合使用。

闭包进阶

  • 1)、闭包的关键变量

  • this

  • owner

  • delegate

  • 2)、闭包委托策略

闭包的关键变量

this 与 owner、delegate

其差异代码如下代码所示:


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 则会指向离它最近的那个闭包对象

delegate 与 this、owner 的差异

其差异代码如下代码所示:


def nestClouser = {def innnerClouser = {// 代表闭包定义处的类 printlin "scriptClouser this:" + this// 代表闭包定义处的类或者对象 printlin "scriptClouser this:" + owner// 代表任意对象,默认与 ownner 一致 printlin "scriptClouser this:" + delegate}


// 修改默认的 delegateinnnerClouser.delegate = pinnnerClouser.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_ONLYprintln stu.toString()


需要注意的是,要想使上述 pretty 闭包的 delegate 修改生效,必须选择其委托策略为 Closure.DELEGATE_ONLY,默认是 Closure.OWNER_FIRST 的。

3、Groovy 数据结构

Groovy 常用的数据结构有如下 四种:


  • 1)、数组

  • 2)、List

  • 3)、Map

  • 4)、Range


数组的使用和 Java 语言类似,最大的区别可能就是定义方式的扩展,如下代码所示:


// 数组定义 def array = [1, 2, 3, 4, 5] as int[]int[] array2 = [1, 2, 3, 4, 5]


下面,我们看看其它三种数据结构。

1、List

即链表,其底层对应 Java 中的 List 接口,一般用 ArrayList 作为真正的实现类,List 变量由[]定义,其元素可以是任何对象


链表中的元素可以通过索引存取,而且 不用担心索引越界。如果索引超过当前链表长度,List 会自动往该索引添加元素。下面,我们看看 List 最常使用的几个操作。

1)、排序

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}

2)、添加

// 添加 list.add(6)list.leftShift(7)list << 8

3)、删除

// 删除 list.remove(7)list.removeAt(7)list.removeElement(6)list.removeAll { return it % 2 == 0 }

4)、查找

// 查找 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 }

5)、获取最小值、最大值

// 最小值、最大值 list.min()list.max(return Math.abs(it))

6)、统计满足条件的数量

// 统计满足条件的数量 def num = findList.count { return it >= 2 }

Map

表示键-值表,其 底层对应 Java 中的 LinkedHashMap


Map 变量由[:]定义,冒号左边是 key,右边是 Value。key 必须是字符串,value 可以是任何对象。另外,key 可以用 '' 或 "" 包起来,也可以不用引号包起来。下面,我们看看 Map 最常使用的几个操作。

1)、存取

其示例代码如下所示:


aMap.keyNameaMap['keyName']aMap.anotherkey = "i am map"aMap.anotherkey = [a: 1, b: 2]

2)、each 方法

如果我们传递的闭包是一个参数,那么它就把 entry 作为参数。如果我们传递的闭包是 2 个参数,那么它就把 key 和 value 作为参数。


def result = ""[a:1, b:2].each { key, value ->result += "value"}


assert result == "a1b2"


def socre = ""[a:1, b:2].each { entry ->result += entry}


assert result == "a=1b=2"

3)、eachWithIndex 方法

如果闭包采用两个参数,则将传递 Map.Entry 和项目的索引(从零开始的计数器);否则,如果闭包采用三个参数,则将传递键,值和索引。


def result = ""[a:1, b:3].eachWithIndex { key, value, index -> result += "key$value)" }assert result == "0(a1)1(b3)"


def result = ""[a:1, b:3].eachWithIndex { entry, index -> result += "entry)" }assert result == "0(a=1)1(b=3)"

4)、groupBy 方法

按照闭包的条件进行分组,代码如下所示:


def group = students.groupBy { def student ->return student.value.score >= 60 ? '及格' : '不及格'}

5)、findAll 方法

它有两个参数,findAll 会将 Key 和 Value 分别传进 去。并且,如果 Closure 返回 true,表示该元素是自己想要的,如果返回 false 则表示该元素不是自己要找的。

Range

表示范围,它其实是 List 的一种拓展。其由 begin 值 + 两个点 + end 值表示。如果不想包含最后一个元素,则 begin 值 + 两个点 + < + end 表示。我们可以通过 aRange.from 与 aRange.to 来获对应的边界元素


如果需要了解更多的数据结构操作方法,我们可以直接查 Groovy API 详细文档 即可。

4、Groovy 面向对象

如果不声明 public/private 等访问权限的话,Groovy 中类及其变量默认都是 public 的

1)、元编程(Groovy 运行时)

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 {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.sexperson.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)}

2)、脚本中的变量和作用域

对于每一个 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

四、文件处理

1、常规文件处理

1)、读文件

eachLine 方法

我们可以使用 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 "{oneLine}"}


然后,我们可以使用 'targetFile.bytes' 直接得到文件的内容。

使用 InputStream

此外,我们也可以通过流的方式进行文件操作,如下代码所示:


//操作 ism,最后记得关掉 def ism = targetFile.newInputStream()// do sthism.close

使用闭包操作 inputStream

利用闭包来操作 inputStream,其功能更加强大,推荐使用这种写法,如下所示:


targetFile.withInputStream{ ism ->// 操作 ism,不用 close。Groovy 会自动替你 close}

2)、写文件

关于写文件有两种常用的操作形式,即通过 withOutputStream/withInputStream 或 withReader/withWriter 的写法。示例代码如下所示:

通过 withOutputStream/、withInputStream copy 文件

def srcFile = new File(源文件名)def targetFile = new File(目标文件名) targetFile.withOutputStream{ os->srcFile.withInputStream{ ins->os << ins //利用 OutputStream 的<<操作符重载,完成从 inputstream 到 OutputStream //的输出}}

通过 withReader、withWriter copy 文件

def copy(String sourcePath, String destationPath) {try {//首先创建目标文件 def desFile = new File(destationPath)if (!desFile.exists()) {desFile.createNewFile()}


//开始 copynew 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 对象。示例代码如下所示:

保存对应的 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}

从文件中读取 Object 对象

def readObject(String path) {def obj = nulltry {def file = new File(path)if (file == null || !file.exists()) return null//从文件中读取对象 file.withObjectInputStream { input ->obj = input.readObject()}} catch (Exception e) {


}return obj}

2、XML 文件操作

1)、获取 XML 数据

首先,我们定义一个包含 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()

2)、获取 XML 数据的两种遍历方式

获取 XML 数据有两种遍历方式:深度遍历 XML 数据 与 广度遍历 XML 数据,下面我们看看它们各自的用法,如下所示:

深度遍历 XML 数据

def titles = response.depthFirst().findAll { book ->return book.author.text() == '李刚' ? true : false}println titles.toListString()

广度遍历 XML 数据

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'

3)、生成 XML 数据

除了使用 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 = 3boolean mainstream = truedef 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 flavorString versionString value}

4)、Groovy 中的 json

我们可以 使用 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()

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
深度探索 Gradle 自动化构建技术(二、Groovy 筑基篇)