Gradle 构建学习(三)---------详解 Plugin与Groovy闭包

356 阅读6分钟

sourceset

很多人可能会觉得奇怪,为什么gradle知道去 src main java 这个路径下去找源码编译,去res路径下找资源编译。其实这些东西都是在sourceset里面默认设置好的, 那么我们怎么知道这个sourceSet有哪些配置是可以修改的呢?当然只有去看源码了。因为我们是android工程 所以我们只要看android的sourceset就可以了。

其实看看这个结构也就知道的差不多了。

再看看 我们一般的资源目录,有的时候我们会觉得这样的资源目录是不够的,为啥?当你的项目有了规模以后 所有模块的资源文件都写在一起,找起来真的很难找。 但是有了sourceset 来优化这个东西就变的简单了

我们新建2个文件夹 一个叫shop 一个叫forum 可以看出来 他们的图标和上面的res的图标是不同的,因为此时gradle还是仅仅将他们当作一个普通文件夹 而不是当作一个资源文件夹。 下面我们就要修改这个配置了。

 sourceSets {
        main {
            resources.srcDirs(['src/main/res', 'src/main/res-forum', 'src/main/res-shop'])
        }
    }

然后再看一下:

已经可以看到 我们新建的文件夹 已经被识别成是res的路径了。 有了他 我们就可以将我们的资源文件 也像java源代码一样 有了类似于package的效果。

plugin

当我们像把我们写的gradle 代码打包给别人使用的时候 就需要plugin了,其实这个plugin你就理解成是一个特殊的jar包就行。 就跟我们写sdk 是一样的,

基本目录结构就是这样 你甚至可以直接写java 代码 ,比groovy 更容易上手一些。

plugin 如何与使用者通信

可以定义一个实体类 用于接收参数

package com.myplugin;

public class MyExtension {
    public String getVersionName() {
        return versionName;
    }

    public void setVersionName(String versionName) {
        this.versionName = versionName;
    }

    public String getVersionCode() {
        return versionCode;
    }

    public void setVersionCode(String versionCode) {
        this.versionCode = versionCode;
    }

    private String versionName;
    private String versionCode;

    @Override
    public String toString() {
        return "MyExtension{" +
                "versionName='" + versionName + '\'' +
                ", versionCode='" + versionCode + '\'' +
                '}';
    }
}

然后定义一个task 用于打印我们接受到的参数

package com.myplugin;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;

public class MyTask extends DefaultTask {
    public MyTask() {
        setGroup("other2");
    }

    @TaskAction
    private void doSth() {
        System.out.println("info:" + getProject().getExtensions().findByName("pluginExtension").toString());
    }
}

最后不要忘记去plugin中注册我们的扩展和task

package com.myplugin;

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

public class MyGradlePlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        project.getExtensions().create("pluginExtension", MyExtension.class);
        project.getTasks().create("asdasda", MyTask.class);
    }
}

处理好这些之后 我们点一下sync 同步,就可以在右边的列表看到我们的task了

看下使用的时候怎么传值: 去我们的app project下 使用一下这个plugin

最后看下执行结果

groovy 基础

groovy的闭包 还是需要好好理解的,这对你一开始学习gradle很有帮助,虽然本质上来说 你用java也可以写gradle的plugin,但是 github上大部分的 gradle plugin 使用的 主要语言还是groovy,比如tinker, 比如matrix 比如arouter 等等。

闭包的基本使用

static void main(String[] args) {
    def clouser = { println("hello world") }
    //下面这2种方法都一样
    clouser.call()
    //这个方法 更加简洁
    clouser()

    //如何设置闭包的参数
    def c2 = { String name, int age ->
        println("hello ${name} ,you are ${age}")
    }
    c2("wuyue", 18)

    // 闭包的默认参数 就是it
    def c3 = {
        println("hello ${it}")
    }
    c3("burning")

    // 闭包的 最后一行 一般就是闭包的返回值
    // 甚至可以省略一个return 关键字
    def c4 = {
        "hello ${it}"
    }
    println(c4("xxx"))


}

来看下闭包的具体应用,其实就是理解好 闭包的本质就是将一个函数 作为一个参数传递到另外一个函数里面。理解到这个程度就可以了。

比如说这里有个求阶乘的写法

//求阶乘,我们一眼就看出来 这个求阶乘的写法 少了一个循环
int fab(int number) {
    int result = 1
    1.upto(number, { num -> result = result * num })
    return result
}

我们可以到这个upto方法里面看看

第三个参数 就是闭包了, 但是在函数体内部,我们可以发现 这个闭包 被循环调用了。 调用的次数 就是 阶乘的 1~5

所以大家就把闭包 理解成一个 特殊的函数即可,这个函数是可以作为参数 传递到另外一个函数里面的。

大家在学习groovy 或者看其他gradle plugin 源码的时候 有发现 闭包调用的时候 闭包参数不知道怎么传的,只要点到方法里面 看一眼,就知道 这个闭包 需要什么类型的参数,需要几个参数了。

再看一个例子,找出字符串里面 第一个是数字类型的字符

  println("wuyue2".find {
        it.isNumber()
    })

知道这段代码的意思并不难,难的是你要怎么才能理解 find这个函数 传进去的闭包 为什么要这么写?一定学会看闭包的参数,否则 gradle 你是肯定学不好的。

看这个图,里面是个dowhile循环, while 里面调用的 恰好就是我们的闭包,前面我们说过闭包是一个函数, 这里的while循环要成立, 首先就得保证 这个闭包的函数 ** 返回值 必须是一个 布尔值,** 其次,这个闭包的参数 在find函数里面自动帮我们传递了, 我们可以看出来他的参数的值 其实就是这个调用者字符串的 每一个字符。

groovy 闭包进阶--闭包的委托策略

首先 我们要搞清楚 this owner 和 delegate 的 含义

从上图可以看出, 这三个东西 都是一个值。 那groovy 为啥要搞三个东西?

this:代表闭包定义处的类 这个很好理 我们根据上图可以知道 我们定义这个c 闭包 所处的类 就是ClouTest类 owner:代表闭包定义处的类 或者 闭包定义处的对象 delegate 代表任意对象

多数情况下,这三者的作用 就是一致的。 但是某些场景下, 这三者并不一样。 我们继续来看几个例子

我们在 ClosureTest 里面定义一个内部类

class Person {
    def static c1 = {
        println(this)
        println(owner)
        println(delegate)
    }

    def static s() {
        def c2 = {
            println(this)
            println(owner)
            println(delegate)
        }
        c2()
    }
}

然后看一下 执行结果

你会发现 他指向的就是这个内部类了,不是内部类的对象喔, 因为上面都是static 方法。

我们去掉static 关键字以后 再看一次执行结果,这里所指向的 就是具体的对象了。

接着我们定义 一个闭包中的闭包 并看看他的执行结果

执行结果可以看出一个结论: 闭包中定义一个闭包的时候 owner和delegate 都会指向 最近的闭包。而this永远指向 闭包所处的类

那deleagate 什么时候与owner 不一致呢? 我们下面看一个例子就能明白了

class Student {
    String name

    Student(String name) {
        this.name = name
    }
    def pretty = { "my name is ${name}" }

    @Override
    String toString() {
        pretty()
    }
}

class Teacher {
    Teacher(String name) {
        this.name = name
    }
    String name
}

def stu = new Student("wuyue")
def tea = new Teacher("android")
println(stu.toString())

这段代码 大家相信都能看的出来执行结果 是

那如果 此时我们希望 stu 的输出结果 是 my name is android 怎么办?

其实只要稍微修改一下即可:

我们手动将stu的pretty的闭包 设置一下代理对象为 tea ,然后设置闭包的策略为 代理优先即可。

这个delegate的闭包用法 在很多gradle plugin的开源代码中都能碰到,所以这里单独拎出来讲一下。其实本质上就是个代理模式, 和kotlin 中的by 关键字 有一些类似。有兴趣的同学 可以再自己多写几个例子好好体会一下。