2019独角兽企业重金招聘Python工程师标准>>>
9/26/2016 1:59:42 PM
深入理解gradle编译-语法篇
导读
本篇文章主要介绍Groovy编程语法。在Gradle构建(builds)上,使用Groovy语法编写了大量特定性的构建文件。其中包括DSL(Domain Specific Language)和构建文件中加入的一些Groovy代码声明。
Groovy语法旨在生成目标性编程语言,基于java开发,编译生成java字节代码。同时Groovy具有函数功能,它是一种面向对象语言,可以说是从C++到Java的下一代语言。
##1.Groovy语法初体验
首先我们要搭建一个Groovy语法试验环境,通过Groovy官网网站点击DownLoad下载,window和Linux都可以。笔者当前下载版本为:apache-groovy-sdk-2.4.7,解压进入bin目录,执行:
$ ./groovy --version
Groovy Version: 2.4.7 JVM: 1.7.0_79 Vendor: Oracle Corporation OS: Windows 7
也可以配置环境变量(首选):
$ groovy -v
Groovy Version: 2.4.7 JVM: 1.7.0_79 Vendor: Oracle Corporation OS: Windows 7
这里首先来看第一个实例A-1,使用Groovy语法输出Hello, World
。
[A-1]. Hello, World! in Groovy
println 'Hello, World!'
其中注意事项:
- 分号
;
是可选的。如果添加了他们,可以正常工作,但他们不是必需的。 - 圆括号
()
是可选的。如果编译器能够正确识别语法,去掉圆括号也是工作正常的。上例中println方法传入String类型的参数,这里没有使用圆括号。 - 在Groovy中支持两种类型的String:单引号引用字符,像hello,为
java.lang.String
进程。双引号字符,允许修改。
在Groovy中没有原语。所有的变量使用类包装,如java.lang.Integer, java.lang.Character, and java.lang.Double
。本地数据类型整数常量定义和浮点型定义如下所示:
[A-2. Groovy中的基础类型]
assert 3.class == Integer // 整数类型,常量定义
assert (3.5).class == BigDecimal // 浮点类型,常量定义,对应java.math.BigDecimal
assert 'abc' instanceof String // 单引号字符串
assert "abc" instanceof String // 双引号字符串,允许插入值
String name = 'Dolly'
assert "Hello, ${name}!" == 'Hello, Dolly!' // 双引号字符插入内容,全格式插入
assert "Hello, $name!" == 'Hello, Dolly!' // 双引号字符插入内容,段格式,没有歧义情况下使用
assert "Hello, $name!" instanceof GString
注意,我们只可以直接引用常量方法,因为包装类已经做了初始化操作
Groovy允许我们使用明确的类型声明变量,像String,Date和Employee,使用def关键字,如下所示:
[A-3. 静态与动态数据类型]
Integer n = 3
Date now = new Date()
def x = 3
assert x.class == Integer
x = 'abc'
assert x.class == String
x = new Date()
assert x.class == Date
Java自动引入java.lang package。在Groovy中,下面的一些包是自动导入的:
- java.lang
- java.util
- java.io
- java.net
- groovy.lang
- groovy.util
类java.math.BigInteger
和java.math.BigDecimal
也是无需导入直接使用的。
###1.1 assert断言方法
assert方法在Groovy中执行其参数,依据Groovy断言,意思是:
- 非零数字,即是真
- 非空集合包含字符串,即是真
- 非空引用,即是真
- Boolean是true,即是真
具体断言如下例所示:
[A-4. The Groovy 断言]
assert 3; assert -1; assert !0
assert 'abc'; assert !''; assert !""
assert [3, 1, 4, 1, 5, 9]
assert ![]
断言通过任何信息不返回。断言失败则抛出异常,如下例所示,并附有大量的debug信息:
[A-5. 断言失败]
int x = 5; int y = 7
assert 12 == x + y // passes
assert 12 == 3 * x + 4.5 * y / (2/x + y**3) // fails
失败信息如下所示:
[A-6. 断言失败抛出异常]
Caught: Assertion failed:
assert 3 + 4 == 3.plus(5)
| | |
7 | 8
false
Assertion failed:
assert 3 + 4 == 3.plus(5)
| | |
7 | 8
false
at oparator.run(oparator:1)
###1.2 操作符
在Groovy中,每一种操作符都对应一个方法调用。例如,+(加号),调用plus方法。在Groovy中被广泛的使用。下面一些例子:
[A-7. 操作符使用]
assert 3 + 4 == 3.plus(4)
assert 3 * 4 == 3.multiply(4)
assert 2**6 == 64
assert 2**6 == 2.power(6)
assert 'abc' * 3 == 'abcabcabc' // String.multiply(Number)
try {
3 * 'abc'
} catch (MissingMethodException e) {
// no Number.multiply(String) method
}
String s = 'this is a string'
assert s + ' and more' == 'this is a string and more'
assert s - 'is' == 'th is a string'
assert s - 'is' - 'is' == 'th a string'
Date now = new Date()
Date tomorrow = now + 1 // Date.plus(Integer)
assert tomorrow - 1 == now // Date.minus(Integer)
注:Groovy使用较广泛的操作符,**
就是一种
在Java中,使用==
判断两个引用是否来自相同对象。在Groovy中,==
调用equal
方法,所以是检测的等价,而不是相等,就不再是对象之间的关系判断了。如果想判断引用之前的关系,使用is
方法。
###1.3 集合
Groovy为集合提供了本地方法。使用方括号和独立的值来创建ArrayList数组。我们可以使用操作符将集合类型转成其他类型。集合也是拥有操作符的,实现类似plus, minus, and multiply
实现如下所示:
[A-8. 集合实例]
def nums = [3, 1, 4, 1, 5, 9, 2, 6, 5]
assert nums instanceof ArrayList
Set uniques = nums as Set
assert uniques == [3, 1, 4, 5, 9, 2, 6] as Set
def sorted = nums as SortedSet
assert sorted == [1, 2, 3, 4, 5, 6, 9] as SortedSet
assert sorted instanceof TreeSet
assert nums[0] == 3
assert nums[1] == 1
assert nums[-1] == 5 // end of list
assert nums[-2] == 6
assert nums[0..3] == [3, 1, 4, 1] // two dots is a Range
assert nums[-3..-1] == [2, 6, 5]
assert nums[-1..-3] == [5, 6, 2]
String hello = 'hello'
assert 'olleh' == hello[-1..0] // Strings are collections too
在Groovy中,使用..
表示从A到B之前的范围值。在这个范围上,开始于第一个位置,向后遍历至最后一个。负数位置表示从集合最后位置起,向前第几个位置。
Maps使用冒号:
来分离键值对。通过方括号[position]来操作map结构中,类似于getAt和putAt方法,具体参考下例:
[A-9. Map实例化方法]
def map = [a:1, b:2, c:2]
assert map.getClass() == LinkedHashMap
// 将值赋值给map中的a键
assert map.a == 1
// 使用putAt方法
assert map['b'] == 2
// 使用getAt方法
assert map.get('c') == 2
###1.4 代码块Closure也称闭包
Groovy中有一个类被称为Closure,表示一块代码可以像一个对象一样使用。Closure类似于java 8中的lambda,将参数或是求值方法作为一个代码块。Groovy closures可以在外部修改定义的变量。
许多方法在Groovy中采用Closure作为参数。例如,集合方法中复制操作,就是一种典型的Closure实例,如下例所示:
[A-10. 使用groovy方便遍历每一个Closure参数]
def nums = [3, 1, 4, 1, 5, 9]
def doubles = [] // 空集合
nums.each { n -> // 取出一个值作为closure参数,变量名为n
doubles << n * 2 // 左移操作符将内容加入集合
}
assert doubles == [6, 2, 8, 2, 10, 18]
这是一种很常用的方法,将集合数据拷贝到double变量中,但是也有一种更好的替代方法,叫做collect(用户自己调试的时候去掉中文注释)。使用collect方法将集合赋值给一个新的集合。这是一种简单的映射方法,只需要思考如何减少适配映射的过程即可。
[A-11. 使用collect方法,将集合赋给另一个集合]
def nums = [3, 1, 4, 1, 5, 9]
def doubles = nums.collect { it * 2 }
assert doubles == [6, 2, 8, 2, 10, 18]
当closure有一个简单参数,这里也不需要给参数命名,使用箭头操作符it即可。在这上面例子中,collect方法创建了doubles集合并使用it*2将nums中的每一个元素乘以2赋值给doubles变量。在《Gradle Recipes for Android》一书中,A-11代码实例中对变量doubles的赋值使用有错误,读者可以对比关注一下。
###1.4 POGOs
在java中最常使用getters和setters对对象的属性值进行读取和复制。Groovy提供了一个相似类叫做POGOs。如下所示:
[A-12. POGO一个例子]
import groovy.transform.Canonical
[@Canonical](https://my.oschina.net/u/2345489)
class Event {
String name
Date when
int priority
}
这么一个小的类其实有很大的功能。
- 默认Event类为public
- 默认参数属性为private
- 默认方法为public
- Getter和Setter方法会为每个属性自动生成,不需要在通过public或者private去写
- 默认构造函数和映射关系(attribute:value)都已经提供
POGO包含@canonical声明,有Abstract Syntax Tree(AST)转换。AST转换修改在编译的时候修改语法树生成。
其中@canonical声明是AST编撰中三种声明的集合:@ToString, @EqualsAndHashCode, and @TupleConstructor
。所以@canonical声明在类中做了如下工作:
- 重写toString方法,跟踪每一个属性值。
- 重写equals方法,对每一个属性做非安全检查,并对等价做处理。
- 重写hashCode方法,生成键值映射对。
- 添加构造函数,并将属性初始化作为参数。
我们来看一下具体是使用,如下:
[A-13. 使用POGO的Event]
import groovy.transform.Canonical
[@Canonical](https://my.oschina.net/u/2345489)
class Event {
String name
Date when
int priority
}
Event e1 = new Event(name: 'Android Studio 1.0',
when: Date.parse('yyyy/MM/dd', '1973/07/21'), priority: 1)
Event e2 = new Event(name: 'Android Studio 1.0',
when: Date.parse('yyyy/MM/dd', '1973/07/21'), priority: 1)
println e1.toString()
println e2.toString()
###1.5 Groovy在Gradle编译文件中使用
Gradle构建文件支持Groovy语法。这里简单做个说明,表示Groovy在Gradle中的使用。在Gradle编译文件中applay方法是工程的初始化。方法上括号是可选的,这里省略了。参数被设置一个叫plugin值为'com.android.application'
。
apply plugin: 'com.android.application'
在下例中,android
表示一种数据,有DSL插件支持,使用closure作为一个参数。在closure中的属性方法,包括compileSdkVersion
,其中圆括号是可以省略的。在Gradle的编译文件中,属性是需要是用等号=
赋值的,等号会调用setter方法。Android插件为compileSdkVersion(23)
增加setter方法,如setCompileSdkVersion(23)
。
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
}
同时,嵌套属性,像compileSdkVersion
可以使用点的引用来替代,如下所示:
android.compileSdkVersion = 23
两种效果是一致的。
在最近的版本中,插件中增加了清除任务(clean task)在Gradle构建文件中。任务的名字叫做clean,具体是实例化Delete类,使用closure闭包。非常符合常规的做法,closure使用了圆括号,如下所示:
task clean(type: Delete) {
delete rootProject.buildDir
}
上面的实现中调用了delete方法,传入参数为rootProject.buildDir
。该值为rootProject属性,在工程的最顶层,并且buildDir的默认值为“build”目录,所以该任务是删除顶层工程的“build”目录。
注意:删除顶层工程的目录也会调用app子目录,同时删除子工程的build目录
编译使用了SDL,也就意味着它的参数在编译的时候使用。这里的fileTree方法使用了圆括号。其中的dir参数表示当前目录文件夹。include参数表示了Groovy文件列表。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
###1.6 额外信息
一本书Making Java Groovy,by Ken Kousen (Manning),讨论Groovy在java中的集成,也有一个章节讲述Gradle构建。Groovy决定性书籍Groovy in Action, Second Edition, by Dierk Konig,Paul King, et al. (Manning).
Groovy主页http://groovy-lang.org/
视频教程Groovy Programming Fundamentals,Practical Groovy Programming,和 Mastering Groovy Programming。
##2 Gradle基础
本文的核心在于介绍在Android环境下使用Gradle构建文件。Gradle是一个强大的构建工具,然后它被广泛的用在其他项目中。本节主要介绍Gradle基础。我们能够在Android构建文件中的使用的所有能力。
###2.1 安装gradle Gradle不依赖于其它部分,直接可通过ZIP下载。我们仅需要下载最新贡献的Gradle,开始使用它,地址为https://gradle.org/。安装起来非常容易:
- 下载并解压zip文件
- 为解压文件设置GRADLE_HOME环境变量,指向解压文件
- 将GRADLE_HOME目录下的bin目录添加到path下。
gradle命令可以在工程的根目录下执行,同时编译很多module。默认情况下编译文件叫做build.gradle,但是其它命名也是可以使用的。使用-b或者--build-file标志用来使用不同的构建文件。
作为替代,Gradle提供了一个封装(wrapper),可以第一次使用的时候自动下载和安装Gradle。该封装默认情况下会使用目录中的最新版本。
由上节我们知道Gradle构建文件是由Groovy语法编写,我们运行Gradle的时候不需要安装Groovy。Gradle内部已经集成了Groovy,可以用来直接构建。
我们想看Gradle的详细信息可以通过:
Gradle -v
输出结果为:
$ gradle -v
------------------------------------------------------------
Gradle 2.4
------------------------------------------------------------
Build time: 2015-05-05 08:09:24 UTC
Build number: none
Revision: 5c9c3bc20ca1c281ac7972643f1e2d190f2c943c
Groovy: 2.3.10
Ant: Apache Ant(TM) version 1.9.4 compiled on April 29 2014
JVM: 1.7.0_79 (Oracle Corporation 24.79-b02)
OS: Windows 7 6.1 amd64
###2.2 构建生命周期
Gradle的构建分为三个不同的阶段:
- 初始化:读取环境配置文件
init.gradle
和gradle.properties
,设置所有在settings.gradle中罗列的子工程,也可以叫module。 - 配置:评估所有的构建脚本并构建模型,包括DAG(directed acyclic graph),算是构建路线图。
- 执行:执行所有期望执行的任务。
###2.3 Android工程 Gradle构建文件包含很多任务,他们加入到DAG中。Gradle是一个基于插件的体系结构,所以通过添加插件进行构建,我们可以添加任务和功能进行构建。
在Android世界之外最常用的插件就是Java插件。由于Gradle已经支持插件,我们只需要在build.gradle
文件中加入apply命令,即可使用插件,如下所示:
apply plugin: 'com.android.application'
事实上,插件自身定义了一系列相关联的任务。查看任务的是否有效,在工程的根目录使用tasks,输出如下所示:
$ gradle tasks
Parallel execution is an incubating feature.
WARNING [Project: :walletapp] Support for libraries with same package name is deprecated and will be removed in a future release.
:tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.
Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
extractDebugAnnotations - Extracts Android annotations for the debug variant into the archive file
extractReleaseAnnotations - Extracts Android annotations for the release variant into the archive file
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]
Help tasks
----------
components - Displays the components produced by root project 'wallet-app'. [incubating]
dependencies - Displays all dependencies declared in root project 'wallet-app'.
dependencyInsight - Displays the insight into a specific dependency in root project 'wallet-app'.
help - Displays a help message.
model - Displays the configuration model of root project 'wallet-app'. [incubating]
projects - Displays the sub-projects of root project 'wallet-app'.
properties - Displays the properties of root project 'wallet-app'.
tasks - Displays the tasks runnable from root project 'wallet-app' (some of the displayed tasks may belong to subprojects).
Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.
Verification tasks
------------------
check - Runs all checks.
clean - Deletes the build directory.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.
Other tasks
-----------
assembleDefault
jarDebugClasses
jarReleaseClasses
transformResourcesWithMergeJavaResForDebugUnitTest
transformResourcesWithMergeJavaResForReleaseUnitTest
To see all tasks and more detail, run gradle tasks --all
To see more detail about a task, run gradle help --task
BUILD SUCCESSFUL
Total time: 2.588 secs
任务列表显示哪些是有效的,但是没有展示他们之间的依赖关系。若想知道具体任务的依赖关系可以直接执行该任务(build):
$ gradle build
tasks任务格式是一个有向无环图(directed acyclic graph)。在这种情况下视图展示了任务之间的关系,我们从线上文档提出的一张图来说明,显示Java插件的DAC图。
每一种关系都使用了一种箭头指向了下一个部分,同时他们也是有很多关系的,这里在DAC上没有环路。运行构建task意味着:
- 执行构建build,check和assemble任务必须被执行。
- check任务依赖于test任务,test任务又依赖于testClasses和classes等等。
###2.4 仓库与依赖
当前的构建文件定义测试任务,但不是一个测试library。下面Java工程中典型的构建文件格式:
apply plugin: 'java'
repositories {
jcenter()
}
dependencies {
testCompile 'junit:junit:4.12'
}
Gradle为构建提供了一种Domain Specific Language (DSL)插件。仓库repositories和dependencies元素是DSL构建文件的一部分。
仓库是库的一种集合,可以通过命令将库文件缓存至本地,具体依赖于用户工程目录下.gradle文件。构建文件的库使用jcenter()方法连接。其它的构建库还有mavenCentral(),对应Maven公共核心库。如果我们的构建文件包含多个仓库,会根据每一个依赖轮流请求。
列出的依赖关系,在dependencies模块。一个依赖包含的库文件的基本信息(组、名称和版本),同时依赖配置是需要的。testCompile
作为预定义的依赖配置,对于Java插件来讲,预定义的有:
- compile
- runtime
- testCompile
- testRuntime
- archives
- default
前四个是比较通用的,但是每一个都包含了较丰富的内容。例如,compile可以使整个项目能够进行编译,使得编译库有效,testCompile依赖增加了src/test/java代码树。JDBC驱动需要依赖runtime依赖,testRuntime仅是依赖数据库中的测试部分。
###2.5 自定义任务 Gradle DSL是可拓展的,我们不需要做任何操作,插件以及提供了该功能。迟早,每一次构建都变成自定义构建,Gradle希望我们记住这些。下面我们来看一下增加自定义任务的例子:
def task {
doLast {
println 'hello'
}
}
doLast模块表示代码应该运行的时候执行。一些代码运行在配置的时候。
Gradle也提供了doFirst模块,但是不怎么常用。同时我们可以使用doLast模块的缩写,左移操作符。如下任务运行在执行时。这是很容易忽视语法,所以这种方法是不可取的,尽量写出doLast模块。
def task << {
println 'hello'
}
Gradle API提供了很多有效的构建任务,也是可以被自定义的。如下所示,定义Copy task。
def copyOutputs(type: Copy) {
from "$buildDir/outputs/apk"
into '../results'
}
拷贝任务本身包括配置和执行时间的选择。在这种情况下,设置from和into属性并设置所需要的值。这种方法来配置现有的任务,它有利于告诉Gradle你想要什么,而不是指定执行什么操作。
###2.6 多工程构建
子目录工程作为独立的Gradle项目包含构建文件和依赖。事实上他们可以互相依赖。settings.gradle罗列了Gradle工程的所有子目录。在典型的Android app中,Settings.gradle包含了app目录,表示了应用程序实际代码的真正位置。
在多工程构建文件中,每一个子工程都有一个构建文件。为了在工程建公用模块,可以使用subprojects
或allprojects
模块,都可以重写配置文件中的Porject类。
##参考
译《Gradle Recipes for Android》-Just Enough Groovy to Get By
Groovy主页http://groovy-lang.org/
android studio