1. Groovy介绍
Groovy
是一种基于Java
平台的动态编程语言,与Java是完全兼容,除此之外有很多的语法糖来方便我们开发。Groovy
代码能够直接运行在Java
虚拟机(JVM
)上,也可以被编译成Java
字节码文件。
以下是Groovy
的一些特性:
- 简洁:
Groovy
的语法比Java
更加简洁,可以用更少的代码完成同样的功能。 - 动态语言:
Groovy
是一种动态语言,支持动态类型和动态方法调用等特性,这意味着你可以在编写代码时不必指定变量的类型。 - 完全兼容Java:
Groovy
可以无缝使用Java
的所有类库,也可以直接在Groovy
代码中使用Java
代码。
2. Groovy运行机制
Groovy是一种基于Java虚拟机(JVM)的面向对象编程语言,其运行机制主要包括以下几个方面:
-
解析阶段:Groovy代码首先会被Groovy编译器解析为一个抽象语法树(AST)。AST是源代码的图形化表示,它以树状的形式描绘出源代码的结构,使编译器能够更好地理解和处理代码。
-
编译阶段:在AST生成后,Groovy编译器会将它转换为Java字节码。这是因为Groovy是一种运行在JVM上的语言,必须将源代码转换为Java字节码,才能被JVM执行。
-
运行阶段:生成的Java字节码最后会被JVM加载并执行。在这个过程中,如果Groovy代码中包含了动态类型,那么Groovy会在运行时进行类型检查和方法调用的解析。
-
动态语言的特性:作为一种动态语言,Groovy的一大特性就是它的动态性。它支持动态方法调用,即在运行时解析方法调用,而不是在编译时。这使得Groovy在处理一些特定问题时更加灵活,例如处理JSON和XML等数据格式。
- 可以想象成纯反射的调用,加上元编程的特性,使Groovy可以在运行时解析方法调用
- 除非加上
@CompileStatic
会按照Java
的方式静态编译,否则都是动态编译的
-
元编程:Groovy还支持元编程,它允许开发者在运行时修改类的结构或行为。这使得Groovy可以实现一些强大的功能,例如创建DSL(领域特定语言)、添加或修改类的方法等。
-
脚本执行:Groovy还可以作为脚本语言使用,即不需要进行编译,直接运行Groovy代码。在脚本模式下,Groovy会使用一个特殊的类加载器来解析和执行代码。
Groovy
的运行机制深度整合了编译型语言和解释型语言的优势,既拥有编译型语言的性能优势,又保留了解释型语言的灵活性和便利性。
3. Groovy DSL
本身Groovy DSL
的目标就是成为一个通用的DSL
语言,所以在Groovy
中,方法调用可以不写括号
比如 :
turn(left).then(right)
可以简写为turn left then right
take(2.pills).of(chloroquinine).after(6.hours)
可以简写为take 2.pills of chloroquinine after 6.hours
paint(wall).with(red, green).and(yellow)
可以简写为paint wall with red, green and yellow
check(that: margarita).tastes(good)
可以简写为check that: margarita tastes good
given({}).when({}).then({})
可以简写为given { } when { } then { }
具体详见 Groovy DSL
3.1 Groovy DSL 示例一
比如我们在Android项目中经常可以看到这样一行代码
apply plugin: MyPlugin
这行代码等价于
apply([plugin : MyPlugin])
当方法的参数是一个map的时候,可以将方括号[]
去掉
apply(plugin: MyPlugin)
当不引起歧义的时候,可以把圆括号去掉,从而得到了我们经常看到的这行代码
apply plugin : MyPlugin
3.2 Groovy DSL 示例二
在新版的Gradle
中,默认情况下,已经不使用apply plugin
了,而是使用plugins{}
来引入插件了。
plugins {id 'com.android.application' version '8.1.3' apply false
}
本质是有一个plugins
的方法,调用了一个id 'com.android.application' version '8.1.3' apply false
的闭包
plugins({id('com.android.application').version('8.1.3').apply(false)
})
4. 闭包
4.1 最简单的闭包
先来看一个最简单的闭包
//声明一个闭包
def closure = {println "hello world!"//return 1
}//可以直接调用它,因为它就是一个函数
closure()
//等同于上面这行
closure.call()
4.2 带参数的闭包
带参数的闭包只需要传入需要的参数,声明闭包的时候,指明这个参数(比如param1
)就好了
def closure = { param1 ->println("running start...:" + param1)println("running end...")
}//进行调用,并传参
closure("heiko")
//等同于上面这行
closure.call("qwerty")
打印的日志
running start...:heiko
running end...
running start...:qwerty
running end...
4.3 闭包在实际开发中的应用
4.3.1 无参数
一般在实际开发中,闭包是作为传参传入的,通过closure.call()
进行回调
def closure(Closure closure){println("running start...")//closure() 这种调用方式也可以closure.call()println("running end...")
}
然后在调用方法的时候,就可以很方便的通过闭包{}
进行调用了
closure {println("running........")
}
打印的日志如下
running start...
running........
running end...
4.3.2 有参数的情况
闭包有参数的情况,那么通过closure.call()
传入了两个参数10
和15
def calc(Closure closure) {//closure(10,15) 这种调用方式也可以def result = closure.call(10, 15)println("result:" + result)
}
那么在调用方法的时候,闭包可以声明v1,v2
这两个参数,然后就可以直接使用了
calc { v1, v2 ->println("v1:" + v1 + " v2:" + v2)v1 + v2
}
打印的日志如下
v1:10 v2:15
result:25
4.3.3 调用闭包的时候传参
调用方法的时候,我们可以传参,然后还可以将这个参数,回调给闭包closure.call(num1, num2)
def calc2(num1, num2, Closure closure) {//closure(num1,num2) 这种调用方式也可以def result = closure.call(num1, num2)println("result:" + result)
}
调用方法的时候,就是在()
里多传入两个参数就好了
calc2(6, 7) { v1, v2 ->println("v1:" + v1 + " v2:" + v2)v1 + v2
}
打印日志如下
v1:6 v2:7
result:13
4.4 闭包{}是怎么出现的
4.4.1 最初的闭包
def calc3(num1, num2, Closure closure) {//closure(num1,num2) 这种调用方式也可以def result = closure.call(num1, num2)println("result:" + result)
}
4.4.2 调用方法
闭包作为方法的最后一个参数的时候,可以写在方法外面
calc3(1, 2) { v1, v2 ->println("v1:" + v1 + " v2:" + v2)v1 + v2
}
4.4.3 方法没有 其他参数的情况
如果方法没有其他参数的话,调用的时候是()
,闭包{}
在()
外面
def calc3(Closure closure) {def result = closure.call(num1, num2)println("result:" + result)
}calc3() { v1, v2 ->println("v1:" + v1 + " v2:" + v2)v1 + v2
}
4.4.4 省略大括号
方法调用的时候,在不引起歧义的情况下,大括号()
也可以省略,这样就成为我们最终看到的闭包的样子了。
def calc3(Closure closure) {def result = closure.call(1, 2)println("result:" + result)
}calc3 { v1, v2 ->println("v1:" + v1 + " v2:" + v2)v1 + v2
}
5. 写一个自己的android闭包
在Android
项目,我们平时最常见的就是android
这个闭包了,那么我们能不能自己写一个android
闭包呢
android {namespace 'com.heiko.mytest'compileSdk 34defaultConfig {applicationId "com.heiko.mytest"minSdk 24targetSdk 34}
}
5.1 声明MyAndroidBean类
声明MyAndroidBean
类,用来定义需要传递的参数
class MyAndroidBean {public String namespacepublic Integer compileSdk
}
5.2 声明函数 : myandroid
声明函数myandroid
,传参为一个闭包closure
,然后调用project.configure(myAndroidBean, closure)
使闭包转化为MyAndroidBean
,然后就可以调用myAndroidBean
的属性了。
def myandroid(Closure closure) {MyAndroidBean myAndroidBean = new MyAndroidBean()project.configure(myAndroidBean, closure)println(myAndroidBean.namespace)println(myAndroidBean.compileSdk)
}
5.3 调用myandroid
接着写上这些代码,来调用myandroid
,并配置了namespace
和compileSdk
的值
myandroid {namespace = "com.heiko.mm"compileSdk = 31
}
5.4 Sync下项目
然后我们Sync
下项目,可以发现打印出了如下日志
myandroid {namespace = "com.heiko.mm"compileSdk = 31
}
5.5 声明MyDefaultConfig类
声明MyDefaultConfig
类,用来定义mydefaultConfig
闭包内的参数
class MyDefaultConfig {public String applicationIdpublic int minSdkpublic int targetSdk
}
5.6 声明函数 : mydefaultConfig
声明函数mydefaultConfig
,传参为一个闭包closure
,然后调用closure.delegate = config
。closure.delegate = defaultConfig
这行代码的作用是将闭包的委托对象设置为defaultConfig
实例。这意味着在闭包内部,当你尝试访问或设置一个属性(如applicationId、minSdk或targetSdk
)时,实际上是在defaultConfig
对象上执行这些操作。
class MyAndroidBean {public String namespacepublic Integer compileSdkpublic MyDefaultConfig defaultConfigdef mydefaultConfig(Closure closure) {MyDefaultConfig config = new MyDefaultConfig()closure.delegate = configclosure.call()defaultConfig = config}
}def myandroid(Closure closure) { // 添加project参数MyAndroidBean myAndroidBean = new MyAndroidBean()closure.delegate = myAndroidBeanclosure.call()println("namespace:" + myAndroidBean.namespace)println "compileSdk:" + (myAndroidBean.compileSdk)println "applicationId:" + (myAndroidBean.defaultConfig.applicationId)println "minSdk:" + (myAndroidBean.defaultConfig.minSdk)println "targetSdk:" + (myAndroidBean.defaultConfig.targetSdk)
}
在Groovy中,闭包(Closure)是一种可以引用和使用其周围环境中的变量的代码块。闭包有三种重要的属性:delegate、owner和this。
delegate属性是执行闭包时用于解析方法调用和属性引用的对象。也就是说,当你在闭包内部调用一个方法或引用一个属性,Groovy会首先在delegate对象上查找这个方法或属性。如果在delegate对象上找不到,它将在owner和this对象上查找。
默认情况下,delegate对象是owner对象,但你可以自由地改变它。当你设置了一个新的delegate,你可以在闭包中引用和操作这个新对象的方法和属性,就像它们是在闭包内部定义的一样,这个特性使得你可以在闭包中使用DSL样式的代码。
5.7 调用mydefualtConfig
这个时候就可以去调用mydefaultConfig
方法了,并可以对applicationId、minSdk、targetSdk
属性进行配置。
myandroid {namespace = "com.heiko.mm"compileSdk = 31mydefaultConfig {applicationId = "com.heiko.mm"minSdk = 21targetSdk = 31}
}
最后Sync
下项目,可以看到打印日志如下
namespace:com.heiko.mm
compileSdk:31
applicationId:com.heiko.mm
minSdk:21
targetSdk:31
6. Gradle系列文章
Android Gradle 开发与应用 (一) : Gradle基础-氦客-CSDN博客
Android Gradle开发与应用 (二) : Groovy基础语法-氦客-CSDN博客
Android Gradle开发与应用 (三) : Groovy语法概念与闭包-氦客-CSDN博客
Android Gradle开发与应用 (四) : Gradle构建与生命周期-氦客-CSDN博客
基于Gradle 8.2,创建Gradle插件-氦客-CSDN博客
Android Gradle插件开发_实现自动复制文件插件-氦客-CSDN博客