欢迎你们加入QQ群一块儿讨论: 489873144(android格调小窝) 个人github地址:https://github.com/jeasonlzy javascript
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
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()
方法在调用的时候,能从全局取到这个属性。
ScriptBytecodeAdapter.setGroovyObjectProperty(var2, hello.class,
this , (String)
"name" );
可是这样仍是与咱们的想象有差距,name并无在成员位置,那如何才能才能让咱们定义的属性就生成在成员变量的位置呢?这时候须要@Field
注解,以下:
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中定义的函数,若是至少有一个参数,在调用的时候能够省略括号。若是某个函数没有参数,那就不能省略括号,不然会当成一个变量使用。
def func(String a){
println(a)
}
func
'hello'
在android项目中,好比build.gradle
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 clos1 = {
a , def b, int c =
2 ->
a + b + c
}
println clos1(
5 ,
3 )
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()
运行后获得以下结果:
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
中加入如下几行代码:
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中看见如下代码:
task hello << {
println
"hello world"
}
实际上他的原始调用含义是:
task ("hello" ) .leftShift (closure)
由于task重载了leftShift,因此可使用 << 操做符,这和c++的特性是同样的
6)Command Chains
这个特性不只能够省略函数调用中的括号,并且能够省略,连续函数调用中的. 点号, 好比 a(b).c(d) 这里a c是函数, b d是函数参数, 就能够缩写为a b c d。这个特性强大之处在于不只适用于单个参数类型函数,并且适用于多个参数类型的函数,当参数类型为闭包时一样适用。
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工程,他有以下结构:
├── 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的东西。
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,可是为何能够呢?
咱们须要在根目录下也添加一个build.gradle。这个build.gradle通常干得活是:配置其余子Project的。好比为子Project添加一些属性。这个build.gradle有没有都无所谓。
继续在根目录下添加一个名为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的构建脚本生命周期具有三大步,以下:
上图告诉咱们如下信息,
Gradle工做包含三个阶段:
首先是初始化阶段。对咱们前面的multi-project build而言,就是执行settings.gradle
Initiliazation phase的下一个阶段是Configration阶段。
Configration阶段的目标是解析每一个project中的build.gradle。好比multi-project build例子中, 解析每一个子目录中的build.gradle。在这两个阶段之间,咱们能够加一些定制化的Hook。这固然是经过 API来添加的,须要特别注意:每一个Project都会被解析。
Configuration阶段完了后,整个build的project以及内部的Task关系就肯定了。一个 Project包含不少Task,每一个Task之间有依赖关系。Configuration会创建一个有向图来描述Task之间的 依赖关系,是一个有向图,因此,咱们能够添加一个HOOK,即当Task关系图创建好后,执行一些操做.
最后一个阶段就是执行任务了。你在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对象:
建立一个Settings对象,
根据settings.gradle文件配置它
根据Settings对象中定义的工程的父子关系建立Project对象
执行每个工程的build.gradle文件配置上一步中建立的Project对像
其中有不少有用的方法:
void apply(Map<
String , ?> options);
void dependencies(Closure configureClosure);
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 .prop 1 =
"prop1"
ext
.prop 2 =
"prop2"
project
.ext {
prop3 =
"prop3"
prop4 =
"prop4"
}
ext {
prop5 =
"prop5"
prop6 =
"prop6"
}
println project
.ext .prop 1
println project
.ext .prop 2
println project
.prop 3
println project
.prop 4
println prop5
println prop6
3.经过plugin 添加的extension ,就是插件定义本身的特有扩展属性,每个extension经过一个和extension同名的只读属性访问
4.经过plugin 添加的属性。一个plugin 能够经过Project的Convention对象为project添加属性和方法。
5.project中的task,一个task对象能够经过project中的同名属性访问,可是它是只读的
6.当前project的父工程的extra 属性和convention 属性,是只读的
当获取或者设置这些属性的时候,按照上述的顺序依次寻找,若是都没找到,则抛出异常。
方法可见范围
Project对象自己
build.gradle文件中定义的方法
经过plugin 添加的extension,每一个extensions 均可以做为一个方法访问,它接受一个闭包或Action做为参数
经过plugin 添加的方法。一个plugin 能够经过Project的Convention对象为project添加属性和方法。
project中的task ,每个task 都会在当前project中存在一个接受一个闭包或者Action做为参数的方法,这个闭包会在task的configure(closure)方法中调用。
当前工程的父工程中的方法
当前工程的属性可见范围中全部的闭包属性均可以做为方法访问
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的语法为:
task <taskName> << {
}
这句话应该这么理解:
首先调用project的task方法,传入一个taskName,返回一个task
调用task的leftShift 方法 传入一个closure,根据leftShift的解释,咱们知道这个闭包将添加到task的action list里去,在任务执行的时候运行
有时候,咱们可能会错写成
task <taskName> {
}
少了这个<< 操做符,意思就彻底不同了,这个时候调用的函数为
Task
task (String name, Closure configureClosure);
这时,第二个参数closure用来配置task,在task建立的时候,也就是构建整个任务有向图的时候执行,而不是在task执行的时候运行。不过咱们能够在这个闭包内配置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的类型
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相关
├── app
│ └── build
.gradle
├── lib
│ └── build
.gradle
├── build
.gradle
└── settings
.gradle
root/settings.gradle
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
- - - - - - . - - - - - -
- >
. - - - -
- >
. - - - -
lib/build.gradle
- - - - - - . - - - - - -
在根目录下执行 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
从上述例子中咱们验证了如下结果:
若是一个task在build.gradle中定义,可是在构建中不会执行,那么它的Task对象会建立,可是不会在任务图中出现。
咱们能够经过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,最后使用的时候以下:
apply plugin:
'helloPlugin'
这个文件里面内容指明了插件类的全类名,以下:
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"
}
}
}
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
引入插件
buildscript
{ repositories{ mavenCentral() maven { url uri('./repo' ) }
}
dependencies
{ classpath 'com.lzy.plugin:helloPlugin:0.1.1' }
}
而后,在app工程下,咱们应用这个插件,
apply plugin:
'helloPlugin'
最后,在app或者根目录下执行,gradle sayHello
,这样就打印出了咱们插件中定义的文字。
以上就是一个自定义插件的建立和应用过程,虽然很简单,可是能够帮助咱们理解gradle是如何经过plugin完成不少复杂的工做的。
Extension
1)状况一:
有时候咱们但愿在使用插件的时候,额外配置一些参数,这时候就须要额外写一个Ext类,以下: PersonExt.groovy
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
}
}
}
}
最后咱们再使用插件的地方添加如下属性:
person {
name =
"abc"
age =
18
boy =
true
}
这里使用=号进行复制,实际上可加可不加,本质是利用了grooy特性,调用了setName方法,而且省略了方法调用的括号。
执行 gradle sayHello
获得以下结果:
: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
}
}
}
}
在使用插件的地方添加如下代码:
hobbies {
basketball {
level =
4
school =
"beijing"
}
football {
level =
6
school =
"qinghua"
}
}
执行 gradle sayHello
获得以下结果:
: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
获得以下结果:
: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
核心就是下面两个命令:
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
参数的描述
若是你以为好,对你有过帮助,请给我一点打赏鼓励吧,一分也是爱呀!