Gradle 核心 个人笔记

Gradle 以前专门深入学习过,但是因为使用频率不高,加上时间一长,基本上忘的差不多了。为了记住基础知识点,所以有了这篇笔记 。

文章很多内容转自 刘望舒大神的博客,个人做了精简、补漏、和错误修正。

1 Gradle 入门

1.1 环境配置

1.官网 下载对应 Gradle 版本的 binary-only;
2.打开.bash_profile 文件来配置环境变量,命令:open -e .bash_profile
3.配置 gradle 路径,命令:export PATH=$PATH:~/xxx/gradle-xxx/bin
4.保存,并更新 .bash_profile 文件,命令:source ~/.bash_profile
5.测试是否配置成功,命令:gradle -v

1.2 示例代码

运行 gradle 命令时,会从当前目录下寻找 build.gradle 文件执行构建。

如 gradle -q hello 命令,会从 build.gradle 文件中寻找 hello 任务执行。

task hello {
    doLast {
        println 'Hello world!'
    }
}

task(任务)和 action(动作)是 Gradle 的重要元素。
task 代表一个 独立的原子性操作,比如复制一个文件,编译一次 Java 代码,这里我们简单的定义一个名为 hello 的任务。
doLast 代表 task 执行的 最后一个 action,通俗来讲就是 task 执行完毕后会回调 doLast 中的代码。

操作符 << 是 doLast 方法的快捷版本,上面示例可以:

task hello << {
    println 'Hello world!'
}

如果报 Could not find method leftShift() for arguments...,是因为:
操作符 << 在 Gradle 4.x 中被弃用(deprecated),并且在 Gradle 5.0 被移除(removed)

1.3 任务 Task

创建

def Task hello = task(hello)
hello.doLast{
     println "hello world"
}
def Task hello = task(hello, group:BasePlugin.BUILD_GROUP)
hello.doLast{
     println "hello world"
}
tasks.create(name: 'hello') << {
    println "hello world"
}

tasks 类型为 TaskContainer,上述创建方式最终调用的都是 tasks.create。

依赖

task hello {
    doLast {
        println 'hello world'
    }
}

task go (dependsOn: hello) {
    println 'go'
}

先输出 go,再输出 hello world;

task hello {
    doLast {
        println 'hello world'
    }
}

task go(dependsOn: hello) {
    doLast {
        println 'go'
    }
}

先输出 hello world,再输出 go;

task hello {
    println 'hello world'
}

task go(dependsOn: hello) {
    println 'go'
}

先输出 hello world,再输出 go;

动态定义

动态定义任务指的是在运行时来定义任务的名称,如下所示。

3.times {number ->
    task "task$number" {
        doLast{
            println "task $number"
        }
    }
}

gradle -q task0 输出 task 0;

3.times {number ->
    task "task$number" {
        println "task $number"
    }
}

gradle -q task0 输出 task 0 task 1 task 2

上述示例意为:
循环创建 3 个任务,任务名为 task$number。3.times 表示循环 3 次,数字迭代从 0 ~ 2。

分组和描述

为任务配置分组和描述,可以更好的管理任务。

task hello {
    group = 'build'
    description = 'demo'
    def test = 'mytest'
    doLast {
        println "$group" //注意 双引号
        println "${description}" //括号加不加都可以
        println "$test" //自定义属性要用def,否则会报 Could not set unknown property
    }
}

task go(dependsOn: hello) {
    doLast {
        println 'go'
    }
}

其它配置方式

def Task hello = task(hello)
hello.description = 'demo'
hello.group = 'build'
hello.doLast {
    println "任务分组: ${group}"
    println "任务描述: ${description}"
}
task go(dependsOn: hello) << {
    println "go for it"
}

1.4 日志等级

级别 用于
ERROR 错误消息
QUIET 重要的信息消息
WARNING 警告消息
LIFECYCLE 进度信息消息
INFO 信息性消息
DEBUG 调试消息

gradle -q task 中的 q 即日志输出级别中的 QUIET 及更高级别。

1.5 常用命令

作用 命令 备注
获取所有任务信息 gradle tasks 默认情况下,只会显示那些被分组的任务的名称和描述。
排除任务 gradle taskA -x taskB 上例中,gradle -q go -x hello,只执行 go。
获取任务帮助信息 gradle help --task task 输出 path、type、group、description
多任务执行 gradle taskA taskB 先执行A,再执行B。但是A依赖B,则B先执行,且A只执行一次,如上例中输出 build demo mytest go

2. Groovy 入门

2.1 编写和调试

Groovy 的代码可以在 Android Studio 和 IntelliJ IDEA 等 IDE 中进行编写和调试,缺点是需要配置环境,推荐在文本中编写代码并结合命令行进行调试(文本推荐使用 Sublime Text)。

2.2 变量

def 定义变量,可以不设置变量类型,默认 public

def a = 1;
def int b = 1;
def c = "hello world";

2.3 方法

返回值:返回类型或 def 关键字定义
参数:任意数量,可以不申明类型
可见性:默认 public

def a = 3

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

def minus(a, b) {
    a - b
}



task go {
    doLast {
        println add(1, 2)
        def num = minus 2, 1
        println num
        //直接 println minus 2, 1 是错误的
    }
}
  1. 分号 可以参略;
  2. 方法括号 可以省略;
  3. 参数类型 可以省略;
  4. return 可以省略。

2.4 类

task go {
    def book = new Book()
    book.name = "Test" //成员变量必须使用= 没有括号可省 除非定义函数name
    book.changeMoney 100
    book.setName "Test1"
    //book.name "Test2" //错误的
    println book.name
    println book.money
}

class Book {
    String name
    Integer money = 30
    def changeMoney(money) {
        this.money = money
    }
}

和 Java 类似,会自动生成 setter 和 getter 方法,默认 public。

2.5 for 循环

//遍历范围
def x = 0
for ( i in 0..3 ) {
    x += i
}
//遍历列表
def x = 0
for ( i in [0, 1, 2, 3] ) {
    x += i
}
//遍历Map中的值
def map = ['a':1, 'b':2, 'c':3]
x = 0
for ( v in map.values() ) {
    x += v
}

2.6 switch 语句

task method {
    def x = 16
    def result = ""

    switch ( x ) {
        case "ok":
            result = "found ok"
        case [1, 2, 4, 'list']:
            result = "list"
            break
        case 10..19:
            result = "range"
            break
        case Integer:
            result = "integer"
            break
        default:
            result = "default"
    }
    assert result == "range"
}

2.7 数据类型

Groovy 数据类型包括:Java 中的基础数据类型,Groovy 中的容器类,闭包

String

包含 普通字符串 String(java.lang.String)和插值字符串 GString(groovy.lang.GString)。

单引号不支持插值;
双引号支持插值,使用 $ or ${}
三引号保留文本换行和缩进,不支持插值。

当双引号字符串中包含插值表达式时,字符串类型为GString

List

List 默认实现为 ArrayList,可以使用 as 来显示指定 List 实现类。

task go {
    def num = [1, 2, 3] // 定义
    println "is num instance of List: " + (num instanceof List) // true
    def linknum = [1, 2, 3] as LinkedList
    println "is linknum instance of LinkedList: " + (linknum instanceof java.util.LinkedList) // true
    println num[1] // 2
    println num[3] // null
    println num[-1] // 3
    num << 4 // 末尾追加一个元素
    println num[3] // 4
}

Map

task go {
    def map = [1: 'a', two: 'b', 'three': 'c', "four": 4]
    println map[1] // a
    println map['1'] // null
    // println map[two] 错误 Could not get unknown property 'two' for task
    println map['two'] // b
    println map['four'] // 4

    def key = 'value'
    def cache = [key: '这里的key是String']
    println cache['key']
    cache = [(key): '这里的key是value']
    println cache['value']
}

注意键关联问题,使用 (x) 告诉解析器我们传递的是一个变量,而不是定义一个字符串键值。

闭包

闭包是 groovy.lang.Cloush 类的一个实例。

1.闭包可以赋值给变量或字段

def p = {it -> println it}

2.调用

<1. 把闭包当作函数

def build = {
    it -> println it
}

task go {
    build(10)
}

<2. 把闭包当作参数

def method(closure){
    closure(3) //只负责把参数传给闭包,闭包的代码块是什么不关心,类似Listener,但是更简单
}

task go {
    method {
        it -> println it
    }
}

但是上述代码会 报错。The current scope already contains a variable of the name it。

类似的代码但是没用闭包则没有问题:

def say(a){
    println a
}

task go {
    say 3
}

原因是:
task 后边也是一个闭包,闭包参数在没有声明的情况下,默认参数名为 it,而 method 使用的参数名也是 it,变量重复定义导致冲突,只需要改个名即可。

def method(closure){
    closure(3)
}

task go {
    method {
        m -> println m
    }
}

task 中是可以直接调用 build.gradle 中定义的变量或函数的,但要注意变量名。

其它例子:

def run(a, b, callback) {
    def res = a + b;
    callback(res)
}

task go {
    run(3, 4, {
        i -> println i
        })
}

// 或者省略括号
task go {
    run 3, 4, {i -> println i}
}

如果报错,检查一下是不是括号用成中文了... sublime 很不明显。

2.8 I/O

1.读取

def filePath = '/Users/dixon.xu/Demo/gradle_demo/test.txt'
def file = new File(filePath) ;
file.eachLine {
    println it
}

def filePath = "D:/Android/name.txt"
def file = new File(filePath) ;
println file.text

2.写入

File 内容被覆盖。

def filePath = "/Users/dixon.xu/Demo/gradle_demo/test.txt"
def file = new File(filePath);

file.withPrintWriter {
    it.println("test1")
    it.println("test2")
}

2.9 其它

asType

String a = '23'
int b = a as int
def c = a.asType(Integer)
assert c instanceof java.lang.Integer

判断字符串为空

if (name != null && name.length > 0) {}

或简化为

if (name) {}

安全取值

避免大量判空。

if (school != null) {
    if (school.getStudent() != null) {
        if (school.getStudent().getName() != null) {
            System.out.println(school.getStudent().getName());
        }
    }
}

上面 Java 语句可以替换为:

println school?.student?.name

with 操作符

task method {
    Person p = new Person()
    p.name = "aaa"
    p.age = 19
    p.sex = "男"
    println p.name
}

class Person {                       
    String name                      
    Integer age
    String sex
}

初始化代码可以简化为:

Person p = new Person()
p.with {
   name = "aaa"
   age= 19
   sex= "男"
 }   
println p.name

2.10 仿 Android Gradle 配置代码

前言:下面的代码编译报错。

def a = 10

def method() {
    println a
}

task go {
    method()
}
// 报错 Could not get unknown property 'a' for root project 'gradle_demo' of type org.gradle.api.Project.

完整代码,细节见注释。

// 定义类
class Android {
    int sdkVersion
    boolean enable
    void sdkVersion(sdkVersion){
        this.sdkVersion = sdkVersion
    }
    void enable(enable){
        this.enable = enable
    }
}

// 定义参数
ext {
    def android
} 

// 定义函数
def android(closure) {
    Android android = new Android()
    closure.delegate = android
    closure() // 可以使用closure.call() 或 closure()调用闭包,直接closure不会异常但是不生效
    println '编译(初始化配置)输出: ' + android.sdkVersion + ' ' + android.enable
    // 将初始化编译时生成的android赋值给ext.android
    project.ext.android = android
}

// 仿Android配置代码 调用函数,传入闭包
android {
    sdkVersion 3
    enable true
}

// 仿Android编译代码
task compile {
    // 拿到配置的Android参数,然后执行xxx
    println project.ext.android.sdkVersion
}

3. Gradle Wrapper

3.1 前言

Gradle Wrapper,它是一个脚本,可以在计算机没有安装 Gradle 的情况下运行 Gradle 构建,并且能够指定 Gradle 的版本,开发人员可以快速启动并运行 Gradle 项目,而不必手动安装。

AS 在新建项目时会自带 Gradle Wrapper,这也是我们很少去单独去下载安装 Gradle 的原因。

当使用 Gradle Wrapper 启动 Gradle 时,如果指定版本的 Gradle 没有被下载关联,会先从 Gradle 官方仓库下载该版本 Gradle 到用户本地,进行解包并执行批处理文件。后续的构建运行都会重用这个解包的程序。

3.2 构建

使用 gradle wrapper 就可以在项目目录中生成 Gradle Wrapper 的目录文件。生成目录结构如下:

├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
名称 说明
gradle-wrapper.jar 包含 Gradle 运行时的逻辑代码。
gradle-wrapper.properties 负责配置包装器运行时行为的 属性文件,用来配置使用哪个版本的 Gradle 等属性。
gradlew Linux 平台下,用于执行 Gralde 命令的包装器脚本。
gradlew.bat Windows 平台下,用于执行 Gralde 命令的包装器脚本。

由于上述目录配置了 Gradle 的版本等信息,当其他用户 clone 下来后会自动下载对应 Gradle 版本;因为还包含多平台的 Gradle 命令脚本,所以可以直接进行项目的构建。

使用 Gradle 命令选项来配置指定的 gradle wrapper。

命令 说明
–gradle-version 用于下载和执行指定的 gradle 版本。
–distribution-type 指定下载 Gradle 发行版的类型,可用选项有 binall,默认值是 bin,-bin 发行版只包含运行时,但不包含源码和文档。
–gradle-distribution-url 指定下载 Gradle 发行版的完整 URL 地址。

比如使用命令行:gradle wrapper –gradle-version 4.2.1 –distribution-type all,就可以生成版本为 4.2.1 的包装器,并使用 -all 发行版。

3.3 配置

gradle-wrapper.properties 中的可配置属性如下。

名称 含义
distributionBase Gradle 解包后存储的主目录。
distributionPath distributionBase 指定目录的子目录。distributionBase+distributionPath 就是 Gradle 解包后的存放位置。
distributionUrl Gradle 发行版压缩包的下载地址。
zipStoreBase Gradle 压缩包存储主目录。
zipStorePath zipStoreBase 指定目录的子目录。zipStoreBase+zipStorePath 就是 Gradle 压缩包的存放位置。

3.4 使用

使用 Gradle Wrapper 应当使用 gradlew 和 gradlew.bat 脚本。

如 mac 平台,执行 gradlew compile

当 Gradle 版本不存在时,会先去下载。

下载的文件保存在 ~/.gradle/wrapper/dists/gradle-6.3-bin。如果此后属性文件的 distributionUrl 不变,就会一直使用本地的该版本。

3.5 升级

升级 Gradle Wrapper 有两种方式,一种是设置 Gradle 属性文件的 distributionUrl 属性,第二种是通过运行 wrapper 任务,推荐使用第二种方式。

gradlew wrapper --gradle-version 6.3

3.6 自定义

Gradle Wrapper 的属性文件可以通过自定义 Wrapper Task 来设置。比如想要修改下载的 Gradle 版本为 6.3,可以这么设置:

task wrapper(type: Wrapper) {
    gradleVersion = '6.3'
}

也可以设置 Gradle 压缩包的下载地址和 Gradle 解包后的本地存储路径等配置。

task wrapper(type: Wrapper) {
    gradleVersion = '6.3'
    distributionUrl = '../../gradle-6.3-bin.zip'
    distributionPath = wrapper/dists
}

distributionUrl 属性可以为本地的项目目录,也可以为网络地址。

4. Gradle 插件浅析

Gradle 插件,非 Android Gradle 插件

4.1 分类

脚本插件

一个 .gradle 文件,用于额外的构建脚本,通过 apply from: 'xx' 调用。

对象插件

实现了 Plugin(org.gradle.api.plugins)接口的类,通过 apply plugin: 'xx' 调用。

对象插件又可以分成俩种。

1. 内部插件
apply plugin: 全限定名
eg: apply plugin: org.gradle.api.plugins.JavaPlugin
Gradle 默认导入了 org.gradle.api.plugins 包,所以可以简写为 apply plugin: JavaPlugin
也可以使用 pulginid 替代:apply plugin: 'java'

2. 第三方插件
第三方插件需要在项目根目录 build.gradle 的 buildscript 设置仓库和依赖,Android Gradle 插件也属于第三方插件。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
    }
}
apply plugin: 'com.android.application'

如果插件托管在 Gradle 插件官网,也可以使用如下方式配置插件,且不需要再配置 buildscript 了。

plugins {
  id "com.jfrog.bintray" version "1.8.4"
}

4.2 自定义简单的对象插件

apply plugin: CustomPlugin

class CustomPlugin implements Plugin {
    @Override
    void apply(Project project) {
        project.task('CustomPluginTask') {
            doLast {
                println '自定义插件'
            }
        }
    }
}

使用 gradlew CustomPluginTask 命令调用任务。

5. Gradle 插件开发

5.1 简单自定义插件扩展

class CustomPluginPluginExtension {
    String message = 'from CustomPlugin'
}
class CustomPlugin implements Plugin {
    @Override
    void apply(Project project) {
        def extension = project.extensions.create('custom', CustomPluginPluginExtension)//1
        project.task('CustomPluginTask') {
            doLast {
                println extension.message
            }
        }
    }
}
apply plugin: CustomPlugin
custom.message = "自定义插件拓展"

注释1处用于添加拓展插件 CustomPluginPluginExtension 到插件列表中,名称为 custom。

5.2 buildSrc 工程插件

将插件的源代码放在 rootProjectDir/buildSrc/src/main/groovy 目录中,Gradle 会自动识别来完成编译和测试。

1.在 rootProjectDir/buildSrc/src/main/groovy 目录下创建 CustomPlugin.groovy 文件,代码如下。

import org.gradle.api.Plugin
import org.gradle.api.Project

class CustomPlugin implements Plugin {
    @Override
    void apply(Project project) {
        project.task('CustomPluginTask') {
            doLast {
                println "自定义插件"
            }
        }
    }
}

2.build.gradle 中使用 apply plugin: CustomPlugin 引用。

3.gradlew CustomPluginTask 执行任务。

5.3 独立项目插件

创建插件

生成插件类 jar 包,供多项目使用。

1.IntelliJ 创建 Gradle 项目,勾选 Groovy,命名为 CustomPluginShare。

2.移除干扰目录 test,清空 build.gradle 文件,修改为下面的代码。

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

点击 build 之后,会发现没反应,External Libraries 也没有增加新的 jar 包。打开 Gradle 栏,执行同步,External Libraries 新增三个 jar 包。

gradle-api-5.2.1.jar
gradle-installation-beacon-5.2.1.jar
groovy-all-1.0-2.5.4.jar

现在,可以在工程中使用 Groovy 语法和 Gradle 的 api 了。

3.在 src/main/groovy/ 目录中创建一个包,我的是 com.example.plugins,在 com.example.plugins 创建一个 groovy 文件:
src/main/groovy/com/example/plugins/CustomPlugin.groovy

package com.example.plugins

import org.gradle.api.Plugin
import org.gradle.api.Project

class CustomPlugin implements Plugin {
    @Override
    void apply(Project project) {
        project.task('CustomPluginTask') {
            doLast {
                println '自定义插件'
            }
        }
    }
}

4.下面配置一些属性,以供外部识别插件。

新建 src/main/resources/META-INF/gradle-plugins/ 目录,并创建文件 com.example.plugins.customplugin.properties,将内容修改为:

implementation-class=com.example.plugins.CustomPlugin

① 文件名 com.example.plugins.customplugin.properties 决定插件的 id,如此例:

apply plugin: 'com.example.plugins.customplugin'

② 内容 com.example.plugins.CustomPlugin 代表此插件对应的入口函数全路径。即 apply plugin 文件名,就会调用文件内容 implementation-class 指定的函数。

5.将插件上传至本地。

build.gradle 中添加如下内容。

apply plugin: 'maven'
group = 'com.example.plugins'
version = '1.0.0'
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('../repo'))
        }
    }
}

name 默认为项目名,这样当依赖时,就可以使用下面的配置方式。

dependencies {
    //classpath 'group:name:version'
    classpath 'com.example.plugins:CustomPluginShare:1.0.0'
}

Gradle 点击同步,出现并执行 uploadArchives task。

至此在本地生成插件相关的文件,生成在项目同级目录下。

使用插件

1.IntelliJ 创建 Gradle 项目,勾选 Groovy,命名为 Project。

2.清空 build.gradle,如下配置。

// 调用依赖
apply plugin: 'com.example.plugins.customplugin'

buildscript {
    repositories {
        maven {
            // 配置仓库地址
            url uri('../repo')
        }
    }
    dependencies {
        // 配置依赖
        classpath 'com.example.plugins:CustomPluginShare:1.0.0'
    }
}

3.Gradle 一栏点击同步,Task 中出现 CustomPluginTask,点击执行即可。

Gradle Plugin Demo

Github 链接

[TOC]

你可能感兴趣的:(Gradle 核心 个人笔记)