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

news2024/12/25 2:35:51

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 34

    defaultConfig {
        applicationId "com.heiko.mytest"
        minSdk 24
        targetSdk 34
    }
}

5.1 声明MyAndroidBean类

声明MyAndroidBean类,用来定义需要传递的参数

class MyAndroidBean {
    public String namespace
    public 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 applicationId
    public int minSdk
    public int targetSdk
}

5.6 声明函数 : mydefaultConfig

声明函数mydefaultConfig,传参为一个闭包closure,然后调用closure.delegate = configclosure.delegate = defaultConfig这行代码的作用是将闭包的委托对象设置为defaultConfig实例。这意味着在闭包内部,当你尝试访问或设置一个属性(如applicationId、minSdk或targetSdk)时,实际上是在defaultConfig对象上执行这些操作。

class MyAndroidBean {
    public String namespace
    public Integer compileSdk
    public MyDefaultConfig defaultConfig

    def mydefaultConfig(Closure closure) {
        MyDefaultConfig config = new MyDefaultConfig()
        closure.delegate = config
        closure.call()
        defaultConfig = config
    }
}

def myandroid(Closure closure) { // 添加project参数
    MyAndroidBean myAndroidBean = new MyAndroidBean()
    closure.delegate = myAndroidBean
    closure.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 = 31

    mydefaultConfig {
        applicationId = "com.heiko.mm"
        minSdk = 21
        targetSdk = 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.coloradmin.cn/o/1484793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驾辰龙跨Llama持Wasm,玩转Yi模型迎新春

今年新年很特别,AI工具添光彩。今天就来感受下最新的AI神器天选组合“WasmEdgeYi-34B”,只要短短三步,为这个甲辰龙年带来一份九紫离火运的科技感。 环境准备 这次用的算力是OpenBayes提供的英伟达RTX_4090*1、24GB显存、20核CPU、80GB内存…

【论文笔记】Mamba:挑战Transformer地位的新架构

Mamba Mamba: Linear-Time Sequence Modeling with Selective State Spaces Mamba Mamba摘要背景存在的问题本文的做法实验结果 文章内容Transformer的缺点Structured state space sequence models (SSMs)介绍本文的工作模型介绍State Space ModelsSelective State Space Mod…

docker三剑客compose+machine+swarm小结

背景 在容器领域,不少公司会使用docker三剑客composemachineswarm进行容器编排和部署,本文就简单记录下这几个工具的用法 三剑客composemachineswarm compose compose主要是用于容器编排,我们部署容器时,容器之间会有依赖&…

git的安装、使用

文章目录 安装gitgit学习网站git初始配置具体配置信息 新建版本库(仓库)git的工作区域和文件状态工作区域文件状态git文件提交的基础指令 git基础指令1. 版本提交2. 分支创建3. 分支切换4. 分支合并(1) git merge(2) git rebase 5. 在git的提交树上移动(…

vue+springboot项目部署服务器

项目仓库:vuespringboot-demo: vuespringboot增删改查的demo (gitee.com) ①vue中修改配置 在public文件夹下新建config.json文件: {"serverUrl": "http://localhost:9090"//这里localhost在打包后记得修改为服务器公网ip } 然后…

三天学会阿里分布式事务框架Seata-seata事务日志mysql持久化配置

锋哥原创的分布式事务框架Seata视频教程: 实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)_哔哩哔哩_bilibili实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)共计10条视频&…

Java中的Collection

Collection Collection 集合概述和使用 Collection集合概述 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素 JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现 创建Collection集合的对象 多态的方式 具体的实现类ArrayList C…

Pycharm的下载安装与汉化

一.下载安装包 1.接下来按照步骤来就行 2.然后就能在桌面上找到打开了 3.先建立一个文件夹 二.Pycharm的汉化

javaweb day9 day10

昨天序号标错了 vue的组件库Elent 快速入门 写法 常见组件 复制粘贴 打包部署

修改一个教材上的网站源码使它能在www服务器子目录上正常运行

修改一个教材上的网站源码,使它能在www服务器子目录上正常运行。 该网站源码是教材《PHPMySQL网站开发项目式教程》上带的网站源码。该源码是用 php html 写的。该源码包含对mysql数据库进行操作的php代码。以前该网站源码只能在www服务器的根目录上正常运行&…

一文认识蓝牙(验证基于Aduino IDE的ESP32)

1、简介 蓝牙技术是一种无线通信的方式,利用特定频率的波段(2.4GHz-2.485GHz左右),进行电磁波传输,总共有83.5MHz的带宽资源。 1.1、背景 蓝牙(Bluetooth)一词取自于十世纪丹麦国王哈拉尔Haral…

[技巧]Arcgis之图斑四至点批量计算

前言 上一篇介绍了arcgis之图斑四至范围计算,这里介绍的图斑四至点的计算及获取,两者之间还是有差异的。 [技巧]Arcgis之图斑四至范围计算 这里说的四至点指的是图斑最东、最西、最南、最北的四个地理位置点坐标,如下图: 四至点…

SCP命令行向服务器端上传文件或下载文件

环境要求 使用scp(Secure Copy Protocol)命令在本地和远程系统之间安全地复制文件和目录,需要满足以下环境要求: SSH服务:scp依赖于SSH(Secure Shell)协议来安全地传输文件。因此,…

第四十八回 解珍解宝双越狱 孙立孙新大劫牢-Python模块和包概念与使用

吴用对宋江说,有个人,他是石勇的关系,与祝家庄的峦廷玉关系好,还是杨林、邓飞的老相识,他有一计.... 原来在宋江攻打祝家庄的时间段,山东海边登州也发生了一件事。登州山下有一家猎户,弟兄两个…

下载github项目到pycharm

一、下载git 1.下载git链接 https://git-scm.com/ 2.一路点击next,最后finish 二、使用git 1.安装成功后在开始菜单栏会找到如下内容,其中常用的是Git Bash 2.点击Git Bash 3.这里就可以克隆github上的代码了 点击复制,在命令行输入…

《猛兽派对》好玩吗值得买吗?苹果电脑也能装《猛兽派对》吗?猛兽派对好友通行证 动物派对 猛兽对战游戏

目录 一、《猛兽派对》好玩吗? 游戏玩法: 物理引擎: 关卡设计: 游戏特色: 评价: 荣誉: 二、苹果电脑也能装《猛兽派对》吗? 第1步:下载并安装CrossOver这款软件…

备战蓝桥杯---线段树基础2

今天我们把线段树的另一个模板看一下: 在这里,我们注意到乘的操作,因此我们用两个懒标记来分别表示加和乘,这时我们面临了一个问题,就是当我们把标记往下传时,它的儿子怎么知道是先乘还是先加? …

存储型xss案例

一、环境 DWVA网上自己找 二、开始闯关 先随便写看看 看看源码,数据是被插入到数据库里面了,魔术开关关闭了 数据最后插入显示到index.php 看一下我们目前的cookie值是 看了源码之后也没进行过滤那么我们试着直接插,看是否过滤 之后用户每…

详解如何保证消息队列不丢失消息(以kafka为例)

✨✨祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 目录 一、引言 二. 持久化存储 2.1持久化存储原理: 2.2使用示例: …

递归法解决多重背包问题

多重背包问题同样是01背包问题的变种,同样可以通过修改01背包部分代码来求解。 方法一:修改递归函数,根据题目新的限制条件——使用次数扩充状态参数个数,将同一个物品选用的次数同样作为参数传递到栈空间中,同时也对…