Gradle基础

1. Gradle是什么

Gradle是一个通用的构建工具,支持诸多主要的 IDE,包括 Android Studio、IntelliJ IDEA、Visual Studio 等

  • Gradle 的底层实现(核心引擎和框架)其实是用 Java 编写的
  • 开发者通常使用 Groovy 或 Kotlin 来编写构建脚本
1.1 那么为什么Gradle的底层不使用Groovy进行开发,而是使用Java ?
  • Java具有更被广泛验证的稳定性,生态系统庞大且成熟,拥有大量的开源库和工具
  • Java是静态语言,具有更好的可维护性,尤其是在一个需要长期维护的大型开源项目时
  • 随着时间推移,Gradle还引入了Kotlin DSL作为构建脚本的开发,而底层仍然使用Java,这样可以确保无论脚本层如何变化,核心引擎的性能和表现都能一致

2. Gradle和Ant、Maven

  • Ant : 2000年由Apache推出的纯Java编写的构建工具,通过build.xml文件管理项目
  • Maven : 2004年Apache推出的使用pom.xml管理项目的构建工具
  • 缺点 : 配置文件编写不够灵活,构建过程僵化
  • Gradle : 2012年Google推出的权限项目构建工具,集合了Ant和Maven各自的优势。
  • 相对学习成本高

解决Android项目下载Gradle 修改镜像源 android安装gradle_Java

3. Gradle安装包

在Gradle中,src、bin和all是不同类型的发布版本或分发方式,这些版本在内容和使用上有所不同。
Gradle的所有安装包都可以在官网上下载到 : Gradle Release,以下是它们之间的主要区别:

  • src(源码版):包含了Gradle的完整源代码
  • bin(二进制版):包含了Gradle的可运行程序,但不包含源码和文档
  • all(完整版):包含了Gradle的完整发布,包括可运行程序、用户文档和源码

4. Gradle项目结构

Android项目的目录结构就是继承自Gradle的,所以我们会感觉Gradle的项目结构非常的熟悉。
下面这张图,我对Gradle目录的结构,做了对应的说明。

解决Android项目下载Gradle 修改镜像源 android安装gradle_Groovy_02

其中

  • gradlew.bat 这个是在windows下的可执行脚本
  • gradlew是在其他系统下的可执行脚本

无论是gradlew.bat还是gradlew实际上都是执行的wrapper文件夹底下指定版本的Gradle的指令

详见 Gradle Directories

5. Gradle Wrapper

Gradle的项目中,都有gradle-wrapper.jar和gradlew.bat脚本。
假设我们把这个项目复制到没有安装任何Gradle的机器上,会发生什么 ?

会自动去下载指定的gradle。

可以看一下脚本里的源码,本质上会去加载并运行gradle-wrapper.jar。gradle-wrapper.jar 会把gradle指定版本给下载下来后,然后用这个指定gradle版本来执行构建。

把gradlew wrapper给上传上去,以保证任何人将项目源码下载下来之后,都可以和你一样的gralde版本去构建,以避免版本不一致带来的问题。

6. GradleUserHome

GradleUserHome是Gradle构建工具的一个重要属性,它是Gradle的工作及缓存仓库文件目录。如果不进行特别配置,GradleUserHome的默认路径通常是用户目录下的“.gradle”文件夹 (C:\Users\我的用户名\.gradle)。在这个目录下,Gradle会下载或缓存大量文件。

解决Android项目下载Gradle 修改镜像源 android安装gradle_android_03

由于GradleUserHome占用存储空间比较大,我们可以配置全局的环境变量,将GradleUserHome设置到非系统盘中,从而减少C盘的磁盘空间占用。

解决Android项目下载Gradle 修改镜像源 android安装gradle_android_04

如果你把一个gradle的脚本放在init.gradle这个目录,那么这个脚本就会对你机器上所有的gradle构建生效,在你的gradle构建之前,它就会先执行一下。比如说执行一个全局的仓库替换(国内下载很慢)。
但是init.gradle 文件和项目的 settings.gradle 或 settings.gradle.kts 文件不同。settings.gradle 文件是用于配置项目设置(如项目名称、包含的模块等)的,而 init.gradle 是全局的,并且针对Gradle守护进程。
在大多数情况下,你不需要手动创建或编辑 init.gradle 文件,除非你有特定的全局配置需求。如果你确实需要修改它,确保你了解这些更改的影响,并在必要时进行充分的测试。

6.1 gradle-wrapper.properties文件解读

gradle项目的wrapper目录下,有一个`gradle-wrapper.properties`文件,这个文件用来配置Gradle具体的版本和下载地址以及存放路径

  • zipStore : 压缩包存放的目录
  • distribution : 压缩包解压后存放的目录
  • distributionUrl : Gradle的版本及下载地址,默认的是国外的下载地址,可能下载速度比较慢,可以将URL改为国内的 ,比如https://mirrors.cloud.tencent.com/gradle/gradle-8.2-bin.zip

解决Android项目下载Gradle 修改镜像源 android安装gradle_Gradle_05

7. Gradle相关的其他文章

Android Gradle插件开发_实现自动复制文件插件


Groovy基础语法

 1. Groovy是什么

Groovy是基于JVM虚拟机的一种动态语言,语法和Java非常相似,并能够无缝地与Java代码集成和互操作,增加了很多动态类型和灵活的特性。(闭包、DSL)

语法和Java非常相似这个特点,意味着,如果我们完全不懂Groovy的语法,也可以按Java的语法来写Groovy代码。 这对于Java工程师来说是非常友好的。

不过,如果我们懂得一些Groovy的语法,那么对于Groovy的开发,会更加的方便和得心应手。以下是一些Groovy区别于Java的一些特性。

2. def类型推断

在 Groovy 编程语言中,def 关键字是一个非常灵活的声明符,它用于定义变量、方法或属性,并且允许类型推断。使用 def 时,编译器会根据所赋值的对象自动确定变量的类型,因此开发者不需要显式地指定变量的类型。

Groovy是一种动态类型语言,这就意味着它既可以表现出强类型语言的特点,也可以表现出弱类型语言的特点。
在Groovy中,你可以选择明确声明变量的类型,这就像强类型语言那样,编译器会在编译时检查类型错误。这种方式有助于提早发现和避免一些可能的错误。
另一方面,你也可以选择不声明变量的类型,让Groovy在运行时决定变量的类型,这就像弱类型语言那样。这种方式使得代码更加简洁,但也可能增加运行时出错的风险。
因此,Groovy既可以被视为强类型语言,也可以被视为弱类型语言,这取决于你如何使用它。

2.1 变量定义:

当你使用 def 声明一个变量时,Groovy 不需要知道变量的具体类型,它可以在运行时动态地决定变量的类型。


def a = 10 // a 将被推断为整数类型 Integer
def b = "Hello" // b 将被推断为字符串类型 String


2.2 可变类型:

使用 def 定义的变量可以改变其引用的对象类型,这是因为在 Groovy 中,变量本身没有固定的类型,而是指向了一个对象。


def c = true // c 被推断为布尔类型 Boolean
c = "World" // 现在 c 变成了字符串类型


2.3 方法定义:
在 Groovy 中,def 还可以用来定义方法(函数),此时方法返回的类型也是可以由编译器推断得出或者不返回任何值(void)。


def add(a, b) {
    return a + b
}


2.4 属性定义:

在类中定义属性时,也可以使用 def,这将创建一个具有隐式 getter 和 setter 的属性,其类型同样是动态推断的。

总之,def 关键字是 Groovy 动态特性的体现,它增强了代码的简洁性和灵活性,特别是在快速开发和脚本编写场景中表现尤为突出。不过,在大型项目或需要严格类型检查的情况下,可能需要更多地考虑是否使用明确类型的变量声明来增强代码的可读性和维护性。

3. 字符串
Groovy中的字符串和java中非常相似,还可以使用单引号''


task stringTest {
    String str1 = "hello"
    var str2 = "wrold"
    def str3 = "!"
    //print(str1 + str2 + str3)
    println("${str1} ${str2} " + str3)
}


4. 列表

列表可以直接用[]进行定义,遍历起来也非常方便


task listTest {
    def list = [5, 6, 7, 8, 9]
    println list[0]

    //遍历方式一
    for (int data in list) {
        println(data)
    }
    //遍历方式二
    for(int i in 0..4){
        println(list[i])
    }
}


5. Map

Map也是一样的,使用[key:value]的形式定义,遍历也非常方便


task mapTest() {
    def map = ["name": 'heiko', "age": 18]
    println map["name"]
    map.each {
        println it
        println it.key + ":" + it.value
    }
}

task method {
    def a = methodA(3, 5)
    println(a)
}


6. 方法

groovy语法当中默认将函数非空(有代码的)最后一行作为结果进行返回


def methodA(int a, int b) {
    //groovy语法当中默认将函数非空(有代码的)最后一行作为结果进行返回
    a + b
}


7. 类

类的使用和Java中几乎一样


class Student {
    private String name
    private int age

    private String getName() {
        return name
    }

    private int getAge() {
        return age
    }

    private void setName(String name) {
        this.name = name
    }

    private void setAge(int age) {
        this.age = age
    }


    @Override
    String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

task classTest() {
    Student student = new Student()
    student.name = "heiko"
    student.age = 20
    println(student)
    student.setName("XXX")
    student.setAge(21)
    println(student)
}


8. 闭包

本质是一个开放的、匿名的代码块,它可以接受参数、也可以有返回值。
可以认为闭包是一个匿名的接口或类。

8.1 语法

闭包的参数可以是0个、1个或多个


{ [params ->] 
	//具体代码
}


Groovy大括号{}是闭包,小括号()是可以省略的,默认是带有小括号的,但是Groovy因为是糖果语法可以省略小括号
闭包{} 只针对{} 区域内有效。

8.2 一个最简单的闭包


def myClosure = {
    println("running start...")

    println("running end...")
}

//调用闭包方式一
myClosure() 
//调用闭包方式二
myClosure.call()


9. 示例 : 使用groovy语法读取一个文件 : .properties文件


def getUrlPath(String buildType) {
    def url

    Properties properties = new Properties()
    def proFile = file("src/main/filters/" + buildType + "/config.properties")
    if (proFile.canRead()) {
        properties.load(new FileInputStream(proFile))
        if (properties != null) {
            url = properties["SERVER2"]
        }
    }

    url
}


10. Gradle相关的其他文章

Android Gradle插件开发_实现自动复制文件插件


Groovy语法概念与闭包 

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 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,并配置了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 applicationId
    public int minSdk
    public int targetSdk
}
5.6 声明函数 : mydefaultConfig

声明函数mydefaultConfig,传参为一个闭包closure,然后调用closure.delegate = config。closure.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