在之前Android组件化设计的模块中,曾经简单介绍过Gradle的基本配置Android组件化设计1 — Gradle的神奇之处,如果想要深入Gradle的学习,这些知识显然是不够的,那么从这个模块开始,从Grovy语法开始,深入理解Gradle的神奇之处
Groovy的认识1 Groovy简介2 Groovy语法
2.1 基本数据类型2.2 循环2.3 String2.4 List2.5 映射 3 Grovy中的类
3.1 访问修饰符3.2 构造方法3.3 方法的重载 4 闭包Closure
4.1 Java匿名内部类4.2 闭包4.3 向闭包传递参数4.4 闭包委托
1 Groovy简介Groovy是支持Gradle脚本编写的语言之一,Kotlin同样也可以编写Gradle脚本,因此如果对Kotlin比较熟悉,那么Groovy的语法就很简单了,而且作为动态语言,没有太多的限制,因此写Gradle脚本的人,一般”都有问题“。
Groovy是基于JVM的语言,能够跟Java语言做很好的结合,也支持扩展;Kotlin也是基于JVM的语言,因此Groovy支持面向对象编程,同时支持静态和动态语言
2 Groovy语法 2.1 基本数据类型对于所有语言的基本数据类型来说,无非就是那些,int、float、double等等,而对于动态类型的语言来说,不需要明确这个变量的类型,Groovy就可以推断出来这个变量的类型,而且还可以重新赋值新的类型
def age = 12
print("type ${age.metaClass}")
groovy.lang.metaClassImpl@1d9b7cce[class java.lang.Integer]
例如age,我并没有声明这个是一个Int类型,但是Groovy推断出这个是Int类型,而且是自动装箱
def age = 12
println("type ${age.metaClass}")
age = "12岁"
println("type ${age.metaClass}")
[class java.lang.Integer]
[class java.lang.String]
这就是动态语言的灵活之处,Java中肯定不能这么干,如果是个Int类型,给赋值String类型铁定报错
def对标kotlin中的var,那么final则对标kotlin中的val,不能被重新赋值
final age = 12 age = "123" println(age)
这样在编译时虽然没有报错,但是在运行时会报错,属于运行时常量
The variable [age] is declared final but is reassigned
如果定义了一个Int类型的变量,那么能重新赋值为String类型吗?不可以!这样***也从侧面说明了Groovy是强类型语言***
int year = 123 year = "123" //不允许这样的语法出现2.2 循环
如果熟悉Kotlin的话,循环也就不在话下,很简单,通过range就可以实现
for (i in 0..<5) {
println(i)
}
这里的运算符,跟Kotlin稍微有些不同,例如 < 对应在kotlin中 util ,不包括当前的数字
2.3 String字符串在Gradle中常见,类似于配置的工程依赖,就是单引号,其实单引号并不是字符串,而是char类型
implement ’com.github.xxxx:1.1.0‘
而双引号才是真正的字符串,而相比于单引号 ’‘ ,双引号可以插值,类似于Kotlin中的插值
def str = '你好' def str2 = "$str 中国!" 你好 中国!
有三引号吗?有的!三引号主要是用来换行用的,不能插值
def str = '''
你好中国
我的母亲
我最爱的祖国
'''
println(str)
2.4 List
Groovy中的集合,如果写代码可能没法体现,截个图
声明了一个集合,看着像个数组,但是编译器已经推断出,这个是一个Int类型的数组
集合的遍历跟Kotlin一致
list.forEach{value->
println(value)
}
list.indices.forEach{index->
println("当前第$index 个元素是 ${list.get(index)}")
}
Groovy数组中可以是混合元素,这也体现出了Groovy语法的灵活性,Java是禁止这么干的
2.5 映射Groovy中的映射,其实就是map
def map = [:]
这样就定义了一个map,其中key和value随便放,没有任何的限制
def map = [:]
map.a = "123"
map.b = "456"
map.forEach{ key,value->
println("$key 对应的值为 $value")
}
a 对应的值为 123
b 对应的值为 456
map的初始化赋值,可以直接放在map中,也可以跟示例中的方式动态添加
3 Grovy中的类class Car{
def year
def quality
}
先创建一个类,在Groovy类中,参数类型可以直接设置为def自动推断
def car = new Car(year: 2000) println(car.getYear())
看一下Groovy中的Car在编译过后,生成的Java类,其中是默认给生成了对应的get和set方法,可以直接去获取到数据
public class Car implements GroovyObject {
private Object year;
private Object quality;
@Generated
public Car() {
metaClass var1 = this.$getStaticmetaClass();
this.metaClass = var1;
}
@Generated
@Internal
@Transient
public metaClass getmetaClass() {
metaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticmetaClass();
return this.metaClass;
}
}
@Generated
@Internal
public void setmetaClass(metaClass var1) {
this.metaClass = var1;
}
@Generated
public Object getYear() {
return this.year;
}
@Generated
public void setYear(Object var1) {
this.year = var1;
}
@Generated
public Object getQuality() {
return this.quality;
}
@Generated
public void setQuality(Object var1) {
this.quality = var1;
}
}
3.1 访问修饰符
在Car类的构造方法中,默认属性都是public可访问的,但是如果把year这个字段设置为private
class Car{
private def year
def quality
}
首先在Car的Java类中,Car的year字段的get和set方法已经没有了,意味着不能通过get和set方法操作year,但是可以通过以下的形式直接访问
def car = new Car(year: 2000) println(car.year) //类似于Java中的反射,拿到属性值 println(car.'year')3.2 构造方法
如果不创建构造方法,那么就默认使用空参的构造方法,可以通过具名参数的方式,给属性赋值,类似于示例中方式,相当于传入了一个map
class Car{
def year
def quality
Car(year){
this.year = year
}
Car(){}
}
如果创建一个带参数的构造函数,那么在获取某个参数值的时候,会先走带参构造函数,然后再走无参构造函数
def car = new Car(year: 2000 , quality: 80) println(car.year) [year:2000, quality:80]
这个时候,如果获取year,那么拿到的就是一个map对象
Car(year,quality){
this.year = year
this.quality = quality
}
如果构造函数中,传入的参数大于构造函数中参数的个数,对于相同属性的参数,会实现组合
def car = new Car(year: 2000 , quality: 80,age : 12,12) println(car.year) [year:2000, quality:80, age:12]3.3 方法的重载
class Car{
def year
def quality
void setYear(int year){
println("set year int")
}
void setYear(String year){
println("set year String")
}
void setYear(Object year){
println("set year String")
}
Car(){}
}
对于setYear有3个重载方法,分别代表传入的3种数据类型,那么看下下面的调用方式
def car = new Car() Object year = "234" car.setYear(year)
虽然在创建year对象的时候,是命名了Object类型,但是在运行时,Groovy推断出year是String类型,所以最终调用的是setYear(String year)这个方法,而不是setYear(Object year),这也是动态语言的一个特性;
但是如果推算出当前数据类型是Boolean类型,但是没有对应的重载方法,那么就执行Object参数的重载方法
4 闭包Closure闭包是Closure中最重要的一个概念,在gradle中,闭包处处可见,那么闭包到底是什么?它的出现到底给我们带来了什么好处
4.1 Java匿名内部类在Android开发中,如果使用Java写一个监听事件,通常需要写一个接口,然后创建一个方法,用来设置接口,然后使用的时候,创建一个匿名内部类使用
interface OnClickListener{
void action()
}
void setOnClickListener(onClickListener listener){
listener.action()
}
setOnClickListener(new OnClickListener(){
@Override
void action() {
//do something
}
})
这种方式的弊端在于,接口中就1个方法,在使用的时候还是需要创建一个匿名内部类,非常的冗长,那么有没有什么办法能够针对这种单方法的接口,做一些优化处理
4.2 闭包背景我们已经知道了,那么闭包就是用来解决这种,单方法接口冗长代码的弊端,之前传递匿名内部类,现在需要传递闭包就可以了。
在Kotlin的高级函数中,是把函数作为参数传递,那么闭包同样也是通过这种方式,只不过是传递一个匿名代码块
def static pick(n,block){
for(i in 1..n){
block(i)
}
}
pick(4,{println(it)})
在使用的时候,传入了一个匿名代码块,可以看做是闭包,而且如果函数的最后一个参数为闭包,那么可以放出来在后面
pick(4){value->
println(value)
}
所以说,闭包你就可以认为它是一个匿名代码块,然后执行了一部分代码
4.3 向闭包传递参数刚才的示例中,就只有一个参数,如果只有一个参数,那么 it 就是默认的值;如果有多个参数,那么就需要声明区分
def static passParams(closure){
def map = [:]
map.a = "123"
map.b = "456"
map.forEach{ key,value->
closure(key,value)
}
}
使用的时候,就不能使用it作为接收参数了
passParams{ key,value->
println("key $key value $value")
}
4.4 闭包委托
在此之前,首先熟悉一下,闭包的几个重要成员
public Closure(Object owner, Object thisObject) {
this.resolveStrategy = 0;
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
this.parameterTypes = cachedClass.getParameterTypes();
this.maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters();
}
其中this、owner、delegate这3个属性比较重要,一般情况下delegate会被赋值为owner,两者是一样的
resoure {
println("$this")
println("owner is ${owner.getmetaClass()}")
println("delegate is ${delegate.getmetaClass()}")
}
owner is groovy.lang.metaClassImpl@3911c2a7[class Test]
delegate is groovy.lang.metaClassImpl@3911c2a7[class Test]
this指向的就是闭包对象,也就是执行闭包的上下文;在闭包内引用的方法和变量,都是绑定到了this,由this处理方法的调用,或者属性的访问;如果this不能处理,则交给owner,最后才是delegate
owner则是定义它的时候,类的对象,如果在一个闭包中,又定义了一个闭包,那么这个owner就是闭包1的对象,delegate也是闭包1的对象,但是delegate是可以修改的
resoure {
println("闭包1")
resoure {
println("闭包2")
}
}
如果我定义了一个闭包,然后在闭包中执行了fun函数,这种是没有问题的
def static fun(){
println("闭包代理")
}
def static closure = {
fun()
}
但是如果fun函数是在一个类中,就无法调用了
class A{
def static fun(){
println("闭包代理")
}
}
def static closure = {
fun()
}
这个时候,就可以修改代理来使用这个fun函数
closure.delegate = new A() closure()
在闭包中执行这个fun函数的时候,this是没法处理的,this指向的不是A类对象(看下面的代码,是Test类),owner同样也不可以,那么最后交由delegate处理,因为delegate指向的是A类对象,是可以处理这个fun方法,那么就正常执行了。
class Test {
static class A{
def static fun(){
println("闭包代理")
}
}
def static fun(){
println("脚本中的fun")
}
def static closure = {
fun()
}
closure.delegate = new A()
closure()
}
那么如果在脚本中还有一个fun函数,那么最终执行的肯定是脚本中的函数,因为this的优先级最高
那么如果我不想遵循这个策略,我就想先从delegate指向的类中查找方法执行,那么可以修改这个策略的,在Closure的构造方法中,有一个属性resolveStrategy就是用来修改策略的,默认是0;
public static final int OWNER_FIRST = 0; public static final int DELEGATE_FIRST = 1; public static final int OWNER_onLY = 2; public static final int DELEGATE_onLY = 3; public static final int TO_SELF = 4;
0意味着OWNER_FIRST,如果想要代理优先,那么就可以将resolveStrategy的值改为1,那么最终的处理就是从delegate开始,最终就是从A类中执行了fun函数
最后一个问题,Groovy这么灵活,为啥Android中不直接使用Groovy开发,而仅仅Gradle中使用Groovy开发呢?从下面一个小例子就能知道是什么原因了
执行一个循环任务,执行1千万次
double start = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
}
double end = System.nanoTime();
System.out.println("耗时:"+(end - start)/(10000000 * 1000));
Java耗时:0.0016699069324307542
Groovy耗时:0.0763667539030927
虽然动态语言使用起来很灵活,但是性能方面是打了很多折扣的



