Gradle 爬坑指南 -- 概念初解、Grovvy 语法、常见 API

4,863 阅读18分钟

书接上文:Gradle 爬坑指南 -- 导论

上回说到学习资料,我给大家理了理这些资料大概的一个学习顺序,本文咱们正是开始 Gradle 的学习。先从理解 Gradle 构建工具、Grovvy 脚本语言、Grovvy 语法开始,一步步深入理解,希望这样能让大家都能学的会

(*  ̄3)(ε ̄ *)

理解 Gradle、Groovy

对于拦路虎、大 Boss,理论先于实际。手握理论的浮尘,方能遇坑过坑、遇水搭桥 (๑•̀ㅂ•́)و✧

1. 什么是构建工具

简单的说就是自动化的编译、打包程序

我们来回忆一下,入门 java 那会,大家都写过 Hello Wrold!吧。然后老师让我们干啥,javac 编译, java 运行。在这里编译需要我们手动执行一次 javac,大家想过没有,要是有100个文件呢?那我们就得手动 100次 javac 编译指令

到这里大家都会想到自动化吧,是的,自动化编译工具就是最早的构建工具了。然后我们拓展其功能,比如说:

  • 100个文件,编译后我要分10个文件夹保存
  • 哎呀,文件夹不好使了,别人要我提供 .jar 文件
  • 我去,产品加功能了,要加入 C++ 文件进来,C、java 文件要一起编译
  • 产品要有展示图片,还要有声音,多媒体资源也要加进来
  • 业务拓展了好几个渠道,每一个渠道都要提供一个定制化的 .jar 出来
  • 业务拓展了,要全平台了,win、android、ios 都要支持

上面都是我臆想的,不过我觉得发展的历程大同小异。随着需求叠加、平台扩展,对代码最终产品也是有越来越多的要求。jar/aar/exe 这些打包时有太多的不一样,我们是人不是机器,不可能记得住的这些差异不同。那就必须依靠自动化技术、工具,要能支持平台、需求等方面的差异、能添加自定义任务的、专门的用来打包生成最终产品的一个程序、工具,这个就是构建工具。构建工具本质上还是一段代码程序

我这样说是想具体一点,让大家有些代入感好理解构建工具是什么。就像下图,麦子就是我们的代码、资源等,草垛就是最终打包出来的成品,机器就是构建工具。怎么打草垛我们不用管,只要我们会用机器就行了

打包也没什么神奇的,就是根据不同平台的要求,怎么把烂七八糟的都装一块,和妹子们出门前收拾衣服打包装箱一样。打包的目的是减少文件体积,方便安装,任何打包出来的安装包,本质都是一个压缩包

2. Gradle 也是一种构建工具

Android 项目这么多东西,既有我们自己写的 java、kotlin、C++、Dart 代码,也有系统自己的 java、C++ 代码,还有引入的第三方代码,还有图片、音乐、视频文件,这么多代码、资源打包成 APK 文件肯定要有一个规范,干这个活的就是我们熟悉的 gradle 了

APK 文件我们解压可以看到好多文件和文件夹,具体不展开了

不用把 Gradle 想的太难了,Gradle 就是帮我们打包生成 apk 的一个程序。难点的在于很灵活,我们可以在其中配置、声明参数、执行自己写的脚本、甚至导入自己的写的插件,来完成我们自定义的额外的任务。但是不要本末倒置,Gradle 就是帮我们打包 APK 的一个工具罢了

下面3段话大家理解下,我觉得说的都挺到位的,看过后面还可以翻回来看这3句话,算是对 Gradle 的总结性文字了,很好~

Gradle 是通用构建、打包程序,可以支持 java、web、android 等项目,具体到你的平台怎么打包,还得看你引入的什么插件,插件会具体按照我们平台的要求去编译、打包。比如我引入的:apply plugin: 'com.android.application',我导入的是 android 编译打包插件,那么最终会生成 APK 文件,就是这样。我引入的:apply plugin: 'com.android.library' android lib 库文件插件,那么最终会生成 aar 文件

Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西

Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。Gradle中每一个待编译的工程都是一个Project,一个具体的编译过程是由一个一个的Task来定义和执行的。一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西

3. Gradle 是个程序、Groovy 是特定领域 DSL 语言

  • Gradle 是运行在 JVM 实例上的一个程序,内部使用 Groovy 语言
  • Groovy 是一种 JVM 上的脚本语言,基于 java 扩展的动态语言

Gradle 简单来说就是在运行在 JVM 上的一个程序罢了,虽然其使用的是 Groovy 这种脚本语言,但是 Gradle 会把 .gradle Groovy 脚本编译成 .class java字节码文件在 JVM 上运行,最终还是 java 这套东西

Android 项目里 settings.gradle、诸多build.gradle 脚本都会编译成对应的 java 类:SettingProject 再去运行,引入的插件也是会编译成对应的 java 对象再执行构建任务

Gradle 内部是一个个编译、打包、处理资源的函数或者插件(函数库),可以说 Gradle 其实就是 API 集合,和我们日常使用的 Okhttp 框架没什么区别,里面都是一个个 API,区别是干的活不同罢了

打开 Gradle 文件目录看看,核心的 bin 文件就一个 gradle 脚本,这个脚本就是 Gradle 核心执行逻辑了,他会启动一个 JVM 实例去加载 lib 中的各种函数去构建项目,这么看 gradle 其实很简单、不难理解

红框里的是 Gradle 自带的内置插件,apply plugin: 'com.android.library'apply plugin: 'com.android.application' 这些都是 gradle 自带的内置插件

19 年 Gradle 提供了中国区 CDN,AS 下载 Gradle 不再慢的和蜗牛一样了

Gradle JVM 进程

Gradle 构建工具在不同场景下会分别使用3个 JVM 进程:

  • client
  • Daemon
  • wrapper

来自Gradle开发团队的Gradle入门教程 --> 官方宣传中这里解释的很清楚,比官方文档都清楚的多

1. client 进程

client 进程是个轻量级进程,每次构建开始都会创建这个进程,构建结束会销毁这个进程。client 进程的任务是查找并和 Daemon 进程通信:

  • Daemon 进程没启动,client 进程会启动一个新的 Daemon 进程
  • Daemon 进程已经存在了,client 进程就给 Daemon 进程传递本次构建相关的参数和任务,然后接收 Daemon 进程发送过来的日志

gradle.properties 里面设置的参数,全局 init.gradle 初始化脚本的任务这些都需要 client 进程传递给 Daemon 进程

2. Daemon 进程

Daemon 进程负责具体的构建任务。我们使用 AS 打包 APK 这依靠的不是 AS 这个 IDEA 开发工具,而是 Gradle 构建工具自己启动的、专门的一个负责构建任务的进程:Daemon。每一个版本的 Gradle 都会对应创建一个 Daemon 进程

Daemon 进程不依赖 AS 而是独立存在,是一个守护进程,构建结束 Daemon 进程也不会销毁,而是会休眠,等待下一次构建,这样做是为了节省系统资源,加快构建速度,Daemon 进程会缓存插件、依赖等资源

必须注意: 每一个 Gradle 版本都会对应一个 Daemon 进程,机器内若是运行过多个版本的 Gradle,那么机器内就会存在多个 Daemon 进程,AS 开发 android 项目,我推荐使用 Gradle 本地文件,不依靠每个 android 项目中 wrapper 管理 gradle 版本,具体后面会说明

从性能上讲:

  • Gradle 在 JVM 上运行,会使用一些支持库,这些库都需要初始化时间,一个长期存在的后台进程有利于节省编译时间
  • daemon 进程会跨构建缓存一些插件、库等缓存数据,这样对加快构建速度的确非常有意义

gradle --status 命令可以查看已启动的 daemon 进程情况:

➜  ~ jps
39554 KotlinCompileDaemon
39509 GradleDaemon
39608
39675 Jps
➜  ~ gradle --status
   PID STATUS   INFO
 39509 IDLE     6.6.1

// INFO 是 gradle 版本号
// Kotlin 语言编写的 Gradle 脚本需要一个新的 daemon 进程出来

若是机器内已经启动了多个 Daemon 进程也不要紧,自己手动杀进程就是了

Daemon 进程在以下情况时会失效,需要启动新的 Daemon 进程,判断 Daemon 进程是否符合要求是上面说的 client 进程的任务:

  • 修改 JVM 配置这回造成启动新的构建进程
  • Gradle 将杀死任何闲置了3小时或更长时间的守护程序
  • 一些环境变量的变化,如语言、keystore、keyStorePassword、keyStoreType 这些变化都会造成旧有的守护进程失效

即便时同一个版本的 Gradle,也会因为 VM 配置不同而存在多个相同 Gradle 版本的 Daemon 进程。比如同时启动好几个项目,项目之间使用的 Gradle 版本相同,但是 VM 使用的不同配置

wrapper 进程

wrapper 进程啥也不干,不参与项目构建,唯一任务就是负责下载管理 Gradle 版本。我们导入 Gradle 项目进来,client 进程发现所需版本的 Gradle 本机没有,那么就会启动 wrapper 进程,根据 gradle.properties 里面的参数去自行 gradle-wrapper.jar 里面的下载程序去下载 Gradle 文件

其他开发工具,我们直接使用 wrapper 来管理 Gradle 的话也是会启动 wrapper 进程的,完事 wrapper 进程会关闭

Gradle 安装

上文书 Gradle 运行在 JVM 之上, 因此需要 JDK1.8 或以上 java 环境

1. 下载 Gradle 版本

从 Gradle 官方上下载,地址:Gradle 官网下载地址,选择 .all 的包下载,我下的是 6.6.1,尽量选择较新的版本

2. 配置项目根目录 build.gradle 脚本文件 Gradle 工具版本号

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
		...
        classpath 'com.android.tools.build:gradle:4.0.1'
		...
    }
}

这里 Gradle 工具的版本号要跟着 AS 的版本号走,AS 是哪个版本,这里就写哪个版本。Gradle 工具中的 API 是给 AS 用的,自然要跟着 AS 的版本变迁

当然这也会对 Gradle 构建工具版本有要求:

  • 第一,大家进来使用比较新的版本号
  • 第二,若是 Gradle 版本太低,编译时会有提示的,告诉你最低 Gradle 构建工具版本是多少

3. 使用本地 Gradle 文件编译项目

Gradle 拥有良好的兼容性,为了在没有 Gradle 环境的机器上也能顺利使用 Gradle 构建项目,AS 新创建的项目默认会在根目录下添加 wrapper 配置

其中 gradle-wrapper.properties 文件中提供了该项目使用的 Gradle 构建工具远程下载地址,这里会对应一个具体的版本号,IDE 开发工具默认会根据这个路径去下载 Gradle 给该项目使用

distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

这样就会产生一个问题:

  • 每个项目单独管理自己的 gradle,很可能会造成机器上同时存在多个版本的 Gradle,进而存在多个版本的 Daemon 进程,这会造成机器资源吃紧,即便关闭 AS 开发工具也没用,只能重启机器才会好转

所以这里我推荐,尤其是给使用 AS 的朋友推荐:在本地创建 Gradle 环境,统一管理 Gradle 构建工具,避免出现多版本同时运行的问题。AS 本身就很吃内存了,每一个 Daemon 构建进程起码都是 512M 内存起步的,多来几个 Daemon 进程,我这 8G 的 MAC 真的搂不住

  1. 打开 AS 中 Gradle 配置:
  • gradle-wrapper.properties -- 使用 wrapper 也就是 AS 来管理 Gradle
  • Specifiled location -- 使用本地文件,也就是我们自己管理 Gradle
  1. 在本地解压 Gradle 压缩包,记住路径,下面配 path 需要

这样配置后,AS 会忽略 gradle-wrapper.properties 文件

4. 配置 path

这里只说 MAC 环境

  1. open -e .bash_profile 打开配置文件
  2. 添加 GRADLE_HOMEPATH
export GRADLE_HOME=/Users/zbzbgo/gradle/gradle-6.6.1
export PATH=${PATH}:/Users/zbzbgo/gradle/gradle-6.6.1/bin

----------------官方写法如下--------------------------------

export GRADLE_HOME=/Users/zbzbgo/gradle/gradle-6.6.1
export PATH=$PATH:$GRADLE_HOME/bin
  1. source .bash_profile 重置配置文件,以便新 path 生效
  2. open -e ~/.zshrc 打开另一个配置
  3. 在最后一行添加 source ~/.bash_profile
  4. source ~/.zshrc 重置配置文件

配置 zshrc 是因为有的机器 bash_profile 配置不管用,添加这个就行了

5. 测试 Gradle 安装是否成功

运行 gradle --version,出现版本号则 Gradle 配置成功

6. 执行一次 Gradle 命令

学习新语言我们都喜欢来一次 hello world,这里我们也来一次

随便创建一个文件夹,在其中创建一个文件,以.gradle结尾,使用 text 编辑器打开,输入:

println("hello world!")

然后 gradle xxx.gradle 执行该文件

OK,成功,大家体验一下,groovy 是种语言,gradle 是种构建工具,可以编译 .gradle 文件

Gradle init 初始化命令

我们平时都是用 AS 开发的,AS 创建 Android 项目时默认就会把 Gradle 相关文件都创建出来。其实 Gradle 和 git 一样,也提供了 init 初始化方法,创建相关文件

运行 init 命令要选择一些参数,其过程如下:

  1. 创建一个文件夹,cd 到该目录,执行 gradle init 命令
  2. 命令行提示选择项目模板
  3. 命令行提示选择开发语言
  4. 命令行提示选择脚本语言
  5. 输入工程名
  6. 输入资源名

了解 Gradle Wrapper 文件

上面虽然说了用 AS 开发我们最好使用本地 Gradle 文件的方式统一配置、管理 Gradle 构建工具,但是 AS Android 项目中的 Wrapper 文件夹的内容还是有必要了解一下的,这可以加深我们对 Gradle 下载、管理的了解

Gradle Wrapper 文件的作用就是可以让你的电脑在不安装配置 Gradle 环境的前提下运行 Gradle 项目,你的机器要是没有配 Gradle 环境,那么你 clone gradle 项目下来,执行 init 命令,会根据 gradle-wrapper.properties 文件中声明的 gradle URL 远程路径去下载 gradle 构建工具,cd 进该项目

  • gradle -v --> linux 平台命令
  • gradlew -v --> window 平台命令

然后就可以在项目目录下运行 gradle 命令了,不过还是推荐大家在机器配置统一的 Gradle 环境

  • gradlew --> linux 平台脚本
  • gradlew.bat --> window 平台脚本
  • gradle-wrapper.jar --> Gradle 下载、管理相关代码
  • gradle-wrapper.properties --> Gradle 下载、管理配置参数

gradle-wrapper.properties 文件中参数详解:

  • distributionUrl --> Gradle 压缩包下载地址
  • zipStoreBase --> 本机存放 Gradle 压缩包主地址
  • zipStorePath --> 本机存放 Gradle 压缩包主路径
    • Gradle 压缩包完整的路径是 zipStoreBase + zipStorePath
  • distributionBase --> 本机 Gradle 压缩包解压后主地址
  • distributionPath --> 本机 Gradle 压缩包解压后路径
    • Gradle 解压完整的路径是 distributionBase + distributionPath
    • distributionBase 的路径是环境 path 中 GRADLE_USER_HOME 的地址
    • Windows:C:/用户/你电脑登录的用户名/.gradle/
    • MAC:~/.gradle/
    • 你 MAC 要是配了 Gradle 环境变量,distributionBase 就是你自己解压缩的 gradle 路径

这几个地址还是要搞清楚的~

Groovy 语法

再次强调 Groovy 是基于 java 扩展的动态语言,直接写 java 代码是没问题的。上面的 hello world 就是这么跑起来。Groovy 没啥难的,大家把他当做一个新的语言来学下就行,Groovy 本身比较简单也不用我们学习的多深入,能基本使用就可以了,语法糖也没多少,最要的闭包明白就大成了。用的很少的专业一些的 API 大家 baidu 一下就出来了

1. 不用写 ; 号

一看这个就知道也是往高阶语言上靠 <( ̄3 ̄)> 表!,比较新的语言都这样,基本都是大同小异

int name = 10
int age = "AAA"

2. 支持动态类型,但是必须用 def 前缀

def name = 10
def age = "AAA"

name = "111"
println(name)

3. 没有基本数据类型了,全是包装类型

Groovy 基于 java,所以 java 的基本数据类型都支持,但是 Groovy 中这些基本数据类型使用的都是包装类型:Integer、Boolean 等

int index = 0
println("index == "+index.class)

4. 方法变化

  • 使用 def 修饰,方法可以不用指定返回类型、参数类型,直接返回最后一行。
  • 方法调用可以不写 (),最好还是加上()的好,要不真不好阅读
  • 实际上不管有没有返回值,Groovy 中返回的都是 Object 类型
def to(x, y){
    x+y
}

def name = 10
def age = 12

name = to name,age 

println(name)

5. 字符串变化

Groovy 支持单、双、三引号来表示字符串${} 引用变量值,三引号是带输出格式的

def world = 'world'
def str1 = 'hello ${world}'
def str2 = "hello ${world}"
def str3 = 
	'''hello
	&{world}'''

6. 不用写 get/set

Groovy ⾃动对成员属性创建 getter / setter,按照下面这个用法调用

class Person{
    def name
    def age
}

Person person = new Person()
person.name = "AA"
person.setAge(123)
person.@age = 128

println(person.name + " / " + person.age)

7. Class 类型,可以省略 .class

8. 没有 ===

Groovy 中 == 就是 equals,没有 === 了。而是用 .is() 代替,比较是不是同一个对象

class Person {
    def name
    def age
}

Person person1 = new Person()
Person person2 = new Person()
person1.name = "AA"
person2.name = "BB"

println("person1.name == person2.name" + (person1.name == person2.name))
println("person1 is person2" + person1.is(person2))

9. 支持 xx次方运算符

2 ** 3 == 8

10. 三木运算符

def result = name ?: ""

11. 支持非空判断

println order?.customer?.address

12. Switch 变化

def num = 5.21

switch (num) {
   case [5.21, 4, "list"]:
       return "ok"
       break
   default:
       break
}

13. 集合类型

Groovy 支持三种集合类型:

  • List --> 链表,对应 Java 中的 List 接口,一般用 ArrayList 作为真正的实现类
  • Map --> 哈希表,对应 Java 中的 LinkedHashMap
  • Range --> 范围,它其实是 List 的一种拓展
// --> list 
def data = [666,123,"AA"]
data[0] = "BB"
data[100] = 33
println("size --> " + data.size()) // 101个元素

----------------------我是分割线------------------------

// --> map
def key = "888"
def data = ["key1": "value", "key2": 111, (key): 888] // 使用 () key 使用动态值

data.key1
data.["key1"]
data.key2 = "new"

def name2 = "name"
def age2 = 578
data.put(name2, age2)

println("size --> " + data.size()) // 4
println("map --> " + data) // [key1:value, key2:new, 888:888, name:578]
println("key--> " + data.get(key)) // key--> 888

----------------------我是分割线------------------------

// --> range
def data = 1..10
data.getFrom()
data.to()

println("size --> " + data.size())
println("range --> " + data) // range --> 1..10

14. 闭包

这个是绝对重点,大家到这里认真学呀 (○` 3′○) 学会这个后面就容易理解了,后面都是闭包的应用

闭包(Closure) 是 Groovy 最重要的语法糖了,我们把闭包当做高阶语法中的对象式函数就行了

官方定义:Groovy中的闭包是一个开放,匿名的代码块,可以接受参数,返回值并分配给变量

// 标准写法,method1 就是一个闭包 (>▽<)
def method1 = { name,age ->
    name + age
}

// 调用方式
method1.call(123,888)
method1(123,888)

// 默认有一个 it 表示单个参数
def method3 = { "Hello World!$it" }

// 强制不带参数
def method2 = {  ->
    name + age
}

// 作为方法参数使用
def to(x, y,Closure closure) {
    x + y + closure(111)
}

后面大家会经常见到闭包的应用,比如这个自定义 task 任务

task speak{
    doLast {
        println("AAA")
    }
}

举这个例子是为了说明,实际闭包都是嵌套很多层使用

  • speak 是个方法,接收一个闭包作为参数,整个外层 {...} 都是一个闭包
  • 外层闭包内 doLast 方法又接收一个闭包作为参数,内层 {...} 又是一个闭包

通过这个例子大家搞清楚这个嵌套关系就好学了,实际就是一层套一层,有的插件写的我都看吐了

Closure 这东西方便是方便,但是 Closure 里面传什么类型的参数,有几个参数
这些可没有自动提示,想知道详细就得查文档了,这点简直不能忍,我想说官方就不能做过自动提示出来嘛~

15. delegate 闭包委托

这是 Gradle 闭包常见方式:

class Person {
    String name
    int age
}    
 
def cc = {
	name = "hanmeimei"
	age = 26
} 

Person person = new Person()
cc.call()

cc 是闭包,cc.call() 调用闭包,cc.call(persen) 这是给闭包传入参数,我们换个写法:

  • cc.delegate = person 就相当于 cc.call(persen)

这个写法就是:委托 了,没什么难理解的,我这里就是按照最简单的解释来

至于为什么要有委托这种东西,必然是有需求的。我们写的都是 .gradle 脚本,这些脚本实际要编译成 .class 才能运行。也就是说代码实际上动态根据我们配置生成的,传参数也是动态的,委托这一特性就是为了动态生成代码、传参准备的

后面很多 Gradle 中的插件,其 {...} 里面写配置其实走的都是委托这个思路

举个常见的例子,Android {...} 代码块大家熟悉不熟悉,这个就是闭包嵌套,闭包里还有闭包 -->

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
}

16. 插件中使用 delegate + 闭包思路

其实思路很简单,每一个 {...} 闭包都要有一个对应的数据 Bean 存储数据,在合适的时机 .delegate 即可

  1. 闭包定义
def android = {
	compileSdkVersion 25
	buildToolsVersion "25.0.2"
    
    // 这个对应相应的方法
	defaultConfig {
		minSdkVersion 15
		targetSdkVersion 25
		versionCode 1
		versionName "1.0"
	}
}
  1. 准备数据 Bean
class Android {
    int mCompileSdkVersion
    String mBuildToolsVersion
    BefaultConfig mBefaultConfig

    Android() {
        this.mBefaultConfig = new BefaultConfig()
    }

    void defaultConfig(Closure closure) {
        closure.setDelegate(mProductFlavor)
        closure.setResolveStrategy(Closure.DELEGATE_FIRST)
        closure.call()
    }
}

class BefaultConfig {
    int mVersionCode
    String mVersionName
    int mMinSdkVersion
    int mTargetSdkVersion
}
  1. .delegate 绑定数据
Android bean = new Android()
android.delegate = bean
android.call()

17. 一样需要 import 导入包、文件

Groovy 常用 API

1. xml 解析

<response version-api="2.0">
       <value>
           <books>
               <book available="20" id="1">
                   <title>Don Xijote</title>
                   <author id="1">Manuel De Cervantes</author>
               </book>
               <book available="14" id="2">
                   <title>Catcher in the Rye</title>
                  <author id="2">JD Salinger</author>
              </book>
              <book available="13" id="3">
                  <title>Alice in Wonderland</title>
                  <author id="3">Lewis Carroll</author>
              </book>
              <book available="5" id="4">
                  <title>Don Xijote</title>
                  <author id="4">Manuel De Cervantes</author>
              </book>
           </books>
      </value>
   </response>

1)xml 解析

def xparser = new XmlSlurper()
def targetFile = new File("test.xml")
GPathResult gpathResult = xparser.parse(targetFile)
 
def book4 = gpathResult.value.books.book[3]
def author = book4.author
author.text()
author.@id
author['@id'] 
author.@id.toInteger() 

遍历 XML 数据

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

def name = response.value.books.children().findAll { node ->
    node.name() == 'book' && node.@id == '2'
}.collect { node ->
    return node.title.text()
}

2)获取 AndroidManifest 配置文件参数

Gradle 解析 xml 的意义也就是 AndroidManifest 配置文件了,不难

def androidManifest = new XmlSlurper().parse("./app/src/main/AndroidManifest.xml")
def app = androidManifest.application
println("value -->" + app.@"android:supportsRtl")

3)生成 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
}

2. 解析 json

def reponse = getNetworkData('http://yuexibo.top/yxbApp/course_detail.json')
    
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)
}

3. IO

Gradle 中操作文件是比不可少的工作了,Grovvy IO API 大家一定要清楚

1) 获取文件地址

o(^@^)o 大家写插件、task 时获取项目地址这个点总是要会的,下面的代码不光可以使用 rootProject,每个脚本中的 Project 对象也可以使用的,path 和 absolutePath 都行

println(rootProject.projectDir.path/absolutePath)
println(rootProject.rootDir.path)
println(rootProject.buildDir.path)

/Users/zbzbgo/worksaplce/flutter_app4/MyApplication22
/Users/zbzbgo/worksaplce/flutter_app4/MyApplication22
/Users/zbzbgo/worksaplce/flutter_app4/MyApplication22/build

2) 文件定位

思路就是把指定 path 加入当年项目的根路径中,再构建 File 对象使用

//文件定位
this.getContent("config.gradle", "build.gradle")

// 不同与 new file 的需要传入 绝对路径 的方式
// file 从相对于当前的 project 工程开始查找
def mFiles = files(path1, path2)
println mFiles[0].text + mFiles[1].text

或者这样写也是可以的,会在相应的子项目目录下生成文件,这种不用写 this.getContent(XXX)

 def file = project.file(fileName)

3)eachLine 一次读一行

File fromFile = new File(rootProject.projectDir.path + "/build.gradle")

fromFile.eachLine { String line ->
    println("line -->" + line)
}

line 的 API 还有好几个

4)获取输入流、输出流

File fromFile = new File(rootProject.projectDir.path + "/build.gradle")

fromFile.withInputStream { InputStream ins ->
	...... 这里系统会自动关闭流,不用我们自己关
}

def ins = fromFile.newInputStream()
ins.close()

5)<< 复制 文件

Grovvy 的语法糖写起来的简便

File fromFile = new File(rootProject.projectDir.path + "/build.gradle")
File toFile = new File(rootProject.projectDir.path + "/build2.gradle")

fromFile.withInputStream { InputStream ins ->
    toFile.withOutputStream { OutputStream out ->
        out << ins
    }
}

6)<< copy API 复制文件

copy {
    from file(rootProject.rootDir.path+"/build.gradle") // 源文件
    into rootProject.rootDir.path // 复制目标地址,这里不用带文件名

    exclude()
    rename { "build.gradle2" } // 复制后重命名,不写的话默认还是目标文件名
}

7)reader/writer

File fromFile = new File(rootProject.projectDir.path + "/build.gradle")
File toFile = new File(rootProject.projectDir.path + "/build2.gradle")

fromFile.withReader { reader ->
    def lines = reader.lines()
    toFile.withWriter { writer ->
        lines.each { line ->
            writer.writeLine(line)
        }
    }
}

8)Object

File fromFile = new File(rootProject.projectDir.path + "/build.gradle")
File toFile = new File(rootProject.projectDir.path + "/build2.gradle")

fromFile.withObjectInputStream { input ->
    toFile.withObjectOutputStream { out ->
        out.writeObject( input.readObject() )
    }
}

9)获取文件字节数组

def file = new File(baseDir, 'test.txt')
byte[] contents = file.bytes

10)遍历文件树

def dir = new File("/")
//eachFile()方法返回该目录下的所有文件和子目录,不递归
dir.eachFile { file ->
    println file.name
}
dir.eachFileMatch(~/.*\.txt/) {file ->
    println file.name
}

-------------------分割线-------------------

def dir = new File("/")
//dir.eachFileRecurse()方法会递归显示该目录下所有的文件和目录
dir.eachFileRecurse { file ->
    println file.name
}
dir.eachFileRecurse(FileType.FILES) { file ->
    println file.name
}

-------------------分割线-------------------

dir.traverse { file ->
    //如果当前文件是一个目录且名字是bin,则停止遍历
    if (file.directory && file.name=='bin') {
        FileVisitResult.TERMINATE
    //否则打印文件名字并继续
    } else {
        println file.name
        FileVisitResult.CONTINUE
   }
}

11)序列化

boolean b = true
String message = 'Hello from Groovy'
def file = new File(baseDir, 'test.txt')
// 序列化数据到文件
file.withDataOutputStream { out ->
    out.writeBoolean(b)
    out.writeUTF(message)
}
// ...
// 从文件读取数据并反序列化
file.withDataInputStream { input ->
    assert input.readBoolean() == b
    assert input.readUTF() == message
}

12)程序中执行shell命令

def process = "ls -l".execute()
println(process)