Android Gradle开发与应用 (三) : Groovy语法概念与闭包

1. Groovy介绍

Groovy是一种基于Java平台的动态编程语言,与Java是完全兼容,除此之外有很多的语法糖来方便我们开发。Groovy代码能够直接运行在Java虚拟机(JVM)上,也可以被编译成Java字节码文件。

以下是Groovy的一些特性:

  • 简洁Groovy的语法比Java更加简洁,可以用更少的代码完成同样的功能。
  • 动态语言Groovy是一种动态语言,支持动态类型和动态方法调用等特性,这意味着你可以在编写代码时不必指定变量的类型。
  • 完全兼容JavaGroovy可以无缝使用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()传入了两个参数1015

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,并配置了namespacecompileSdk的值

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 = configclosure.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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/505934.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# aes加密解密byte数组

using System.Security.Cryptography; using System.Text;namespace AESStu01;public class AesHelper {// AES加密密钥和向量(需要保密) private static readonly string Key "";//16长度字符串数字混合private static readonly string IV …

Unity曲柄滑块四杆机构运动计算

一、运动效果 二、机构的介绍 曲柄长度:a,线段AB长度 连杆长度:b,线段BC长度 偏心距离:e,滑块轨迹与曲柄中心点A的垂直距离 三、已知点A点B和e的值,计算C点的位置 1、计算s的值 var h math.…

殿堂级Flink源码极精课程预售

一、为什么我们要读源码? 1、让个人技术快速成长: 优秀的开源框架,底层的源码设计思想也非常优秀,同时还有含有大量的设计模式和并发编程技术,优秀的解决方案,熟读源码对猿们技术提升有很大帮助 2、新技术学习能力: Java开源码框架的源码熟读后,若出现…

2024最新算法:鹦鹉优化算法(Parrot optimizer,PO)求解23个基准函数(提供MATLAB代码)

一、鹦鹉优化算法 鹦鹉优化算法(Parrot optimizer,PO)由Junbo Lian等人于2024年提出的一种高效的元启发式算法,该算法从驯养的鹦鹉中观察到的觅食、停留、交流和对陌生人行为的恐惧中汲取灵感。这些行为被封装在四个不同的公式中…

学习和工作的投入产出比(节选)

人工智能统领全文 推荐包含关于投入、产出、过剩、市场关注、案例、结果和避雷等主题的信息: 投入与产出: 投入和产出都有直接和间接两类常见形式。常见的四种组合是:直接投入、直接产出、间接投入、间接产出。 过剩: 过剩是一个重…

BUUCTF---[极客大挑战 2019]LoveSQL1

1.题目描述 2.一般遇到登录,用户名会尝试admin,密码会尝试万能密码" or 11#或者 or 11#。这里尝试了第一种,但是不对。 3.接着尝试第二种 or 11#。提示登录成功了说明这里是单引号注入,并给了一串字符 4.sql注入题一般会设计…

13-微服务初探-自研微服务框架

微服务初探 1. 架构变迁之路 1.1 单体架构 互联网早期,一般的网站应用流量较小,只需要一个应用,将所有的功能代码都部署在一起就可以,这样可以减少开发,部署和维护的成本。 比如说一个电商系统,里面包含…

Ubuntu18.04安装RTX2060显卡驱动+CUDA+cuDNN

Ubuntu18.04安装RTX2060显卡驱动CUDAcuDNN 1 安装RTX2060显卡驱动1.1 查看当前显卡是否被识别1.2 安装驱动依赖1.3 安装桌面显示管理器1.4 下载显卡驱动1.5 禁用nouveau1.6 安装驱动1.7 查看驱动安装情况 2 安装CUDA2.1 查看当前显卡支持的CUDA版本2.2 下载CUDA Toolkit2.3 安装…

2. this 指向问题

this 指向问题 前言 当一个函数调用时,会创建一个执行上下文,这个上下文包括函数调用的一些信息(调用栈,传入参数,调用方式),this就指向这个执行上下文。 this 不是静态的,也并不是在编写的时候绑定的&am…

百度搜索引擎SEO优化方法

随着互联网的不断发展,搜索引擎已经成为人们获取信息、产品和服务的主要途径之一。而在中国,百度作为最大的搜索引擎,其影响力不可忽视。了解并掌握百度SEO关键词优化方法,对于提升网站在搜索引擎中的排名至关重要。 关键词选择&a…

MyBatis 学习(七)之 缓存

目录 1 MyBatis 缓存介绍 2 一级缓存 3 二级缓存 3.1 二级缓存介绍 3.2 二级缓存配置 3.3 二级缓存测试 4 参考文档 1 MyBatis 缓存介绍 MyBatis 缓存是 MyBatis 中的一个重要特性,用于提高数据库查询的性能。MyBatis 提供了一级缓存和二级缓存两种类型的缓存…

《汇编语言》第3版 (王爽)检测点3.2解析

第三章 3.2解析 (1).补全下面的程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H-2000FH。逆序复制的含义如图3.17所示(图中内存里的数据均为假设)。 mov ax,1000H ;将1000H放入AX寄存器中 mov …