认识构建工具 Gradle

目录

1. 什么是构建工具?

以前 Android 开发是用 Eclipse, 而 Eclipse 是一种 IDE(Integrated Development Environment,集成开发环境)

eclipse

Eclipse 支持 Java 开发,而 Android 又是基于 Java 语言的,因此 Google 开发了一个叫 ADT(Android Developer Tools)的工具。

从此就可以在 Eclipse 进行 开发、运行、签名、打包等,在某种意义上 ADT 就是我们的构建工具。

自从 Google 推出 Android Studio 之后,就默认使用 Gradle 来作为构建工具了,并且在 Android Studio 2.2 之后就不再更新 ADT 了。

那么问题来了, 到底什么是构建工具?

构建工具是一个把 源代码生成可用的或可执行应用程序的过程 自动化的程序。

比如 Java 生成 Jar 包,或者在 Android 生成 .apk 的过程中,构建工具会对项目进行 编译、运行、签名、打包、依赖管理等一系列操作。

2. Java 中的构建工具

在 Java 语言中,常用的构建工具有 Ant、Maven、Gradle

2.1 Apache Ant

Apache Ant (Another Neat Tool)是一个用 Java 编写的开源的跨平台的项目构建工具。

Ant 不提供依赖管理器,所以需要自己管理外部依赖。但是 Ant 可以和 Apache lvy 很好的集成,lvy 是一个完善且独立的依赖管理器。

2.1.1 Ant 构建脚本

Ant 的一个构建脚本由三个基本元素构成:一个 Project、多个 Target 和可用的 Task。

ant.jpg

开发者需要显示的指定每一个任务,每个任务包含一组由 XML 编码的指令,必须在指令中明确告诉 Ant 源码在哪里,结果字节码存储在哪里,如何将这些字节码打包成 JAR 文件。Ant 没有生命周期,你必须定义任务和任务之间的依赖,还需要手工定义任务的执行序列和逻辑关系。这就无形中造成了大量的代码重复。

2.2 Apache Maven

Maven 是 Apache 组织下的一个跨平台的项目管理工具,它主要用来帮助实现项目的构建、测试、打包和部署。

Ant 的灵活性、不便于维护,Maven 选择了约定优于配置的思想,提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。Maven 将构建的过程抽象成一个个的生命周期过程,在不同的阶段使用不同的已实现插件来完成相应的实际工作,这种设计方法极大的避免了设计和脚本编码的重复,极大的实现了复用。

Maven 不仅是一个项目构建工具还是一个项目管理工具。它有约定的目录结构和生命周期,项目构建的各阶段各任务都由插件实现,开发者只需遵照约定的目录结构创建项目,再配置文件中生命项目的基本元素,Maven 就会按照顺序完成整个构建过程。Maven 的这些特性在一定程度上大大减少了代码的重复。

2.3.1 Maven 默认目录布局

Maven default layout

2.3.2 生命周期

Maven 基于构建生命周期的思想。每个项目都确切知道有哪些步骤去执行构建、打包和发布应用程序。

在构建生命周期每一个步骤都是一个阶段,这些阶段会被有序地执行。

下图展示了Maven 构建所预定义的阶段和他们执行的顺序。

Maven build lifecycle

2.3.3 构建脚本

Maven 构建脚本样例,名字是 pom.xml,这里要遵循默认约定,所以 Maven 会去 src/main/java 目录中找源代码

这里指定了一个依赖,声明的类库和传递的依赖会有 Maven 的依赖管理器下载。Maven 预配置从 Maven Center 下载依赖。指定其坐标即可。

2.3 Gradle

其中 Ant 和 Maven 都是基于 xml 来进行描述的,xml 是非常好的层级数据描述语言,但对于描述程序流程和构建逻辑却存在很多不足之处,随着构建脚本的复杂度增加,维护构建代码也越来越困难,脚本也越来越臃肿。

Gradle 是基于 JVM 构建工具的新一代版本

Gradle 使用的是一种基于 Groovy 的领域特定语言(DSL,Domain Specific Language),Groovy 是一门 JVM 语言,它提供了 Java 的语法糖。由于 Groovy 是在 Java 的基础上做了很多改进,所有 Java 语言的特性 Groovy 都支持,但是用起来更加灵活。所以完全可以混写 Java 和 Groovy。

DSL:直接点来讲,其实就是这个语言不通用,只能用于特定的某个领域,俗称“小语言”。因此DSL也是语言。

语法糖: 指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

  • 约定灵活
  • 强大的依赖管理
  • 可扩展的构建
  • 具有表达性的语言和强大的 API
  • 和其他构建工具的集成

3. Gradle 构建脚本

友情提示:本次关于 Gradle 的学习主要是为了学习在 Android Studio 上的使用,所以关于 Gradle 的下载安装就不做赘述,从官网下载,安装,配置变量即可。

每个 Gradle 构建包括三个基本的构建块:项目(projects)、任务(task)和 属性(properties)。每个构建至少包括一个项目,一个项目包括一个或多个任务,项目和任务都是有很多属性来控制构建过程。

3.1 项目 Project

每个 Gradle 构建都是以一个脚本开始的。Gradle 构建默认脚本的名字是 build.gradle。当在执行 gradle 命令时,Gradle会去寻找名为 build.gradle 的文件。

每个 Gradle 脚本至少定义一个 project。当开始构建过程后, Gradle 基于配置实例化 org.gradle.Project 类,并且能够通过 project 变量使其隐式可用。下图是 Project 类的 API 接口和重要的方法。

img

一个项目可以创建新的任务(task)、添加依赖(dependencies)和配置(configurations)、应用插件和其他脚本。很多属性比如 name 和 description 都可以通过 getter 和 setter 方法来访问。

Gradle 支持多项目构建,随着软件系统的复杂, 可以分解成一个个功能性模块,模块之间可以相互依赖,而每个模块也都有自己的独立的 build.gradle 脚本。

3.2 任务 Task

task 是 独立原子性工作,一个动作就是任务的原子工作

很多时候一个任务需要在另一个任务之后执行,尤其是当一个任务的输入依赖于另一个任务的输出时,比如项目打包成 JAR 文件之前先要编译成 class 文件,来看下 Gradle API 中任务的表示:org.gradle.api.Task 接口。

Task

从 Hello World 开始了解,

在 build.gradle 中,定义一个独立的原子性工作,task。

task helloWorld {
    doLast {
        println("Groovy")
    }

    doFirst {
        print("Hello ")
    }
}

运行该 task:

$ gradle helloWorld
输出 :Hello Groovy

通过 -q 定义可选命令行选项 quiet,告诉 Gradle 只输出该 task 相关的信息

列出所有可用的 task gradle -q tasks

3.3 属性

每个 Project 和 Task 实例都提供了 setter 和 getter 方法来访问属性,比如:

task helloWorld {
    description = "This is a task."
    println(helloWorld.description)
}

3.4 外部属性

外部属性一般存储在键值对当中,要添加一个属性,需要用 ext 命名空间。

ext {
    aValue = 123
}

task helloWorld {
    println project.ext.aValue
}

运行任务会输出 123

外部属性也可以定义在一个属性文件当中。通过 /gradle 或者根目录下的 gradle.properties 文件来定义属性,这些属性可以通过 project 实例来访问。 注意/.gradle目录下只能有一个 Gradle 属性文件,哪怕是有多个项目。 在这份属性文件生命的属性对所有项目可用

在 gradle.properties 中定义个属性

testValue = 1234

在项目中可以访问这个变量

task helloWorld {
    println project.testValue
    println "test : $testValue"
}

3.5 依赖

Gradle DSL 配置 dependencies 用来给配置添加一个多或多个依赖,Gredle支持各种不同类型的依赖

类型 描述
外部模块依赖 依赖仓库中的外部类库,包括她所提供的元数据
项目依赖 依赖其他 Gradle 项目
文件依赖 依赖文件系统中的一系列文件
客户端模块依赖 依赖仓库中的外部类库,具有生命元数据的能力
Gradle 运行时依赖 依赖 Gradle API 或者封装 Gradle 运行时的类库

每个 Gradle 项目都有 DependencyHandler 接口实例, 可以通过 getDependencies() 方法来获取依赖处理器的引用。

表格中的每种依赖类型都是通过项目的 dependencies 配置块中的依赖处理器的方法来声明的。每个依赖都是 Dependency 类型的一个实例,group、name、version 和 classifier 这几个属性明确标识了一个依赖。

DependencyHandler

3.5.1 外部模块依赖

在 Gradle 中,外部类库通常以 Jar 文件形式存在,被称为外部模块依赖。表示依赖的模块在项目结构之外。其特点是在仓库中能够通过属性明确的标识。

dependencies {
    api 'io.reactivex.rxjava2:rxandroid:2.1.0'
}

Gradle 不会指定默认的仓库,要使用这个类库还要指定使用的仓库,下面代码块表示使用 Maven Central 下载依赖。

repositories {
    mavenCentral()
}

动态版本声明

如果想使用一个依赖的最新版本,可以使用占位符 last.integration,比如声明 Rxjava 的最新版本

dependencies {
    api 'io.reactivex.rxjava2:rxandroid:latest-integration'
}

或者使用一个 + 号来动态声明版本

dependencies {
    api 'io.reactivex.rxjava2:rxandroid:2.+'
}

不过动态版本最好少用或者不用,可靠的和可重复的构建是最重要的。选择新版本的类库可能会导致构建失败。更糟糕的是,可能在不知情的情况下,引入了不兼容的类库版本和副作用,这样也很难知道原因。所以,声明确切类库版应该成为惯例。

3.5.2 文件依赖

文件依赖也很常见,比如我们把一个 JAR 包放在 lib 目录下,就是文件依赖

dependencies {
    api fileTree(include: ['*.jar'], dir: 'libs')
}

3.5.3 项目依赖

在多项目构建时,可以声明项目依赖,配置 compile 是 Java 插件提供的,比如下面代码块,声明编译时依赖的项目

dependencies{
    complie project(':model')
}

3.6.1 Maven 仓库

Maven 仓库是 Java 项目中最常用的仓库类型之一,在构建脚本声明一个依赖时,其属性可以用来获取它在仓库的确切位置。(Maven 仓库)

image

RepositoryHandler 接口提供了两个方法运行第一预配置的 Maven 仓库 mavenCentral() 和 mavenLocal()

  • Maven Central 仓库,使用 mavenCentral() 方法用来将 Maven Central 引用添加到一系列仓库中

  • 本地 Maven 仓库,使用 mavenLocal() 方法用来在文件系统中关联一个本地的 Maven() 仓库

  • 添加自定义的 Maven 仓库

 repositories {
     mavenCentral()
     mavenLocal()
     maven {
         name 'Custom Maven Repository',
         url 'http://repository.forge.cloudbees.com/release/')
     }
 }

https://mvnrepository.com/repos

4.Gradle 包装器

Gradle Wrapper

只能在自己的机器跑?

一个项目使用了 Gradle,在其他人使用这个项目的时候,还要安装 Gradle。不同的 Gradle 版本有不同特性,还要询问你安装了哪个版本的 Gradle,怎么运行, Mac 和 Windows 安装有什么区别。因为在本机上运行成功再其他机器上运行失败,太平常了。

针对这个问题,Gradle 提供了一个方便而使用的解决方案,就是 Gradle 包装器。它能够让机器在没有安装 Gradle 运行的情况下运行 Gradle 构建(PS:在使用 AS 的时候,我们没有专门安装过 Gradle 吧,就是因为有这个 Gradle Wrapper)

Gradle 包装器让构建脚本运行在一个指定的 Gradle 版本上。它中心仓库自动下载 Gradle 运行的时候,解压再构建。 目标就是创建可靠的、可复用的、与操作系统、系统配置或Gradle版本无关的构建。

4.1配置包装器

配置包装器,只需做两件事:创建一个包装器 和 执行任务生成包装器文件


image

gradleVersion 指定了想要使用的 Gradle 版本,任务执行之后,会在构建脚本同级目录下看到包装器文件

image

(PS:这个层级看起来也很眼熟哈,没错,AS 里面默认使用 Gradle 构建,这些都为我们做好了。)

包装器提供了名为 gradlew 的脚本,使用他们运行构建和使用已安装好的 Gradle 运行构建是一样的。

  • gradle-wrapper.jar 是 Gradle Wrapper 的主体功能包,包含下载 Gradle 版本的代码。
  • gradle-wrapper.properties Wrapper 运行时的配置文件,文件主要指定了该项目需要从哪里下载什么版本的 Gradle,下载后放到哪里。
  • gradlew,gradlew.bat 使用Wrapper 执行构建的 shell 脚本 和 Windows 批处理文件

4.2 Wrapper 的工作流程

gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

4.2 Wrapper 的工作流程

The Wrapper workflow

在我们使用 Gradle 构建的时候,如果没有 Gradle 那么先下载 Gradle 的发行版(从 distributionUrl 这个地址去下载,下载到指定路径 Base+Path)中,下载完成之后,再进行 Gradle 构建直接使用即可。再来看这个文件下的几个属性就很清楚了

  • distributionBase
  • distributionPath
  • zipStoreBase
  • zipStorePath
  • distributionUrl 要下载的 gradle 地址以及版本,gradle-wrapper 会先去 wrapper/dists 目录去找,如果没有对应的版本就会下载。

在 IDEA 的 Settings 有配置根目录 ,所以下载的 Gradle 版本一般在用户目录下的.gradle/wrapper/dists 存放


5. 多项目构建

比如在 Android 的开发中也会用模块化结构来分离业务逻辑,每一个模块都可以作为一个单独的项目来看待。在多项目构建中是通过 settings 文件来声明子项目(模块)的。

5.1 Settings 文件

settings 文件声明了所需的配置来实例化项目的层次结构。在默认情况下,这个文件被命名为 settings.gradle,并且和 build.gradle 文件放在一起。

若想使每个子项目都成为构建的一部分,可以调用带有项目路径参数的 include 方法。比如

//参数是项目路径,不是文件路径
include 'model'
include 'repository', 'web'

上面代码中所提到的项目路径是相对于根目录的项目目录。也可以构建更深层次的项目结构。使用冒号(:)来风格每个子项目的层次结构。

include ':app'

Settings API


img

我们也常用这样的写法来指定模块的目录

include ':skinLibrary'
project(':skinLibrary').projectDir = new File('libThirdParts/skinLibrary')

5.2 子项目配置

在 3.1 介绍了一些 Project API 的属性和方法,同时这个 API 为实现多项目构建也提供了相关的方法。

image

Project API 提供了 allprojects 和 subprojects 方法来定义所有项目或者只有子项目的一些逻辑。

比如:

image

你可以用 allprojects 方法给所有的项目添加属性,由于根项目不需要 Java 插件,你可以使用 subprojects 给所有子项目添加 Java 插件,如下所示:

 allprojects {
        group = 'com.manning.gia'
        version = '0.1'
    }

    subprojects {
        apply plugin: 'java'
    }

    project(':repository') {
        dependencies {
            compile project(':model')
        }
    }

    project(':web') {
        apply plugin: 'war'
        apply plugin: 'jetty'

        repositories {
            mavenCentral()
        }

        dependencies {
            compile project(':repository')
            providedCompile 'javax.servlet:servlet-api:2.5'
            runtime 'javax.servlet:jstl:1.1.2'
        }
    }

5.3 子项目的配置文件

如果多项目构建只有一个 build.gradle 文见 和 settings.gradle,随着项目中的子项目越来越多,那么构建文件也越来越难维护,所以可以为每个项目创建单独的 build.gradle

根目录的 build.gradle 只保留公共配置块即可

allprojects {
    group = 'com.manning.gia'
    version = '0.1'
}
subprojects {
    apply plugin: 'java'
}

repository 子项目的构建代码:

dependencies {
    compile project(':model')
}

6. 参考资料

书籍《实战 Gradle》在线阅读英文版(需梯子)
stormzhang 的公众号 给 Android 初学者的 Gradle 知识普及

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271