Gradle构建过程浅析

参考链接:

《Gradle权威指南》(京东购买)
《精通Groovy》
《Groovy官网闭包文档》
《Boyce》

HelloWorld

假设:

此时的操作系统是Win10
此时已经在你的电脑上安装了Android Studio并且新建了一个最简单的工程。

配置环境变量:

GRADLE_HOME –> C:\Users\iRain.gradle\wrapper\dists\gradle-4.6-all\bcst21l2brirad8k2ben1letg\gradle-4.6
PATH –> %GRADLE_HOME%\bin

新建android-gradle目录,并在其中新建文件build.gradle

1
2
3
4
5
task hello{
doLast{
println'Hello World!'
}
}

在android-gradle目录执行命令gradle -q hello

1
2
$ gradle -q hello
>>> Hello World!

Gradle Wrapper

Wrapper是对Gradle的一层包装,方便团队统一Gradle的构建版本。
Wrapper在Windows下是一个批处理脚本,在Linux下是一个shell脚本。

当我们使用Wrapper启动Gradle的时候会自动检查Gradle相关配置。

1
2
3
4
5
6
$ gradle wrapper
:wrapper

BUILD SUCCESSFUL

Total time: 2.804 secs

生成如下文件

——gradle[目录]
————————wrapper[目录]
————————————————gradle-wrapper.jar
————————————————gradle-wrapper.properties
——gradlew
——gradlew.bat

其中gradlew和gradlew.bat分别是linux和windows下的执行脚本,gradle-wrapper.jar具体逻辑实现的jar包,gradle-wrapper.properties是配置文件。

1
2
3
4
5
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
字段名 说明
distributionBase 下载的Gradle压缩包解压后存放的主目录
distributionPath 解压后路径
distributionUrl Gradle发行版压缩包的下载地址
zipStoreBase 同distributionBase,只不过是存放zip压缩包的
zipStorePath 同distributionPath,只不过是存放zip压缩包的

我们可以在build.gradle中自定义wrapper Task来修改生成的配置。

1
2
3
4
task wrapper(type: Wrapper){
gradleVersion = '2.4'
distributionBase= 'GRADLE_USER_HOME'
}

同样可以配置上面的其他参数。

Gradle的语法组成

Gradle不单单是一个配置脚本,它的背后是几门语言:

  • Groovy Language
  • Gradle DSL
  • Android DSL

DSL全称Domain Special Language 领域特定语言。Gradle是基于Groovy的DSL.
Groovy是一种JVM语言,和Java类似也是编译成clss文件然后在JVM上运行的,我们可以使用Groovy和Java混写。

Grovvy快速入门

Grovvy相对于Java语法的特点:

  • Groovy 的松散的 Java 语法允许省略分号和修改符。
  • 除非另行指定,Groovy 的所有内容都为 public。
  • Groovy 允许定义简单脚本,同时无需定义正规的 class 对象。
  • Groovy 在普通的常用 Java 对象上增加了一些独特的方法和快捷方式,使得它们更容易使用。
  • Groovy 语法还允许省略变量类型。

HelloWorld示例

1
println "HelloWorld!"

没错,就是它,就是这么简单。再来看一个例子:

1
2
3
4
5
def repeat(val){
for(i in 1..5){
println val
}
}

这样可以循环5次来输出变量val的值,在Groovy中允许省略变量类型(这个和kotlin很像)

关于其他的语法特点这里就不多说了,请参考相关文档,接下来我们来看看特别重要的闭包的概念。

闭包是Groovy中一个特别重要的特性,可以说它是DSL的基础。

闭包本质上是能够读取其他函数内部变量的函数,是将函数内部和函数外部连接起来的一座桥梁。

JavaScript中的闭包请参考(http://dp2px.com/2018/10/19/web-js-object2/)

在Groovy中,闭包的概念更加的宽泛,不像javascript那样必须是返回函数。在Groovy中,只要是一个代码块就可以叫做闭包,可以作为方法参数,可以作为返回值, 可以单独被调用。为什么Groovy中的闭包这么牛逼?来看Groovy官网的一段闭包的描述:

A closure is an instance of the groovy.lang.Closure class, making it assignable to a variable or a field as any other variable, despite being a block of code:

1
2
3
4
5
6
7
8
9
10
{ item++ }                                                                        

{ String x, int y ->
println "hey ${x} the value is ${y}"
}

def closure = { reader ->
def line = reader.readLine()
line.trim()
}

所以,在Groovy中,闭包就是类Closure,定义一个闭包就是定义一个Closure类。我们知道,在Java中 其实类就可以看成是闭包。因此我们也可以说Groovy中其实没有闭包,Groovy所谓的闭包只不过是提供一种 简便的语法支持来构建简单的类。

当然,Groovy中的这种闭包写起来还是很便利,但是我们在使用Groovy的时候,往往会面临一个问题, 这个地方是用一个方法还是用一个闭包?

1
2
3
4
5
6
7
def method(def arg1, def arg2) {
println("method: $arg1 + $arg2")
}

def closure = {def arg1, def arg2 ->
println("closure: $arg1 + $arg2")
}

为了回答这个问题其实也很简单,只要清楚Groovy中闭包的本质是一个类就可以,在Groovy或Java 中,类是唯一的“可调用对象”,唯一可以作为参数和返回值的对象。所以,使用Groovy的时候只有在 一种情况下建议使用闭包,就是在Java中这个地方你不得不使用类但是又觉得使用类有点多余的时候, 或者你在不得不使用匿名内部类的时候,就使用Groovy提供的便利构建类的方式——闭包。其他情况, 用方法。

所以在一个类中,把一个方法定义成闭包,但是这个闭包不作为方法参数,也不作为方法返回值时,我觉得 是没有任何意义的,除了语法上让别人觉得你懂点Groovy之外。

Gradle任务构建入门

settings.gradle文件

settings.gradle文件大多数的作用是配置子工程的。

1
include ':app'

build.gradle文件

每个Project都会有一个build.gradle文件,这个是构建入口,可以在这里配置版本、需要的插件、依赖库等。

在开发大型项目的时候我们一个Java工程,会被划分为很多小模块,每个模块对应一个child project,这个时候我们可以统一配置也可以分别配置。

project和Task

上面已经提到了,一个工程我们可以分为很多个project,每个project可以有一个单独的build.gradle配置,每个project是由多个Task组成的。

Task是一个原子性的操作,比如打个jar包、复制一份文件、编译一次Java代码等。

1
2
3
4
5
6
7
8
task customeTask1{
doFirst{
println 'customeTask1:doFirst'
}
doLast{
println 'customTask2:doLast'
}
}

Task任务之间可能存在依赖关系,不如我们允许jar之前需要执行编译(compile)任务。

1
2
3
4
5
6
7
8
9
task compile << {
println 'compile'
}

task runjar(dependsOn: compile){
doLast {
println 'run jar'
}
}

Task可以通过API的方式来操作任务,注意脚本前后顺序。

1
2
3
4
5
6
7
8
9
10
11
task ex36Hello << {
println 'dowLast1'
}

ex36Hello.doFirst{
println 'dowFrst'
}

ex36Hello.doLast{
println 'dowLast'
}

另外,上面的”<<”操作符在Task上是doLast方法的短标记形式。

Gradle插件

插件的应用可以通过Project.apply()方法来完成,apply方法有好几种用法,并且插件也分为二进制插件和脚本插件。

二进制插件

二进制插件是实现了org.gradle.api.Plugin接口的插件,它可以有plugin id

1
apply plugin:'java'

其中java是插件的plugin id它是唯一的。Gradle自带的核心插件都有一个plugin id,其实它对应的是org.gradle.api.plugins.JavaPlugin

等价于

1
2
3
apply plugin:org.gradle.api.plugins.JavaPlugin
或者
apply plugin:JavaPlugin

脚本插件

build.gradle文件

1
2
3
apply from:'version.gradle'

...

version.gradle文件

1
2
3
4
ext{
versionName = '1.0.0'
versionCode = 1
}

第三方插件

第三方插件作为jar的二进制插件,我们在应用的时候,必须要在buildscript{}中配置classpath才能使用。

1
2
3
4
5
6
7
8
buildscript{
repositories{
jcenter()
}
dependencies{
classpath 'com.android.tools.build:gradle:1.5.0'
}
}

buildscript{}块是一个在项目构建之前,为项目进行前期准备和初始化相关配置依赖的地方。

例如上面就是Android Gradle的jar依赖路径

1
apply plugin: 'com.android.application'

自定义插件

1
2
3
4
5
6
7
8
9
10
apply plugin: CustomePlugin

class CustomePlugin implements Plugin<Project>{

void apply(Project project){
project.task('CustomePlugin') << {
println "这是一个通过自定义插件方式创建的任务"
}
}
}

自定义插件必须实现Plugin接口,可以使用 gradlew : projname : CustomeTask来执行这个任务。

更多关于自定义插件的知识这里不涉及。