您的位置:首页 > 移动开发 > Android开发

Android Gradle学习记录1 基本特点

2017-06-17 17:25 225 查看
自己以前主要是做Android Framework的,对源码研究的比较多,

但并不是很了解脚本语言、构建工具等,最多也就是修改下Android源码中的mk文件。

现在开始进行Android SDK开发的工作,会比较频繁地用到Android Gradle。

最近好不容易利用Gradle完成了对构建脚本的优化,觉得里面的坑还是很多的,

有必要学习、梳理和总结一下相关的知识。

因此在接下来的这几篇博客里,我们将从Gradle的基础知识入手,

记录一下需要掌握的知识点,并总结下遇到的问题。

1. Groovy

Gradle是一种构建工具,可以方便地按照不同的要求,将代码编译成不同的版本。

Gradle依赖于Groovy,同时本身是一种DSL(Domain Specific Language,领域相关语言)。

直白来讲,DSL就是定义了很多"行话",

例如,在Gradle中使用sourceSets时,sourceSets就代表当前工程中源文件的集合。

其实,这就像C语言中定义了内置的宏、面向对象语言内置的集合类一样,

是一种默认的约定俗成的概念。

关于DSL,我们之后接触Android Gradle的具体示例时,会比较容易理解。

因此,这里我们先重点介绍一下Groovy语言。

Groovy是运行在Java平台上的, 具有类似Python、 Ruby和Smalltalk语言特性的灵活动态语言。

Groovy保证了这些特性,可以像Java语法一样被Java开发者使用。

Groovy程序运行时,首先被编译成Java字节码,然后通过JVM来执行。

Java, Groovy和JVM之间的关系类似于下图:



实际上,由于Groovy Code在真正执行的时候,已经变成了Java字节码,

因此JVM根本不知道自己运行的是Groovy代码。

1.1 部署Groovy开发环境

部署Groovy开发环境的方法有很多。

以在Ubuntu系统上的操作为例,个人觉得最简单的方式是使用sdk安装,

整个操作步骤如下:

//首先下载sdk
curl -s get.sdkman.io | bash


当sdk下载成功后,就会出现类似如下提示:

Please open a new terminal, or run the following in the existing one:

source "/home/zhangjian/.sdkman/bin/sdkman-init.sh"

Then issue the following command:

sdk help

Enjoy!!!


这里应该是要求用户将sdk的路径加入到环境变量中。

//按照要求输入即可
source "/home/zhangjian/.sdkman/bin/sdkman-init.sh"


之后就可以利用sdk下载安装groovy:

sdk install groovy


groovy安装成功后,输入groovy -version,应该可以看到类似如下的信息:

Groovy Version: 2.4.11 JVM: 1.8.0_131 Vendor: Oracle Corporation OS: Linux


groovy安装成功后,我们可以做一下测试。

例如,创建一个test.groovy脚本,内容如下:

println "hello groovy"


然后,在终端中执行命令groovy test.groovy,可以看到如下结果:

hello groovy


需要说明的是,除了可以直接使用JDK之外,

Groovy还有一套GDK,对应的网址是:

http://www.groovy-lang.org/api.html

1.2 Groovy基础语法

Groovy的语法基本与Java类似,例如:

同样支持while、if等关键字,同样利用//或者/**/来进行注释等。

同时,Groovy也有些python的特点,例如支持动态类型等。

我们没必要花大力气专门研究Groovy的语法,用的时候稍微查一下资料即可。

在这一部分,我们主要记录一下Groovy定义变量、函数和字符串时的用法。

毕竟这部分内容用的比较多,是阅读和编写代码的基石。

变量定义

Groovy支持动态类型,即定义变量的时候可以不指定其类型。

例如:

//不需要指定类型
def variable = 1 //Groovy语句不需要用分号结尾

//也可以指定类型
def int x = 1


如上代码所示,Groovy定义变量时使用的关键字是def。

虽然def不是必须的,但为了代码清晰,最好尽量使用该关键字。

函数定义

Groovy定义函数时,也可以不指定参数的类型。

例如:

//无需指定参数类型
String testFunction(arg1, arg2) {
......
}


此外,函数的返回值也可以是无类型的。

例如:

//定义返回值无类型的函数,必须使用关键字def
def testFunc() {
......
return 1;
}

//指定返回类型时,可以不使用def关键字
int anotherFunc() {
.......
return 2;
}


虽然在Groovy中定义函数时,可以省略掉参数和返回值的类型,

但为了降低阅读及使用函数的难度,个人觉得还是主动指明类型比较好。

最后,Groovy中的函数可以不使用return语句来返回结果。

如果不使用return语句的话,函数中最后一句代码的执行结果将被设置成返回值。

例如:

def getResult() {
"First Blood, Double Kill" // 如果这是最后一行代码,则返回类型为String
1000 //如果这是最后一行代码,则返回类型为Integer
}


需要注意的是:

如果函数定义时指明了返回类型的话,函数中必须返回正确的数据类型。

即在不使用return时,如果函数最后一行代码的运行结果,与定义的返回类型不一致,

将产生运行时报错。

个人觉得刻意省略return语句,并不是一种很好的习惯。

从网上的资料来看,似乎Groovy代码调用函数时,可以不加括号。

例如,调用上述函数时,可以直接这么写:

getResult


不过,我自己实验时,发现这么做似乎有问题。

测试代码如下:

def int getResult() {
return 1
}

println getResult


运行结果如下:

Caught: groovy.lang.MissingPropertyException: No such property: getResult for class: test
groovy.lang.MissingPropertyException: No such property: getResult for class: test
at test.run(test.groovy:5)


从异常信息可以看出,Groovy认为getResult是个属性。

因此,通常情况下除非调用类似于println、delete这种内置的比较常见的函数外,

最好还是以括号的形式调用自定义的函数。

字符串

Groovy中可以利用单引号、双引号和三引号来定义字符串,其特点与脚本语言及其相似。

其中:

单引号包裹的内容严格对应Java中的String,不对$符号进行转义。

例如:

def singleQuote='I am $ dolloar' //打印singleQuote时, 输出I am $ dollar


双引号中的内容也会按字符输出,

不过如果其中有$号的话,$会按需作为转义字符。

例如:

def x = 1
def test = "I am $x" //打印test时,将输出I am 1


三引号主要用于支持字符串换行输出。

例如:

def multiLines = '''begin
line 1
line 2
line 3
end'''


打印multiLines的结果如下所示,这一点和python比较相似:

begin
line 1
line 2
line 3
end


2. Groovy中的数据类型

在前一部分,我们介绍了Groovy的基本语法,

现在来看一下Groovy中的数据类型。

简单来讲,Groovy的数据类型可以被分为三大类,即:

Java中的基本类型、Groovy中的容器类和闭包。

2.1 基本数据类型

Groovy中的所有事物都是对象。

因此,当在Groovy中定义int、boolean这些Java中基本的数据类型时,

实际上定义的是它们的包装数据类型。

例如:

def int num = 1
println num.getClass().getCanonicalName() //输出结果是java.lang.Integer


2.2 容器类

Groovy中的容器类主要有三种:

List(链表)、Map(键-值表)及Range(范围)。

List类

Groovy中的List类,底层对应Java中的List接口,

一般用ArrayList作为真正的实现类。

在Groovy中,List变量由[]定义,

其元素默认为Object对象。

例如:

// 元素默认为Object,因此可以存储任何类型
def aList = [5, 'test', true]
println aList.size  //结果为3


Groovy中的List,具有数组的优点,即可以直接通过索引来进行存取。

此外,List还有不用担心越界的好处,当索引超过当前长度时,

List会自动往该索引添加元素。

例如:

def aList = [5, 'test', true]
println aList[2]  //输出true
aList[10] = 8
println aList.size // 在index=10的位置插入元素后,输出11,即自动增加了长度
println aList[9] //输出null, 自动增加长度时,未赋值的索引存储null


如果我们想限定List中元素的类型,可以这么用:

//添加as关键字,并指定类型
def aList = [5, 'test', true] as int[]

// 限定aList中的元素必须是int类型
// 由于我们定义错误,于是会抛出异常
// Caught: java.lang.NumberFormatException: For input string: "test"
// java.lang.NumberFormatException: For input string: "test"
// at test.run(test.groovy:1)


Map类

Groovy中的Map,其底层对应Java中的LinkedHashMap。

当我们定义Map时,对应的格式有点像一个Json字符串。

例如:

def aMap = ['key1':1, "key2":'test', key3:true]
println aMap //打印结果为[key1:1, key2:test, key3:true]


从上面的例子可以看出,Map由[:]这种格式定义,其中:

冒号左边为key值,右边是value。

需要注意的是:

Map中的key必须是字符串,value可以是任何对象。

具体的key值可以用单引号、双引号包裹,也可以不使用引号。

不过,但我们不使用引号定义key时,可能会带来一些误解。

例如:

def key = 'test'
def map = [key:1]  //此时key的值为'key',不是'test'
def otherMap = [(key):1] //此时的key才为'test'


个人觉得花心思记录这些细节用处不大,

由于这些问题导致错误更不值得,

因此定义Map时,我们最好用引号来包裹key。

从Map中读取元素,或向Map中添加元素的操作也比较简单。

对应的做法类似于:

def aMap = ['key1':1, "key2":'test', key3:true]

//读取元素
println aMap.key1    //结果为1
println aMap.key2    //结果为test
//注意这种使用方式,key不用加引号

println aMap['key2'] //结果为test

//插入元素
aMap['key3'] = false
println aMap         //结果为[key1:1, key2:test, key3:false]
//注意用[]持有key时,必须加引号

aMap.key4 = 'fun'    //Map也支持自动扩充
println aMap         //结果为[key1:1, key2:test, key3:false, key4:fun]


Range类

Range是Groovy对List的一种扩展,使用方式类似于:

def aRange = 1..5
println aRange       // [1, 2, 3, 4, 5]

aRange = 1..<6
println aRange       // [1, 2, 3, 4, 5]

println aRange.from  // 1
println aRange.to    // 5

println aRange[0]    //输出1
aRange[0] = 2        //抛出java.lang.UnsupportedOperationException


从上面的示例可以看出,Range存储数据的方式和List很相似,

可以比较简单的定义从from到to的集合。同时,也支持使用索引读取存储的数据。

不过,与List不同的是,Range不支持写入数据,更不支持自动扩展了。

因此,可以认为Range是加了final关键字的List。

这里的介绍比较简单,更多详细的内容可以参阅官方的API文档:

http://www.groovy-lang.org/api.html

需要注意的是:

根据 Groovy 的原则, 如果一个类中有名为 xxyyzz 这样的属性(其实就是成员变量),

Groovy 会自动为它添加 getXxyyzz 和 setXxyyzz 两个函数, 用于获取和设置 xxyyzz 属性值。

因此,当你看到 Range 中有 getFrom 和 getTo 这两个函数时候,

就得知道潜规则下,Range有 from 和 to 这两个属性。

当然,由于它们不可以被外界设置,所以没有公开 setFrom 和 setTo函数。

2.3 闭包

闭包的英文是Closure,是Groovy中比较重要的一种概念。

个人觉得Gradle中闭包的使用,特别像面向对象语言中的lambda表达式。

即类似于一个可执行的代码对象,同时具有函数和对象的特点。

2.3.1 闭包定义和使用

我们先看看如何定义闭包。

当定义一个无显示参数的闭包时,对应的形式类似于:

//同样用def定义一个闭包
def aClosure = {
//代码为具体执行时的代码
println 'this is closure'
}

//像函数一样调用,无参数
aClosure() //将执行闭包中的代码,即输出'this is closure'

//下面这种写法也可以
//aClosure.call()


当定义一个有显示参数的闭包时,对应的形式类似于:

//->前为参数,需要定义具体的类型
def aClosure = { String name, String job ->
println 'My name is: ' + name + ', and my job is: ' + job
}

//调用时,传入对应的参数即可
aClosure('ZhangJian', 'Engineer')

//下面这种写法也可以
//aClosure.call('ZhangJian', 'Engineer')


定义闭包时,需要注意的是:

如果闭包没有定义参数的话,则隐含有一个参数it。

例如:

def aClosure = {
println "My name is: $it"
}

//参数会被赋值给it
//于是执行的结果为: My name is: ZhangJian
aClosure.call('ZhangJian')


如果显示定义参数的话,it就不存在了,例如:

def test = { String name->
println it
}

test('ZhangJian') //groovy.lang.MissingPropertyException: No such property: it for class:


当我们定义无参数的闭包时,如果想消除隐含的it,

那么必须按照如下的格式:

//->前不添加任何信息
def aClosure = {->
println "My name is: $it"
}

//此时,传入参数就会报错
//aClosure.call('ZhangJian')


2.3.2 使用闭包时的注意点

省略圆括号

闭包在Groovy中大量使用,很多类都定义了一些函数,这些函数最后一个参数都是一个闭包。

举例来说,在List类中定义这样的接口:

public static <T> List<T> each(List<T> self, Closure closure)


上面的这个接口被调用后,将依次利用closure处理List中的每一个元素。

不过我们实际使用这个接口时,一般会这样写:

def aRange = 1..5
aRange.each { entry ->
println entry  //将会依次打印Range中的元素
}


从上面的代码可以看出,当我们调用each接口时,对应的圆括号不见了。

正常的写法应该是这样:

def aRange = 1..5
aRange.each ({ entry ->
println entry
}) //注意括号


这是因为Groovy约定,当函数的最后一个参数是闭包时,可以省略函数的圆括号。

知道这个约定后,我们就会理解Gradle中经常出现的一些代码:

task test {
doLast {
println 'just a test'
}
}


这里doLast是个函数,其参数为一个Closure对象,省略了圆括号。

于是,当Groovy底层解析到doLast时,并不会像脚本一样立即执行对应的动作。

Closure参数

从前文可以看出,Closure同时具有函数和对象的特点,使用起来会比较方便。

不过它的坑其实还是挺多的,使用时与上下文有极强的关联。

例如,前文提到的each函数:

public static <T> List<T> each(List<T> self, Closure closure)


我们知道Closure本身具有函数的特点,也有输入参数和返回结果。

但从这个接口的定义中,我们完全看不出任何信息。

不过反过来讲,这种设计也符合针对接口编程的要求,

在使用时更加灵活。

因此,通常情况下,使用包含Closure的陌生接口时,

我们可能需要依赖API文档及对应的实例。

3. 总结

这篇博客可以看作学习Gradle的入门篇,

主要记录一下Groovy语言的基本特点。

从上文可以看出,在有Java基础的前提下,

还是比较容易理解Groovy的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: