前言
做Android开发的没有不知道Gradle的。时不常地去build.gradle中添加一些dependencies,或者修改一些诸如versionCoce,versionName之类的属性都已经是家常便饭。然而基本上也就仅限于此。我们都知道,Gradle是基于Groovy语言的,得益于Groovy语言的简洁与直白,应付所谓的“家常便饭”根本不需要额外的学习。然而,作为一名优秀的“程序猿”,我时常好奇,Gradle到底是啥,如此简洁的背后是不是隐藏着什么大秘密。于是我决定稍微深入地学习一下Gradle。
先抛出结论,经过我这段时间的学习,对Gradle背后的秘密有了一定的了解。然而,理解了这些之后,我对Gradle的使用仍然没有超过所谓“家常便饭”的级别。如果你秉持着能用就好的原则,那么现在你就可以把这篇文章关掉了。余下的内容只是对Gradle的释惑,让我们在使用Gradle时不再只是“能用”,而是“够用”。不过也仅仅是够用。Just Enough Gradle for Android...
Just Enough Groovy for Gradle
众所周知,Gradle是使用Groovy语言编写的(不过现在也有Kotlin版本的),我们先学习一点Groovy的基本语法。
//变量定义
def x = 3
//也可以传统点
int x = 3
//函数定义
String testFunction(def arg1) {//无需指定参数类型,当然指定也可以
return ""
}
//无类型的函数定义,必须使用 def 关键字
def nonReturnTypeFunc() {
last_line //return可以省略,如果没有return,最后一行代码的执行结果就是本函数的返回值
}
//也可以传统点
String getString() {
return "I am a string"
}
//函数调用可以不加括号
println("Hello, Groovy!")
println "Hello, Groovy!"
//没有参数的函数,还是要加上()的,不然谁知道是函数还是变量,例如上方的getString函数调用
getString()
//字符串,单引号的字符串完全是字面值;双引号字符串,有字符串插入
String name = 'Groovy'
assert "Hello, ${name}!" == 'Hello, Groovy!'
//在不引起混淆的时候可以简写
assert "Hello, $name!" == 'Hello, Groovy!'
//List定义
def nums = [1, 2, 3]
assert nums[0] == 1
assert nums[-1] == 3 //倒数第一个
//Map定义
def map = [a:1, b:2, c:3]
assert map.a == 1
assert map['b'] == 2
assert map.get('c') == 3
//List、Map上都定义了诸如each,all,find等函数
nums.each {
//隐含一个参数 it
println it
}
//闭包作为函数的最后一个参数,可以写到括号外面
map.each ({
println "key=${it.key},value=${it.value}"
})
//等价于
map.each() {
println "key=${it.key},value=${it.value}"
}
//括号省略
map.each {
println "key=${it.key},value=${it.value}"
}
//闭包closure,类似于Java 8和Kotlin中的lambda表达式,不同的是在Groovy中闭包都属于一个叫Closure的类
//这就有个问题,如何知道当前闭包的参数有几个,类型是啥,这就有赖于你对API的熟悉程度
public static <T> List<T> each(List<T> self, Closure closure)
Gradle概览
问:码农最重要的能力是什么?答:打码,噢,不对,码代码。但是除了码代码之外,每个码农还必须会把代码部署完成。对于Android来说就是打包。而Gradle就是帮我们做这一系列事情的工具,也就是构建工具。
在Android Studio中一个总工程称为一个Project,每个Project可以包含一个或多个模块,称之为Module,比如说最常见的app Module。在Gradle中,每个build.gradle文件都会生成一个Project对象,顶层build.gradle生成的Project称为RootProject,各个module中的build.gradle生成的Project的称为SubProject。
Gradle中的每一个Project都包含一系列Task,一个具体的编译过程是由一个个的 Task 来定义和执行的。比如 Android APP Project 包含源码编译Task、资源编译Task、lint检查Task、测试Task、打包Task、签名Task等等。一个Project包含有多少Task,主要是由插件决定的。而插件就是用来定义Task的。例如编译Java有Java插件,编译Android APP 有 Android Appliaction 插件,编译 Android Library 有 Android Library 插件。
//app的 build.gradle 指定了android application插件
apply plugin: 'com.android.application'
//module的 build.gradle 指定了android library插件
apply plugin: 'com.android.library'
//纯java工程的 build.gradle 指定了java插件
apply plugin: 'java'
每一个build.gradle对应了一个Project,这个Project包含了哪些Task主要由我们指定的插件决定。
至此,我们知道,编译过程即Task执行过程。不过还有个问题,以Android APP的编译为例,打包Task不是孤立的,它必须依赖于诸如源码编译Task等的执行,也就是说必须先执行源码编译Task等一系列Task,才能执行打包Task。这就是Task之间的依赖。每个Project中的Task会在配置阶段,根据彼此之间的依赖关系构成一个有向无环图(DAG)。这个DAG决定了Task的执行顺序。下图展示了Java插件的Tasks之间的依赖关系(本来想找Android 插件的Tasks,但是没有找到,可能是因为Android很多Task是动态生成的,Tasks不确定):
如上图,箭头方向即代表了依赖关系。例如 build task 依赖于 check task 和 assemble task,也就是说必须先执行check 和 assemble,才能执行build。以此类推。例如我们执行build task
> gradlew build
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
BUILD SUCCESSFUL
Total time: 1.956 secs
可以看出依赖链上的所有task被依次执行。定义task之间的依赖关系也很多简单:
//定义两个task,hello 和 world,后面会有Task的更详细的介绍
task hello << {
println 'hello'
}
task world << {
println 'world'
}
world.depensOn hello
//或者task定义的时候就指定
task world(dependsOn: hello) << {
println 'world'
}
Gradle深入
Gradle 工作包含三个阶段:
- 初始化阶段。读取gradle.properties中的参数;执行settings.gradle,确定共有多少个Project。
- 配置阶段。解析每个 project 中的 build.gradle,根据Taks之间的依赖关系构建有向无环图,确定Task的执行顺序。
- 执行阶段。
如图所示,在每个阶段结束之后,我们都可以添加一些Hook,完成我们的一些目的。
Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应。
- Gradle 对象:在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是 Gradle。我们一般很少去配置这个对象。
- Project 对象:每一个 build.gradle 会转换成一个 Project 对象。
- Settings 对象:settings.gradle 会转换成一个 Settings 对象。
其中最重要的是Project对象。build.gradle 是每个Project构建的入口,在这个文件我们主要:
- 加载插件
- 设置属性、配置插件(例如,版本号,依赖配置等等)
- 自定义Task(独立的,或者依赖于别的Task)
- 改变已有Task的行为(一般是增加一些我们自定义的行为)
一个常见的问题是,我们的工程往往不止一个build.gradle 文件,也即不止一个Project对象,怎么能让这些对象“共享”一些属性呢?Gradle 提供了一种名为 extra property(额外属性) 的方式。extra property 支持Project 和 Gradle 对象。
例如在Android Studio中创建一个支持Kotlin的项目,则其顶层的 build.gradle 如下:
buildscript {
//额外属性
ext.kotlin_version = '1.2.41'
//也可以这样写
ext {
kotlin_version = '1.2.41'
//更多的额外属性
...
}
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
以如上这种方式定义额外属性,是在RootProject对象上定义了额外属性,而所有的SubProject都会“继承”RootProject的额外属性。这样就实现了在多个Project对象“共享”属性。
真正有用的知识
上面扯了那么多,不禁让人疑问,有毛用?!确实是用处不大。看来,是时候展现真正的技术了。
先来一波总结:
很明显,关隘就在于Task。前面说过Task主要是由插件定义的,当然我们也可以定义自己的Task,完成我们的目的。对于Gradle的使用,主要在两个方面,首先是对插件定义好的属性地配置,这就要求我们了解插件定义了哪些属性,这些属性起什么作用;其次是自定义Task,完成我们特定的目的。
定义Task的几种方式
task hello {
doFirst {
println 'hello'
}
doLast {
println 'world'
}
}
task('hello') {
doFirst {
println 'hello'
}
doLast {
println 'world'
}
}
//tasks是project中的一个属性
tasks.create('hello') {
doFirst {
println 'hello'
}
doLast {
println 'world'
}
}
//没啥用的task
task hello {
println 'hello'
}
前面说过,Gradle有三个阶段初始化、配置和执行。如果我们像最后一种方式定义一个Task的话,那么闭包中的代码将在配置的时候执行,这没啥卵用,也不是我们定义Task的目的。
当我们执行一个Task的时候,其实是执行其拥有的actions列表,这个列表保存在Task对象实例中的actions成员变量中
private List<ContextAwareTaskAction> actions = new ArrayList<>()
所以说,Task中的doFirst和doLast方法只是向actions中添加Action,这样才能在执行阶段执行。
task hello {
//doFirst并不常用
doLast {
println 'world'
}
}
//可以简写, << 运算符的重载,doLast 的简写
task hello << {//这里即是一个Action的闭包
println 'world'
}
//不必从头定义每个task,Gradle已经帮我们定义好了很多常用的Task,我们只需要配置一下就能用了
//如下是一个删除task,要删除的是根目录下的build目录
task clean(type: Delete) {
delete rootProject.buildDir
}
//或者一个拷贝task
task copyOutputs(type: Copy) {
from "$buildDir/outputs/apk"
into '../results'
}
groovy 支持运算符重载,Task类上 << 的重载就是一个很好的例子。选择这个运算符进行重载还挺形象的,由左移引申为向actions列表末尾添加。
小结
至此,我们基本上对Gradle有了一个大致的了解。Gradle是一个构建工具,利用插件帮我们定义了许多Task,执行这些Task(其实是Task中的Action)完成构建任务。我们需要做的就是:首先,配置插件的各种属性,方便、快捷,能满足我们大部分的需求;其次,如果配置属性没有办法满足我们的要求,我们可以自定义Task,达到我们的目的。这篇文章主要是对Gradle知识性地介绍,想看更多Gradle在Android中应用的实例,请看Android Gradle 多维度实例。
参考书籍
Gradle for Android
Gradle Recipes for Android
《深入理解Android之Gradle》
《Android Gradle权威指南》