gradle系列——groovy,核心对象(一)

·  阅读 131

本文已参与「新人创作礼」活动,一起开启掘金创作之路

前言

这个系列是针对Android开发中的gradle入门知识,主要解决平时遇到的一些gradle疑问,包括

  • groovy重点语法,闭包到底有什么用
  • gradle是怎么流转工作的
  • 各个gradle脚本都有什么用
  • gradle插件是什么,怎么自定义
  • gradle的实用技巧,模块化

gradle到底是干嘛的

学一个东西之前,当然要搞清楚它到底是干嘛的,学了它能用到什么地方

最开始接触gradle的时候,给我最大的感觉就是,这玩意儿除了能一句话依赖一个第三方库,其他的实在一无是处,每次导入一个项目sync半天,然后还报错了...

但实际上gradle功能非常强大,官方叫做自动化构建工具,那怎么理解这个构建了,构建无非就是把写的源代码经过编译等处理成一个可用的程序的过程,对于我们Android开发来说,就是编译打包生成apk或者aar的这些流程

我们都知道打包一个apk的过程大概需要经过资源文件处理,aidl编译成java,java文件编译成class,class打包成dex,混淆签名等等,例如编译成class,我们可以手动用javac命令也能完成,所以这些流程我们其实都可以自己手动通过具体的脚本工具命令一步步去完成,但想想这样打包一次的工作量得多大,按这种方式的话,一天有一半的时间都在打包,这哪儿还有时间写业务,另外国内的应用市场,主流的得有10来个,每个市场我们可能都有相应的统计渠道,需要编译成对应的渠道包,那发布的时候,我们还得每个渠道都打一遍,这种大量的重复性工作,难免会出现问题

在我们开发中一定需要用到很多第三方的依赖库,例如google官方的kotlin,androidx,第三方的glide,okhttp等等,之前eclipse开发的时候,都应该经历过去官网把jar下载下来,放到指定目录,如果有更新,又要如此往复,相当麻烦也容易出错。

于是,为了解决上面这些重复且繁琐的过程,自动化构建工具就出来了,gradle就是目前最主流的一个,它整合了这些流程,让我们通过简单的配置代码就能完成打包。gradle之前还有ant,maven,这些我也只是了解,我觉得既然gradle已经替代了他们,而且一个人的精力是真的有限,所以我觉得要是没有特别必要使用那两个,就把精力先全部集中在gradle上面就好了,免的学杂了还容易混乱

当然gradle是一个通用的构建工具,它可以应用在很多种项目中,例如Android,web,所以它只定义大的流程框架(例如怎么去整合项目中的各个模块,如何让一系列的任务命令串起来执行),不定义具体的构建逻辑,具体怎么去构建是通过它开放的接口来实现的,这个接口就是插件(后续文章会讲到),例如Android项目对应就有Android插件classpath "com.android.tools.build:gradle:4.2.2"

gradle基础之groovy

gradle是一个构建工具,不是一门语言,它需要使用groovy来编写(现在也可以用kotlin,但是现在主流的还是groovy,先一步步学会的吧)

groovy跟java很多地方都很像,毕竟它编译之后也是生成class文件,运行在jvm上面,所以基本上也可以用java的方式去写,但是那些build.gradle里面的代码,不学是真的看不懂啊

之前我学习groovy,对他的那些语法糖感到很难受,各种简写,阅读起来是真的麻烦,但是自从我学了kotlin,再回来看这个groovy,发现这***不是一样的么?

数据类型

数据类型就不介绍了,无非就int,string,对象集合这些,这些文章很多,而且也很简单,如果你会kotlin,这个就是换了一个关键字,不学都能看懂,下面就主要介绍一些关键的groovy比较特殊的地方,主要的目标就是能看懂build.gradle这些脚本的内容到底是个啥

函数

groovy函数的定义有以下几个关键特性

  • 函数定义时,参数的类型可以不用指定
  • 无指定返回值的函数,需要使用def关键字定义,且函数的最后一行的结果就是返回值,这种情况返回的都是Object类型,有返回值的函数就跟java一样在函数名前指定返回值就好了
  • 函数调用时可以省略括号

看demo就懂了

// 无指定返回值的函数,def修饰
def noReturnMethod(){
    
}

// 指定返回值的方法
String returnStringFunc(){
    return "str"
}

// 参数不指定类型
def getSum(a,b){
    return a+b
}

println getSum(10,2)

// 方法调用省略括号,setting.gradle文件中常见
include ':app'

复制代码

闭包

这个很重要,这个很重要,这个很重要,重要的事情说三遍,弄懂这个闭包,再来看那些gradle文件,就会有恍然大明白的感觉

闭包是groovy中的语法糖,如果你会kotlin的话,这个语法其实也很简单,基本就类似于高阶函数里面的函数对象,换了个说法

// 标准闭包定义,接受两个参数
def myClosure = { p1, p2 ->
    p1 + p2
}
// 闭包的调用,看起来是不是跟kotlin的函数类型基本一样
myClosure.call(1,2)
myClosure(1,2)

// 如果不指定参数,默认是有一个it参数的
def myClosureNoParam = {
    println "param is $it"
}

myClosureNoParam("param")

// 强制指定不带参数,包括it也不可用
def myClosureForce = { ->
    println "myClosureForce"
}

// closure作为函数的参数,这个就跟kotlin的高阶函数非常一样了
def closureFun(a,Closure closure){
    closure(a)
}

// 调用的时候,也是把闭包也在括号外,类似于kotlin中的lambda
closureFun(1){
    println "closure fun $it"
}

复制代码

对照我们平常接触的build.gradle这些文件中,其实基本到处可见闭包

// buildscript 是个函数,里面的参数是一个闭包,函数调用省略了括号
buildscript {
    ext.kotlin_version = "1.5.10"
    // repositories里面也是一个闭包
    repositories {
        google()
        mavenCentral()
    }
    // dependencies里面同样是一个闭包
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
复制代码

以上是闭包的基本用法,到这跟kotlin的高阶函数基本一样,但gradle中闭包有个最关键的功能,就是闭包的委托delegate(这个是理解build.gradle中那些语法的重中之重

在闭包中有三个关键变量,this,owner,delegate,默认情况下,this指向的是定义闭包所在的类,owner和delegate指向的是定义闭包所在的闭包,如果没有闭包,就是所在的类,而delegate是可以改变的,通过直接设置delegate属性

这样做带来的变化就是,可以让闭包内处于所代理的这个对象的上下文环境中,这样说可能有点抽象,举个栗子就明白了

class Person{
    String name

    def setAge(age){
        println "name is $name and age is $age"
    }
}

def closureDelegate = {
    name = "zhangsan"
    setAge 10
}

Person p = new Person()

// 给闭包closureDelegate设置delegate为p对象
closureDelegate.delegate = p
closureDelegate()
复制代码

上述demo中把闭包的delegate指向了对象p,于是闭包就拥有了p对象内部的上下文环境,即可直接操作使用p对象的属性方法

其实在gradle中也是到处都使用到了这个delegate,还是看build.gradle的例子

// buildscript 里面是一个闭包,点击看repositories的源码会发现,repositories是ScriptHandler接口的一个方法,其实buildscript里面这个闭包,对应的delegate就是ScriptHandler
buildscript {
    ext.kotlin_version = "1.5.10"
    // 同样这个google的源码其实是RepositoryHandler接口的一个方法,所以repositories里面这个闭包对应的delegate就是RepositoryHandler
    repositories {
        google()
        mavenCentral()
    }
    // dependencies里面同样是一个闭包
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
复制代码

理解了groovy中的函数的语法糖,还有闭包相关的东西,基本就能看懂绝大多书的gradle脚本内容了,但是gradle有个很恶心的地方,就是闭包看不到参数是什么,比如上面的buildscript,你点击进去看他的源码之后,知道了它是一个函数,函数的参数是一个闭包,但是并不知道这个闭包有什么参数,或者delegate指向什么,这些都只能通过阅读官方文档去找 docs.gradle.org/current/dsl…

例如上述这个buildscript函数,官方文档就有介绍

The given closure is executed against this project's [ScriptHandler]. The [ScriptHandler] is passed to the closure as the closure's delegate.

gradle里面的对象

gradle是一个框架,既然是框架,它就类似于我们平时学的其他框架工具,例如Glide,他自身肯定有很多核心类,接口,属性,我们主要就是利用这些类,接口去完成一些工作。

我觉得学习gradle需要转变一个思想,就是我们平常看到的setting.gradle,build.gradle这些看起来都是一个个的脚本,里面的代码毫无章法,而且因为DSL看起来就更别扭,各个脚本之间也看不出来有什么联系,不像java,必须在class中,各个类之间有没有联系一看有没有引用就知道,但其实既然groovy也基于jvm,所以他还是面向对象的,每个脚本实际都对应了一个实际的对象

在gradle中,最重要的,需要去了解的主要就3个对象,gradle对象,settings对象以及project对象

gradle

gradle对象是一个全局的对象(相当于静态单例),在任何gradle脚本中(setting.gradle,build.gradle,这些都是gradle脚本)都可以直接去使用,gradle对象的主要作用是提供了接口去Hook整个构建流程(构建流程也叫构建生命周期,就是gradle执行一个构建任务的流程,这个后续文章会有专门介绍)

settings

settings对象的使用场景主要就是setting.gradle脚本了,这个脚本下的内容,实际就是运行在setting对象的上下文中,可以理解为在这个文件中可以任意调用setting对象中的方法。例如最常见到的include方法,都是setting对象的方法

project

porject对象的使用场景就是各个build.gradle脚本中了,同settings.gradle,build.gradle脚本中的内容是处于project对象的上下文中,因此可以任意调用project的方法

buildscript

参数是一个闭包,闭包的参数是ScriptHandler类型的,闭包的delegate也是这个ScriptHandler对象,主要用于配置项目需要的插件信息

allprojects

参数是一个闭包,闭包的参数是每一个project对象(每个module下的build.gradle,包括根目录下的,都是一不同的project对象),同样闭包的delegate也是对应的project对象,主要用于对每个project做一些统一的配置

getAllprojects()

获取到所有的project对象

getSubprojects()

获取当前module下所有子module的project对象

getParent()

获取父project对象,根项目的gradle脚本获取到的是null

getRootProject()

获取根项目下的project对象

plugins

获取project使用到的插件

ext

ext称为扩展属性,如果学习过kotlin,应该也就很熟悉,就是在原来对象的基础上增加一些固定的属性值,这样在任何地方就都可以直接使用了

ext属性一般有三种使用场景:

1.在根目录下的build.gradle文件中使用,比如声明kotlin的版本号

注意:在根目录下,如果是在buildscript闭包中需要用到的,则必须是在这个闭包中去声明ext属性,否则会报错找不到,因为buidlscript永远是第一个执行的,不管他在什么位置

// 这个在buildscript内访问不到,但在其他地方,只要在这行代码之后,都能访问到,在子项目的build.gradle中也能访问到
ext.hello_ext = "hello ext"

buildscript {

    ext.kotlin_version = "1.5.10"

    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        // 声明在buildscript,这个可以访问到
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
复制代码

在子module定义的ext属性,只能在当前module使用,跟用def直接定义一个变量差不多作用,意义不大

2.专门创建一个存放ext的gradle脚本

开发都有分模块,单职能的原则,因为项目越来越大,gradle构建可能越来越复杂,需要配置的属性也越来越多,都放在根目录的gradle中也容易混乱,因此可以抽取一个专门存放ext的gradle脚本,例如config.gradle

ext {

    // 一般可以用map去归类存储
    android = [
            compileVersion: 31,
            buildVersion  : "30.0.3"
    ]
}
复制代码

在module的gradle脚本中使用

apply from: '../config.gradle'

android {
    compileSdkVersion android.compileVersion
    buildToolsVersion android.buildVersion
}
复制代码

3.在config.gradle中把依赖统一管理起来,gradle只需要使用循环引入

ext {

    version = [
            appcompat:'1.2.0',
            constraintlayout:'2.0.1'
    ]

    dependenciesMap = [
            appcompat:"androidx.appcompat:appcompat:${version.appcompat}",
            constraintlayout:"androidx.constraintlayout:constraintlayout:${version.constraintlayout}"
    ]
}
复制代码

在module的gradle文件中

dependencies {
    dependenciesMap.each {k,v->
        implementation v
    }
}
复制代码

学习中参考学习了大量大佬的系列文章,感谢大佬们的贡献

Gradle 与 Android 构建入门

Gradle 爬坑指南

Gradle学习系列

深度探索 Gradle 自动化构建技术

Gradle系列

补齐Android技能树

Android 修炼手册

关于Gradle, 搞定Groovy闭包这一篇就够了

以上是gradle系列基础部分的一些知识,后续系列会持续更新,如果有写的不对的地方欢迎批评指正,感觉写的还不错的也欢迎评论点赞

分类:
Android
标签:
分类:
Android
标签: