Android Gradle学习记录1 基本特点

来源:互联网 发布:知乎日报 ress kindle 编辑:程序博客网 时间:2024/06/05 00:43

自己以前主要是做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安装,
整个操作步骤如下:

//首先下载sdkcurl -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 helpEnjoy!!!

这里应该是要求用户将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) {......}

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

例如:

//定义返回值无类型的函数,必须使用关键字defdef 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: testgroovy.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 = 1def test = "I am $x" //打印test时,将输出I am 1

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

例如:

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

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

beginline 1line 2line 3end

2. Groovy中的数据类型
在前一部分,我们介绍了Groovy的基本语法,
现在来看一下Groovy中的数据类型。

简单来讲,Groovy的数据类型可以被分为三大类,即:
Java中的基本类型、Groovy中的容器类和闭包。


2.1 基本数据类型
Groovy中的所有事物都是对象。

因此,当在Groovy中定义int、boolean这些Java中基本的数据类型时,
实际上定义的是它们的包装数据类型。

例如:

def int num = 1println 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]  //输出trueaList[10] = 8println 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    //结果为1println aMap.key2    //结果为test             //注意这种使用方式,key不用加引号println aMap['key2'] //结果为test//插入元素aMap['key3'] = falseprintln 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..5println aRange       // [1, 2, 3, 4, 5]aRange = 1..<6       println aRange       // [1, 2, 3, 4, 5]println aRange.from  // 1println aRange.to    // 5println aRange[0]    //输出1aRange[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: ZhangJianaClosure.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..5aRange.each { entry ->    println entry  //将会依次打印Range中的元素}

从上面的代码可以看出,当我们调用each接口时,对应的圆括号不见了。
正常的写法应该是这样:

def aRange = 1..5aRange.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的。

原创粉丝点击