Gradle构建过程概览

参考链接:

HelloWorld

假设条件:

此时你已经有了JDK6.0以上版本,并配置了环境变量。 此时已经在你的电脑上安装了Android Studio并且新建了一个最简单的工程。

下载并解压:从Gradle官方网站上下载Gradle的发行包。

gradle-package
    |
    ├─bin       # Gradle可执行文件
    ├─doc       
    |   ├─userguide     # 用户指南(HTML和PDF两种版本)
    |   ├─javadoc       # API文档(包括Javadoc和Groovydoc)
    |   ├─dsl           # DSL参考指南。
    |
    ├─lib       # 库文件(jar包)
    ├─media     # 一些资源文件(比如图标)
    ├─samples   # 大量的样例,包括用户指南里的例子
    ├─src       # 源代码。这些源代码仅供参考,要编译Gradle需要去下载源码发行包。

配置环境变量:

GRADLE_HOME –> ~\gradle\gradle-4.6 PATH –> %GRADLE_HOME%\bin

测试安装结果:可以通过运行gradle命令或者gradle -v来查看是否安装成功。

新建android-gradle目录,并在其中新建文件build.gradle(Gradle自带了Groovy库,因此不需要安装Groovy)

task hello{
    doLast{
        println'Hello World!'
    }
}

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

$ gradle -q hello
>>> Hello World!

这里定义了一个任务(task),任务中执行了打印输出操作,接下来我们看看什么是任务。

项目和任务

在Gradle中有两个比较重要的概念就是项目(project)和任务(task),任何构建都是由一个或多个项目组成,一个项目由多个任务组成。

一个项目代表着什么,取决于你想通过Gradle来做什么。比如,一个项目可能代表着一个JAR库,或者是一个Web应用程序。它也可能代表从其他项目所生成的JAR包组装起来的ZIP文件。一个项目不一定是代表一个要构建的东西,它也可能代表一个要完成的东西,比如把您的应用部署到预发布或生产环境。

一个任务表示构建执行的一些原子工作,比如编译一些类,创建一个JAR包,生成javadoc,或者是把一些档案发布到仓库中。

Gradle Wrapper

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

当我们使用Wrapper启动Gradle的时候会自动检查Gradle相关配置, Wrapper的工作流如下:

Wrapper的工作流

更多Gradle Wrapper的操作请查看官方文档

$ gradle wrapper
:wrapper

BUILD SUCCESSFUL

Total time: 2.804 secs

生成目录和文件如下:

hello
│  build.gradle
│  gradlew
│  gradlew.bat
│
├─.gradle
│  ├─4.6
│  │  ├─fileChanges
│  │  │      last-build.bin
│  │  │
│  │  ├─fileHashes
│  │  │      fileHashes.bin
│  │  │      fileHashes.lock
│  │  │
│  │  └─taskHistory
│  │          taskHistory.bin
│  │          taskHistory.lock
│  │
│  └─buildOutputCleanup
│          buildOutputCleanup.lock
│          cache.properties
│          outputFiles.bin
│
└─gradle
    └─wrapper
            gradle-wrapper.jar
            gradle-wrapper.properties

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

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解压后路径
distributionUrlGradle发行版压缩包的下载地址
zipStoreBase同distributionBase,只不过是存放zip压缩包的
zipStorePath同distributionPath,只不过是存放zip压缩包的

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

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示例

println "HelloWorld!"

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

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:

{ 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的时候,往往会面临一个问题, 这个地方是用一个方法还是用一个闭包?

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文件大多数的作用是配置子工程的。

include ':app'

build.gradle文件

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

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

project和Task

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

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

task customeTask1{
    doFirst{
        println 'customeTask1:doFirst'
    }
    doLast{
        println 'customTask2:doLast'
    }
}

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

task compile << {  //注意这里使用了快捷符号
    println 'compile'
}

task runjar(dependsOn: compile){  //依赖关系
    doLast {
        println 'run jar'
    }
}

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

task ex36Hello << {
    println 'dowLast1'
}

ex36Hello.doFirst{
    println 'dowFrst'
}

ex36Hello.doLast{
    println 'dowLast'
}

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

默认任务

Gradle允许你在构建中使用defaultTasks定义一个或多个的默认任务。

defaultTasks 'clean', 'run'

task clean << {
    println 'Default Cleaning!'
}

task run << {
    println 'Default Running!'
}

task other << {
    println "I'm not a default task!"
}

这样我们执行gradle -q不用指定任务,就会执行默认任务clean和run,在多项目构建中,每一个子项目都可以有它自己的指定的默认任务。如果一个子项目没有指定默认任务,而父项目定义了的话,那么将会使用父项目的。

DAG

Gradle有一个配置阶段和一个执行阶段,可以在配置阶段使用钩子方法来根据条件来检查和配置具体的值,例如:

task distribution << {
    println "We build the zip with version=$version"
}

task release(dependsOn: 'distribution') << {
    println 'We release now'
}

gradle.taskGraph.whenReady {taskGraph ->
    if (taskGraph.hasTask(release)) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

执行gradle -q distribution会发现输出为We build the zip with version=1.0-SNAPSHOT

gradle.taskGraph.whenReady会在任务执行之前,执行并影响上面定义的$version,来根据条件判断赋予特定的值。

Gradle插件

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

二进制插件

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

apply plugin:'java'

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

等价于

apply plugin:org.gradle.api.plugins.JavaPlugin
或者
apply plugin:JavaPlugin

脚本插件

build.gradle文件

apply from:'version.gradle'

...

version.gradle文件

ext{
    versionName = '1.0.0'
    versionCode = 1
}

第三方插件

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

buildscript{
    repositories{
        jcenter()
    }
    dependencies{
        classpath 'com.android.tools.build:gradle:1.5.0'
    }
}

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

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

apply plugin: 'com.android.application'

自定义插件

apply plugin: CustomePlugin

class CustomePlugin implements Plugin<Project>{

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

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

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