Groovy&Gradle总结

欢迎你们加入QQ群一块儿讨论: 489873144(android格调小窝)
个人github地址:https://github.com/jeasonlzyjavascript

0x01 Groovy 概述

Groovy 是一个基于 JVM 的语言,代码最终编译成字节码(bytecode),并在 JVM 上运行。它具备相似于 Java 的语法风格,可是语法又比 Java 要灵活和方便,同时具备动态语言(如 ruby 和 Python)的一些特性。html

正由于如此,因此Groovy适合用来定义DSL(Domain Specific Language)。java

简单的来说 DSL 是一个面向特定小领域的语言,如常见的 HTML、CSS 都是 DSL,它一般是以配置的方式进行编程,与之相对的是通用语言(General Purpose Language),如 Java 等。python

0x02 groovy 基本知识

1)首先须要安装groovy环境,具体的环境安装就不说了,网上不少,安装完成后配置环境变量,出现如下结果,即安装成功

2)groovy与java

由于Groovy是基于JVM的语言,因此咱们来看看最后生成的字节码文件。咱们写一个类:
hello.groovyandroid

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
name = "lzy" def say(){ "my name is $name" } println say()

在命令行输入groovy hello.groovy,运行脚本,输出如下结果:

上面的操做作完后,有什么感受和体会?
最大的感受可能就是groovy和shell脚本,或者python好相似。
另外,除了能够直接使用JDK以外,Groovy还有本身的一套GDK。 c++

咱们看一下编译成jvm字节码后的结果git

咱们输入groovyc -d classes hello.groovy命令将当前文件生成字节码文件,-d参数表示在classes文件夹下,最终结果以下:
hello.classgithub

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
import groovy.lang.Binding; import groovy.lang.Script; import org.codehaus.groovy.runtime.BytecodeInterface8; import org.codehaus.groovy.runtime.GStringImpl; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; import org.codehaus.groovy.runtime.callsite.CallSite; public class hello extends Script { public hello() { CallSite[] var1 = $getCallSiteArray(); } public hello(Binding context) { CallSite[] var2 = $getCallSiteArray(); super(context); } public static void main(String... args) { CallSite[] var1 = $getCallSiteArray(); var1[0].call(InvokerHelper.class, hello.class, args); } public Object run() { CallSite[] var1 = $getCallSiteArray(); String var2 = "lzy"; ScriptBytecodeAdapter.setGroovyObjectProperty(var2, hello.class, this, (String)"name"); return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()?var1[3].callCurrent(this, this.say()):var1[1].callCurrent(this, var1[2].callCurrent(this)); } public Object say() { CallSite[] var1 = $getCallSiteArray(); return new GStringImpl(new Object[]{var1[4].callGroovyObjectGetProperty(this)}, new String[]{"my name is ", ""}); } }

到这里咱们能够发现,其实groovy脚本本质就是java,他与java几乎没有区别,只是在java语言在语法上的扩展,支持DSL,加强了可读性。而且咱们得出如下结论:
1. hello.groovy被转换成了一个hello类,它从Script派生。
2. 每个脚本都会生成一个static main函数。这样,当咱们groovy hello.groovy去执行的时候,其实就是用java去执行了这个main函数
3. 脚本中的全部代码都会放到run函数中。好比,say()方法的调用,这句代码其实是包含在run()方法里的。
4. 若是脚本中定义了方法,则方法会被定义在hello类中。
5. 脚本中定义的变量是有它的做用域的,name = “lzy”,这句话是在run()中建立的。因此,name看起来好像是在整个脚本中定义的,但实际
上say()方法没法直接访问它。web

接着咱们把上述的hello.groovy文件修改,在定义name前的加上def修饰符,其他不作任何修改,咱们再次运行代码,发现如下错误:
shell

咱们将修改后的代码编译成class文件后,与以前的正常结果作对比,发现如下不一样:

左边是正确的,右边是错误的,相比下来就是多调用了一个方法,这个方法看起来就是将你定义的属性保存到了某个全局的环境中,确保下面的say()方法在调用的时候,能从全局取到这个属性。

  
  
  
  
  • 1
ScriptBytecodeAdapter.setGroovyObjectProperty(var2, hello.class, this, (String)"name");

可是这样仍是与咱们的想象有差距,name并无在成员位置,那如何才能才能让咱们定义的属性就生成在成员变量的位置呢?这时候须要@Field注解,以下:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
import groovy.transform.Field @Field name = "lzy" def say(){ "my name is $name" } println say()

加上这行注解后,生成的字节码以下:
name属性确实变成了成员变量,而且是在构造方法中被初始化了。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
import groovy.lang.Binding; import groovy.lang.Script; import org.codehaus.groovy.runtime.BytecodeInterface8; import org.codehaus.groovy.runtime.GStringImpl; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.callsite.CallSite; public class hello extends Script { Object name; public hello() { CallSite[] var1 = $getCallSiteArray(); String var2 = "lzy"; this.name = var2; } public hello(Binding context) { CallSite[] var2 = $getCallSiteArray(); super(context); String var3 = "lzy"; this.name = var3; } public static void main(String... args) { CallSite[] var1 = $getCallSiteArray(); var1[0].call(InvokerHelper.class, hello.class, args); } public Object run() { CallSite[] var1 = $getCallSiteArray(); Object var10000 = null; return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()?var1[3].callCurrent(this, this.say()):var1[1].callCurrent(this, var1[2].callCurrent(this)); } public Object say() { CallSite[] var1 = $getCallSiteArray(); return new GStringImpl(new Object[]{this.name}, new String[]{"my name is ", ""}); } }

0x03 Groovy 语法

这里只讲一些比较重要的特性,其他比较基本的语法比较简单,能够参考这里过一遍:
工匠若水的博客:Groovy脚本基础全攻略

1)方法的输入参数优化

groovy中定义的函数,若是至少有一个参数,在调用的时候能够省略括号。若是某个函数没有参数,那就不能省略括号,不然会当成一个变量使用。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
def func(String a){ println(a) } func 'hello'

在android项目中,好比build.gradle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
android { compileSdkVersion 25 buildToolsVersion "25.0.0" }

好比这里compileSdkVersion 和 buildToolsVersion 其实就是调用了一样名字的两个函数,在AndroidStudio里面能够点进去查看函数实现

2)闭包

闭包的概念也许咱们稍微陌生一点,可是实际上,咱们能够简单把它当作一个匿名类,只是编译器提供了更加简单的语法来实现它的功能。
闭包(Closure)是groovy中一个很重要的概念,并且在gradle中普遍使用。简而言之,闭包就是一个可执行的代码块,相似于C语言中的函数指针。在不少动态类型语言中都有普遍的使用,java8 中也有相似的概念:lambda expression,可是groovy中的闭包和java8中的lambda表达式相比又有不少的不一样之处。

咱们能够把闭包当作一个匿名内部类,只是编译器提供了更加简单的语法来实现它的功能。在Groovy中闭包也是对象,能够像方法同样传递参数,而且能够在须要的地方执行。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
def clos = { params -> println "Hello ${params}" } clos("World") //Closure类型的实例,好比上面的闭包咱们又能够定义为: //参数能够声明类型,也能够不声明,还能够有缺省值 Closure clos1 = { a, def b, int c = 2 -> a + b + c //默认返回值就是最后一行计算的结果,return关键字可省略 } println clos1(5, 3) //能够制定一个可选的返回类型 //若是闭包内没有生命任何参数,没有->, 那么闭包内置会定义一个隐含参数it Closure<String> clos2 = { println it return "clos2" }

闭包有三个很重要的属性分别是:this,owner,delegate,分别表明如下概念:

  • this: 对应于定义闭包时包含他的class,能够经过getThisObject或者直接this获取
  • owner: 对应于定义闭包时包含他的对象,能够经过getOwner或者直接owner获取
  • delegate: 闭包对象能够指定一个第三方对象做为其代理,用于函数调用或者属性的指定,能够经过getDelgate或者delegate属性获取

咱们编写以下代码:test1.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
class A { def closure1 = { println "--------------closure1--------------" println "this:" + this.class.name println "owner:" + owner.class.name println "delegate:" + delegate.class.name def closure2 = { println "-------------closure2---------------" println "this:" + this.class.name println "owner:" + owner.class.name println "delegate:" + delegate.class.name def closure3 = { println "-------------closure3---------------" println "this:" + this.class.name println "owner:" + owner.class.name println "delegate:" + delegate.class.name } closure3() } closure2() } } def a = new A() def closure1 = a.closure1 closure1()

运行后获得以下结果:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
--------------closure1-------------- this:com.lzy.A owner:com.lzy.A delegate:com.lzy.A -------------closure2--------------- this:com.lzy.A owner:com.lzy.A$_closure1 delegate:com.lzy.A$_closure1 -------------closure3--------------- this:com.lzy.A owner:com.lzy.A$_closure1$_closure2 delegate:com.lzy.A$_closure1$_closure2

3)代理策略

若是在闭包内,没有明确指定属性或者方法的调用是发生在this, owner,delegate上时,就须要根据代理策略来判断到底该发生在谁身上。有以下几种代理策略:

  • Closure.OWNER_FIRST 默认的策略,若是属性或者方法在owner中存在,调用就发生在owner身上,不然发生在delegate上
  • Closure.DELEGATE_FIRST 跟owner_first正好相反
  • Closure.OWNER_ONLY 忽略delegate
  • Closure.DELEGATE_ONLY 忽略owner
  • Closure.TO_SELF 调用不发生在owner或delegate上,只发生在闭包内

咱们编写以下代码:test2.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
import groovy.transform.Field @Field String name = "abc" class P { String name def pretty = { "my name is $name" } } class T { String name } def upper = { name.toUpperCase() } println upper() def p = new P(name: 'ppp') def t = new T(name: 'ttt') upper.delegate = t upper.resolveStrategy = Closure.DELEGATE_FIRST println upper() p.pretty.delegate = this println p.pretty() p.pretty.resolveStrategy = Closure.DELEGATE_FIRST println p.pretty()

运行后获得以下结果:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
ABC TTT my name is ppp my name is abc

这里重点强调一下,成员变量name必定要加上@Field注解,否者出现的结果必定不是上述结果,缘由以前已经分析过,不加这个注解,name属性将不会是成员变量

4)类的Property

Groovy中的class和java中的Class区别不大,值得咱们关注的区别是,若是类的成员变量没有加任何权限访问,则称为Property, 不然是Field,filed和Java中的成员变量相同,可是Property的话,它是一个private field和getter setter的集合,也就是说groovy会自动生成getter setter方法,所以在类外面的代码,都是会透明的调用getter和setter方法

咱们在上述的test1.groovy的类A中加入如下几行代码:

  
  
  
  
  • 1
  • 2
  • 3
String name = "aaa" public String name1 = "bbb" private String name2 = "ccc"

而后对这个test1.groovy进行编译groovyc -d classes test1.groovy,生成的文件结构以下:

点开A.class发现如下代码:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
public class A implements GroovyObject { private String name; public String name1; private String name2; private Object closure1; ··· public String getName() { return this.name; } public void setName(String var1) { this.name = var1; } ··· }

5)操做符重载

咱们常常在gradle中看见如下代码:

  
  
  
  
  • 1
  • 2
  • 3
task hello << { println "hello world" }

实际上他的原始调用含义是:

  
  
  
  
  • 1
task("hello").leftShift(closure)

由于task重载了leftShift,因此可使用 << 操做符,这和c++的特性是同样的

6)Command Chains

这个特性不只能够省略函数调用中的括号,并且能够省略,连续函数调用中的. 点号, 好比
a(b).c(d) 这里a c是函数, b d是函数参数, 就能够缩写为a b c d。这个特性强大之处在于不只适用于单个参数类型函数,并且适用于多个参数类型的函数,当参数类型为闭包时一样适用。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
task("task1").doLast({ println "111" }).doLast({ println("222") }) //简写 task task1 doLast { println "111" } doLast { println("222") }

7)DSL

借助闭包的特性,咱们能够尝试写一个简单的DSL。下面的代码展现了如何借助groovy的语法特性来实现一个DSL,这些特性咱们稍后会在gradle的脚本中看到。

test3.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
class Book { def _name = '' def _price = 0.0 def shop = [] def static config(config){ Book book = new Book(shop:['A','B']) config.delegate = book config() } def name(name){ this._name = name } def price(price){ this._price = price } def getDetail(){ println "name : ${_name}" println "price : ${_price}" println "shop : ${shop}" } } Book.config { name 'test' price 1.2 detail }

上面所提到的这些groovy的语法特性,构成了Gradle中DSL的基础

0x04 Gradle 基本概念

咱们在AndroidStudio中建立基于Gradle的project时,会默认生成一个多项目结构的Gradle工程,他有以下结构:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
├── app │ └── build.gradle ├── lib │ └── build.gradle ├── build.gradle └── settings.gradle

若是是单工程结构,这个Setting.gradle其实能够省略

Gradle中,每个待编译的工程,或者叫每个Library和App都是单独的Project。根据Gradle的要求,每个Project在其根目录下都须要有一个build.gradle。build.gradle文件就是该Project的编译脚本,相似于Makefile。每个Project在构建的时候都包含一系列的Task。好比一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。具体一个Project到底包含多少个Task,实际上是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
apply plugin: 'com.android.application' //app插件 apply plugin: 'com.android.library' //lib插件 android{ ... } dependencies{ .... }

若是咱们把以上代码在build.gradle中删掉,执行gradle tasks命令列出全部可执行的task,会发现不少task都不见了,由此咱们能得出结论,

这些task都是android application插件生成的。咱们能使用Gradle构建Android 工程,一切都基于这个插件。这个插件从android这个扩展中读取了咱们的配置,生成了一些列构建android 所须要的任务。

咱们执行gradle projects列出全部的工程:

这个图片的最后几句话也告诉了咱们,若是你在根目录下,想查看或者运行某个项目下的任务,能够用gradle :app:tasks这种语法,若是你cd到子项目的根目录下,是不须要加:app这样的前缀的。

对Android来讲,gradle assemble这个Task会生成最终的产物Apk,因此若是一个工程包含5个Model,那么须要分别编译这5个,他们可能还有一些依赖关系,这样就很麻烦了,而在Gradle中,是支持多工程编译的(Multi-Projects Build),咱们在根目录下直接执行gradle assemble,就能按照依赖关系把这5个Model所有编译出来生成最终的Apk,可是为何能够呢?

  1. 咱们须要在根目录下也添加一个build.gradle。这个build.gradle通常干得活是:配置其余子Project的。好比为子Project添加一些属性。这个build.gradle有没有都无所谓。

  2. 继续在根目录下添加一个名为settings.gradle。这个文件很重要,名字必须是settings.gradle。它里边用来告诉Gradle,这个multi-projects包含多少个子Project,内部通常就是一个include指令。根据groovy的语法,他就是在gradle生成的settings对象调用函数 include(‘app’),include接受的参数是一个string数组,所以include后能够加不少参数,这个函数的意义就是:指明那些子project参与此次gradle构建

因此对于一个工程,咱们能对构建过程作出改变的,就只能发生在这些.gradle文件中,这些文件称为Build Script构建脚本。对于Gradle中的构建脚本,一方面能够理解为配置文件,每一种类型脚本文件都是对某一种类型的构建对象进行配置。另外一方面也能够把每一个脚本理解为一个Groovy闭包,这样咱们在执行构建脚本时,就是在执行每个闭包函数,只不过每一个闭包所设置的delegate不同。

如下来自于文档:Gradle Build Language Reference这个文档很重要,后面会常用!!!

  • Project对象:每一个build.gradle会转换成一个Project对象,或者说代理对象就是Project。
  • Gradle对象:当咱们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程当中,只有这么一个对象。Gradle对象的数据类型就是Gradle。咱们通常不多去定制这个默认的配置脚本。
  • Settings对象:每一个settings.gradle会转换成一个Settings对象,或者说代理对象是Setting。

补充一点:Init Script其实就是配置gradle运行环境。彷佛历来没有使用过,可是在每一次构建开始以前,都会执行init script,咱们能够对当前的build作出一些全局配置,好比全局依赖,何处寻找插件等。有多个位置能够存放init script以下:
1. 经过在命令行里指定gradle参数 -I 或者–init-script

1)Build生命周期

Gradle的构建脚本生命周期具有三大步,以下:

上图告诉咱们如下信息,

  1. Gradle工做包含三个阶段:
  2. 首先是初始化阶段。对咱们前面的multi-project build而言,就是执行settings.gradle
  3. Initiliazation phase的下一个阶段是Configration阶段。
  4. Configration阶段的目标是解析每一个project中的build.gradle。好比multi-project build例子中,
    解析每一个子目录中的build.gradle。在这两个阶段之间,咱们能够加一些定制化的Hook。这固然是经过
    API来添加的,须要特别注意:每一个Project都会被解析。
  5. Configuration阶段完了后,整个build的project以及内部的Task关系就肯定了。一个
    Project包含不少Task,每一个Task之间有依赖关系。Configuration会创建一个有向图来描述Task之间的
    依赖关系,是一个有向图,因此,咱们能够添加一个HOOK,即当Task关系图创建好后,执行一些操做.
  6. 最后一个阶段就是执行任务了。你在gradle xxx中指定什么任务,gradle就会将这个xxx任务链上的全部任务所有按依赖顺序执行一遍!固然,任务执行完后,咱们还能够加Hook。

gradle具备如下几个经常使用的命令:
gradle tasks //列出全部的任务
gradle projects //列出全部的项目
gradle properties //列出全部的属性

2)Setting对象

先看文档,方法文档都在文档中:Settings
其中有这么句话比较重要:

In addition to the properties of this interface, the Settings object makes some additional read-only properties available to the settings script. This includes properties from the following sources:

  • Defined in the gradle.properties file located in the settings directory of the build.
  • Defined the gradle.properties file located in the user’s .gradle directory.
  • Provided on the command-line using the -P option.

翻译后就是,除了Setting这个接口本身提供的属性方法外,你还能够在如下位置添加本身的额外属性:
- setting.gradle 平级目录下的 gradle.properties 文件
- 用户.gradle目录下的 gradle.properties 文件
- 使用命令行 -P 属性

其他的文档中比较详细。

3)Project对象

如下内容均来自与文档:Project

每个build.gradle文件和一个Project对象一一对应,在执行构建的时候,gradle经过如下方式为每个工程建立一个Project对象:

  1. 建立一个Settings对象,
  2. 根据settings.gradle文件配置它
  3. 根据Settings对象中定义的工程的父子关系建立Project对象
  4. 执行每个工程的build.gradle文件配置上一步中建立的Project对像

其中有不少有用的方法:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//apply一个插件或者脚本 void apply(Map<String, ?> options); //配置当前project的依赖 void dependencies(Closure configureClosure); //配置当前脚本的classpath void buildscript(Closure configureClosure);

在build.gradle文件中定义的属性和方法会委托给Project对象执行,每个project对象在寻找一个属性的时候有5个做用域做为范围,分别是:

属性可见范围

  • 1.Project 自己
  • 2.Project的ext属性
  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
project.ext.prop1 = "prop1" ext.prop2 = "prop2" project.ext { prop3 = "prop3" prop4 = "prop4" } ext { prop5 = "prop5" prop6 = "prop6" } println project.ext.prop1 println project.ext.prop2 println project.prop3 println project.prop4 println prop5 println prop6
  • 3.经过plugin添加的extension,就是插件定义本身的特有扩展属性,每个extension经过一个和extension同名的只读属性访问
  • 4.经过plugin添加的属性。一个plugin能够经过Project的Convention对象为project添加属性和方法。
  • 5.project中的task,一个task对象能够经过project中的同名属性访问,可是它是只读的
  • 6.当前project的父工程的extra属性和convention属性,是只读的

当获取或者设置这些属性的时候,按照上述的顺序依次寻找,若是都没找到,则抛出异常。

方法可见范围

  1. Project对象自己
  2. build.gradle文件中定义的方法
  3. 经过plugin添加的extension,每一个extensions均可以做为一个方法访问,它接受一个闭包或Action做为参数
  4. 经过plugin添加的方法。一个plugin能够经过Project的Convention对象为project添加属性和方法。
  5. project中的task,每个task 都会在当前project中存在一个接受一个闭包或者Action做为参数的方法,这个闭包会在task的configure(closure)方法中调用。
  6. 当前工程的父工程中的方法
  7. 当前工程的属性可见范围中全部的闭包属性均可以做为方法访问

4) Task对象

先来文档 Task

A Task is made up of a sequence of Action objects. When the task is executed, each of the actions is executed in turn, by calling Action.execute(T). You can add actions to a task by calling Task.doFirst(org.gradle.api.Action) or Task.doLast(org.gradle.api.Action).

Groovy closures can also be used to provide a task action. When the action is executed, the closure is called with the task as parameter. You can add action closures to a task by calling Task.doFirst(groovy.lang.Closure) or Task.doLast(groovy.lang.Closure).

There are 2 special exceptions which a task action can throw to abort execution and continue without failing the build. A task action can abort execution of the action and continue to the next action of the task by throwing a StopActionException. A task action can abort execution of the task and continue to the next task by throwing a StopExecutionException. Using these exceptions allows you to have precondition actions which skip execution of the task, or part of the task, if not true.

建立一个简单的task的语法为:

  
  
  
  
  • 1
  • 2
task <taskName> << { }

这句话应该这么理解:

  1. 首先调用project的task方法,传入一个taskName,返回一个task
  2. 调用task的leftShift 方法 传入一个closure,根据leftShift的解释,咱们知道这个闭包将添加到task的action list里去,在任务执行的时候运行

有时候,咱们可能会错写成

  
  
  
  
  • 1
  • 2
task <taskName> { }

少了这个<< 操做符,意思就彻底不同了,这个时候调用的函数为

  
  
  
  
  • 1
  • 2
//Project类 Task task(String name, Closure configureClosure);

这时,第二个参数closure用来配置task,在task建立的时候,也就是构建整个任务有向图的时候执行,而不是在task执行的时候运行。不过咱们能够在这个闭包内配置task的一些属性。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
//指定Copy类型task的属性 task copyDocs(type: Copy) { from 'src/main/doc' into 'build/target/doc' }

固然咱们还能够这样指定task的行为:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
task exampleTask { doLast{ } } task exampleTask doLast{ } task exampleTask << { }

还能够指定task的类型

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
task name(type: Type){ doLast{ } }

gradle内置为咱们生成了不少task类型,好比Copy,Delete,能够点击连接 查看gradle内置的task类型列表,若是建立task时没有指定type,则他默认是DefaultTask类型。咱们还能够建立本身的task类型,咱们在稍后就会讲到。

咱们还能够能够指定task之间的依赖关系, 经过dependsOn, mustRunAfter, shouldRunAfter来指定。 还能够指定task的分组group, 若是不指定,将会出如今other里面。

5) 构建的生命周期测试

如下各个方法参考文档:
Gradle相关
Task相关

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
├── app │ └── build.gradle ├── lib │ └── build.gradle ├── build.gradle └── settings.gradle

root/settings.gradle

  
  
  
  
  • 1
  • 2
  • 3
println '------setting.gradle execute------' include ':app', ':lib', ':helloPlugin', ':simplePlugin'

root/build.gradle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
println '------root build.gradle execute------' apply from: uri('./build_1.gradle') apply from: uri('./build_2.gradle') println "all project size : ${allprojects.size()}" gradle.settingsEvaluated { settings -> println "settingsEvaluated" } gradle.projectsLoaded { gradle -> println "projectsLoaded" } gradle.beforeProject { project -> println "beforeProject: ${project.name} " } gradle.afterProject { project -> println "afterProject: ${project.name}" } gradle.projectsEvaluated { gradle -> println "projectsEvaluated" } gradle.buildFinished { buildResult -> println "buildFinished" } gradle.taskGraph.whenReady { graph -> println "============task graph is ready============" graph.getAllTasks().each { println "task ${it.name} will execute" } println "============task graph is over=============" } gradle.taskGraph.beforeTask { task -> println "before ${task.name} execute" } gradle.taskGraph.afterTask { task -> println "after ${task.name} execute" } tasks.whenTaskAdded { task -> println "taskAdded:" + task.name } task subTask1 { group "hello" doLast { println "${name} execute" } } task subTask2(dependsOn: 'subTask1') { group "hello" doLast { println "${name} execute" } }

app/build.gradle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
println '------app build.gradle execute------' beforeEvaluate { project -> println "beforeEvaluate: ${project.name} --in--" } afterEvaluate { project -> println "beforeEvaluate: ${project.name} --in--" }

lib/build.gradle

  
  
  
  
  • 1
println '------lib build.gradle execute------'

在根目录下执行 gradle libTask2

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
------setting.gradle execute------ ------root build.gradle execute------ all project size : 5 taskAdded:subTask1 taskAdded:subTask2 afterProject: GradlePlugin afterEvaluate: GradlePlugin beforeProject: app beforeEvaluate: app ------app build.gradle execute------ afterProject: app afterEvaluate: app Incremental java compilation is an incubating feature. beforeEvaluate: app --in-- beforeProject: lib beforeEvaluate: lib ------lib build.gradle execute------ afterProject: lib afterEvaluate: lib projectsEvaluated ============task graph is ready============ task subTask1 will execute task subTask2 will execute ============task graph is over============= :subTask1 before subTask1 execute subTask1 execute after subTask1 execute :subTask2 before subTask2 execute subTask2 execute after subTask2 execute

从上述例子中咱们验证了如下结果:

  1. 若是一个task在build.gradle中定义,可是在构建中不会执行,那么它的Task对象会建立,可是不会在任务图中出现。
  2. 咱们能够经过Gradle或者Project对象中定义的方法获取生命周期中每个过程在执行中的回调。这里注意一下,咱们定义的一些回调在实际执行中彷佛并无被触发,例如,settingsEvaluated,projectsLoaded。具体缘由须要细看。

0x05 自定义一个插件

首先看一下工程结构

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
├── app //root工程 ├── repo //本地maven目录 ├── helloPlugin //plugin工程 │ ├── build.gradle │ └── src │ └── main │ ├── groovy │ │ └── com.lzy.plugin │ │ ├── HelloPlugin.groovy │ │ ├── Person.groovy │ │ └── PersonExt.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── helloPlugin.properties //插件名 │ ├── build.gradle └── settings.gradle

首先,插件工程能够用任意的jvm语言编写,例如,scala,groovy,java等,最终每个插件都会打包成一个jar包,其中META-INF文件下中每个.properties文件表明一个Plugin,最后使用的时候以下:

  
  
  
  
  • 1
apply plugin: 'helloPlugin'

这个文件里面内容指明了插件类的全类名,以下:

  
  
  
  
  • 1
implementation-class=com.lzy.plugin.HelloPlugin

HelloPlugin.groovy:很简单,就定义一个任务,打印一个字符串

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
package com.lzy.plugin import org.gradle.api.Plugin import org.gradle.api.Project class HelloPlugin implements Plugin<Project> { public void apply(Project project) { project.task("sayHello") { group "hello" doLast { println "Hello Plugin" } } } }

首先咱们必须说明的是,插件能够以三种形式存在:
1. 在咱们构建项目的build.gradle脚本中直接编写
2. 在咱们构建项目的rootProjectDir/buildSrc/src/main/groovy 目录下
3. 以单独的project存在

这里采用第三种方式:在插件目录下编写

这种编写方式只能发布到本地
build.gradle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
apply plugin: 'groovy' apply plugin: 'maven' version = '0.1.1' group = 'com.lzy.plugin' repositories { mavenCentral() } dependencies { compile gradleApi() compile localGroovy() } uploadArchives { repositories.mavenDeployer { repository(url: 'file:../repo') } }

有时咱们更想开源出去给其余人用,像Small这样,咱们就能够这么写
build.gradle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
apply plugin: 'maven-publish' publishing { publications { mavenJava(MavenPublication) { from components.java //这两行是发布源码和文档的,能够不发布 artifact sourcesJar artifact javadocJar groupId 'com.lzy.plugin' artifactId 'helloPlugin' version '0.1.1' } } repositories { maven { url "../repo" } } } //默认打包时只会包含编译过的jar包,咱们能够增长如下两个task,将源代码和javadoc打包发布,并经过上述artifact指定: task javadocJar(type: Jar, dependsOn: groovydoc) { classifier = 'javadoc' from "${buildDir}/javadoc" } task sourcesJar(type: Jar) { from sourceSets.main.allSource classifier = 'sources' }

groupId、artifactId、version,这三者组成了插件使用者在声明依赖时的完整语句 groupId:artifactId:version

对于第一种方式:在helloPlugin的根目录下执行,gralde uploadArchives,编译插件工程,并发布到../repo目录。

对于第二种方式:有两种publish任务,publish 和 publishToMavenLocal,
- publish:任务依赖于全部的mavenPublication的generatePomFileFor任务和publishxxxPublicationToMavenRepository,意思是将全部的mavenPublication发布到指定的repository,
- publishToMavenLocal依赖于全部的mavenPublication的generatePomFileFor和publishxxxTomavenLocal任务,意思是将全部的mavenPublication发布到本地的m2 repository。

以上,咱们就建立好了一个gradle plugin,那么如何使用它呢?

首先,在root工程下的build.gradle中,咱们经过buildscript引入插件

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
buildscript{ repositories{ mavenCentral() maven { url uri('./repo') } } dependencies { classpath 'com.lzy.plugin:helloPlugin:0.1.1' } }

而后,在app工程下,咱们应用这个插件,

  
  
  
  
  • 1
apply plugin: 'helloPlugin'

最后,在app或者根目录下执行,gradle sayHello,这样就打印出了咱们插件中定义的文字。

以上就是一个自定义插件的建立和应用过程,虽然很简单,可是能够帮助咱们理解gradle是如何经过plugin完成不少复杂的工做的。

Extension

1)状况一:

有时候咱们但愿在使用插件的时候,额外配置一些参数,这时候就须要额外写一个Ext类,以下:
PersonExt.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
public class PersonExt { String name int age boolean boy @Override public String toString() { return "I am $name, $age years old, " + (boy ? "I am a boy" : "I am a girl") } }

接着修改
HelloPlugin.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
class HelloPlugin implements Plugin<Project> { public void apply(Project project) { project.extensions.add("person", PersonExt) project.task("sayHello") { group "hello" doLast { //如下两种方式均可以 def personExt = project.person def personExt1 = project.extensions.getByName('person') println personExt println personExt1 } } } }

最后咱们再使用插件的地方添加如下属性:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
person { name = "abc" age = 18 boy = true }

这里使用=号进行复制,实际上可加可不加,本质是利用了grooy特性,调用了setName方法,而且省略了方法调用的括号。

执行 gradle sayHello获得以下结果:

  
  
  
  
  • 1
  • 2
  • 3
:sayHello I am abc, 18 years old, I am a boy I am abc, 18 years old, I am a boy

到这里说明咱们定义的Extension正确设置并读取成功了。

2)状况二

若是咱们但愿设置的Extension是一个集合列表,而且该列表长度未知,又该怎么写呢?

咱们须要使用NamedDomainObjectContainer,咱们后面都简称NDOC 这是一个容纳object的容器,它的特色是它的内部使用SortedSet实现的,内部对象的name是unique的,并且是按name进行排序的。一般建立NDOC的方法就是调用Project里的方法:

这里type有一个要求:必须有一个public的构造函数,接受string做为一个参数,必须有一个叫作name 的property。

新增一个类:
HobbyExt.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
public class HobbyExt { String name int level String school HobbyExt(name) { this.name = name; } @Override public String toString() { return "My hobby is $name, level $level, School $school" } }

修改HelloPlugin.groovy代码以下:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
class HelloPlugin implements Plugin<Project> { public void apply(Project project) { NamedDomainObjectContainer<HobbyExt> hobbies = project.container(HobbyExt.class) project.extensions.add('hobbies', hobbies) project.task("sayHello") { group "hello" doLast { def hobbiesExt = project.hobbies def hobbiesExt1 = project.extensions.getByName('hobbies') println hobbiesExt println hobbiesExt1 } } } }

在使用插件的地方添加如下代码:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
hobbies { basketball { level = 4 school = "beijing" } football { level = 6 school = "qinghua" } }

执行 gradle sayHello获得以下结果:

  
  
  
  
  • 1
  • 2
  • 3
:sayHello [My hobby is basketball, level 4, School beijing, My hobby is football, level 6, School qinghua] [My hobby is basketball, level 4, School beijing, My hobby is football, level 6, School qinghua]

到这里说明咱们定义的Extension正确设置并读取成功了。

3)状况三

咱们也能够混合列表和单个属性,就像android{…}同样
新建一个类
Team.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
public class TeamExt { NamedDomainObjectContainer<HobbyExt> hobbies String name int count public TeamExt(NamedDomainObjectContainer<HobbyExt> hobbies) { this.hobbies = hobbies } def hobbies(Closure closure) { hobbies.configure(closure) } String toString() { "this is a team, name: $name, count $count, hobbies: $hobbies" } }

这里的hobbies(closure)函数是必须的,只有实现了这个函数,Gradle在解析team的extension,遇到hobbies配置时,才能经过调用函数,调用 NamedDomainObjectContainer的configure方法,往里面添加对象。
接着咱们修改
HelloPlugin.groovy

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
class HelloPlugin implements Plugin<Project> { public void apply(Project project) { NamedDomainObjectContainer<HobbyExt> hobbyExt = project.container(HobbyExt) def team = new TeamExt(hobbyExt) project.extensions.add("team", team) project.task("sayHello") { group "hello" doLast { def teamExt = project.team def teamExt1 = project.extensions.getByName('team') println teamExt println teamExt1 } } } }

在使用插件的地方添加如下代码:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
team { name = "android" count = 10 hobbies { basketball { level = 4 school = "beijing" } football { level = 6 school = "qinghua" } } }

执行 gradle sayHello获得以下结果:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
:sayHello this is a team, name: android, count 10, hobbies: [My hobby is basketball, level 4, School beijing, My hobby is football, level 6, School qinghua] this is a team, name: android, count 10, hobbies: [My hobby is basketball, level 4, School beijing, My hobby is football, level 6, School qinghua]

到这里说明咱们定义的Extension正确设置并读取成功了。

以上的写法是否是特别像build.gradle文件中的android标签,他的内部能够配置不少属性,原理都和这个同样。

到这里,咱们就经过一个简单的例子就熟悉了Gradle插件的编写规则,并且经过对groovy语法的了解,让咱们对gradle的DSL再也不陌生。

0x06 Groovy和Gradle的调试

主要参考 Small 中 Debug gradle on android studio

核心就是下面两个命令:

  
  
  
  
  • 1
  • 2
  • 3
export GRADLE_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" ./gradlew someTask -Dorg.gradle.daemon=false

其中有如下几个注意事项:

- 对于项目根目录下的gradle.properties文件须要作修改,必定要将org.gradle.jvmargs这个参数给注释掉,缘由是上述在配置remote调试参数的时候已经配置了jvmargs,若是此时配置文件中仍然配置,会致使remote失效,从而不能监听端口
- 在执行./gradlew someTask -Dorg.gradle.daemon=false这行命令时,为了方即可以省略后面的-D参数,改成在配置文件中增长上述配置。这是-D参数的描述

若是你以为好,对你有过帮助,请给我一点打赏鼓励吧,一分也是爱呀!