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

news/2024/5/20 4:12:31 标签: android, 开发语言, Groovy, Gradle, 闭包, 概念, 语法

Groovy_0">1. Groovy介绍

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

以下是Groovy的一些特性:

  • 简洁Groovy语法Java更加简洁,可以用更少的代码完成同样的功能。
  • 动态语言Groovy是一种动态语言,支持动态类型和动态方法调用等特性,这意味着你可以在编写代码时不必指定变量的类型。
  • 完全兼容JavaGroovy可以无缝使用Java的所有类库,也可以直接在Groovy代码中使用Java代码。

在这里插入图片描述

Groovy_10">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的运行机制深度整合了编译型语言和解释型语言的优势,既拥有编译型语言的性能优势,又保留了解释型语言的灵活性和便利性。

Groovy_DSL_29">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

Groovy_DSL__41">3.1 Groovy DSL 示例一

比如我们在Android项目中经常可以看到这样一行代码

apply plugin: MyPlugin

这行代码等价于

apply([plugin : MyPlugin])

当方法的参数是一个map的时候,可以将方括号[]去掉

apply(plugin: MyPlugin)

当不引起歧义的时候,可以把圆括号去掉,从而得到了我们经常看到的这行代码

apply plugin : MyPlugin

Groovy_DSL__59">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
}

android_241">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
}

android_264">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)
}

android_274">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

Gradle_355">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.niftyadmin.cn/n/5402363.html

相关文章

贪心算法(基础题)

455. 分发饼干 题目 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j&#xf…

SQL语言的五大分类 (DQL、DDL、DML、DCL、TCL)

目录 一、DQL 二、DDL 三、DML 四、DCL 五、TCL 一、DQL(数据查询语言) Data Query Language,数据查询语言: select:用于数据查询 关键字:SELECT ... FROM ... WHERE 二、DDL(数据定义语…

Unity(第二十四部)UI

在游戏开发中,用户界面(UI)是至关重要的一部分。它负责与玩家进行交互,提供信息,并增强游戏的整体体验。Unity 提供了强大的工具和功能来创建和管理 UI。 ui的底层就是画布,创建画布的时候会同时创建一个事…

前端开发人员如何做好SEO

前端开发人员如何做好SEO SEO工作不仅限于专业人员。前端开发者也可以在日常开发中实施一些代码层面的SEO优化。 以下是一些前端常用的SEO方法: 设置合理的title、keywords、description title、keywords、description对SEO至关重要,需贴合页面内容编…

MySQL学生成绩管理系统based on C++ and Clion

mysql_free_result()函数的作用是释放结果集的内存,是同步的,也就是要中断一下 该实验使用了MySQL链接数据库的基本使用方法,具体使用了 MYSQL_RES 数据库的mysql_store_result()函数的返回值是一个结果集,该函数的作用是检索比…

Android ANR 日志分析定位

ANR 是 Android 应用程序中的 "Application Not Responding" 的缩写,中文意思是 "应用程序无响应"。这是当应用程序在 Android 系统上运行时,由于某种原因不能及时响应用户输入事件或执行一个操作,导致界面无法更新&…

C++ 引用 相关概念

文章目录 前言引用的概念引用的定义方式引用 的作用 前言 本文介绍 C 中 引用 的概念,引用 的定义和用法。 引用的概念 首先记住几个基本概念: 在 C 中可以给变量取别名,这个 “别名” 就称为这个变量的引用。引用变量在定义的时候就要初…

docker常见的命令集锦

Docker 是一种常用的容器化技术,它提供了一系列的命令来管理和操作容器。以下是一些常见的 Docker 命令集锦: docker run:用于启动一个新的容器实例。可以指定镜像名称、端口映射、环境变量等。 docker stop:用于停止正在运行的容…