安卓-Gradle-指南-全-
安卓 Gradle 指南(全)
原文:
zh.annas-archive.org/md5/f00ea3cb2cb8d986576ff1a526090a0b译者:飞龙
前言
Android 应用的构建过程是一个极其复杂的程序,涉及许多工具。首先,所有资源文件都会在 R.java 文件中进行编译和引用,然后 Java 代码被编译,并由 dex 工具转换为 Dalvik 字节码。这些文件随后被打包成一个 APK 文件,该 APK 文件使用调试或发布密钥进行签名,之后应用才能最终安装到设备上。
手动完成所有这些步骤将是一项既繁琐又耗时的任务。幸运的是,Android Tools 团队一直在为开发者提供处理整个过程的工具,并在 2013 年推出了 Gradle 作为 Android 应用的新首选构建系统。Gradle 的设计使得扩展构建和将其插入现有构建过程变得容易。它提供了一个类似 Groovy 的 DSL 来声明构建和创建任务,并使依赖管理变得简单。此外,它是完全免费和开源的。
到目前为止,大多数 Android 开发者已经转向使用 Gradle,但许多人并不知道如何充分利用它,也不知道仅用几行代码可以完成什么。本书旨在帮助这些开发者,并使他们成为 Gradle 高级用户。本书从 Android 环境中 Gradle 的基础知识开始,接着涵盖依赖、构建变体、测试、创建任务等内容。
本书涵盖的内容
第一章,使用 Gradle 和 Android Studio 入门,解释了 Gradle 为什么有用,如何开始使用 Android Studio,以及 Gradle Wrapper 是什么。
第二章,基本构建自定义,详细介绍了 Gradle 构建文件和任务,并展示了如何对构建过程进行简单的自定义。
第三章,管理依赖,展示了如何使用本地和远程依赖,并解释了与依赖相关的概念。
第四章,创建构建变体,介绍了构建类型和产品风味,解释了它们之间的区别,并展示了如何使用签名配置。
第五章,管理多模块构建,解释了如何管理应用、库和测试模块,并提及了如何将它们集成到构建过程中。
第六章,运行测试,介绍了几个用于单元测试和功能测试的测试框架,如何自动化测试以及如何获取测试覆盖率报告。
第七章,创建任务和插件,解释了 Groovy 的基础知识,并展示了如何创建自定义任务以及如何将它们钩入 Android 构建过程。本章还解释了如何创建可重用的插件。
第八章,设置持续集成,提供了使用最常用的 CI 系统自动构建的指导。
第九章,高级构建自定义,展示了缩小 APK、加快构建过程以及根据密度或平台分割 APK 的技巧和窍门。
您需要为本书准备的内容
要跟随所有示例,您需要能够访问装有 Microsoft Windows、Mac OS X 或 Linux 的计算机。您还需要安装 Java 开发工具包,并且建议您安装 Android Studio,因为大多数章节中都有提到。
本书面向的对象
本书面向希望更好地理解构建系统并成为构建过程大师的 Android 开发者。本书将从头到尾介绍 Gradle 的基础知识,到创建自定义任务和插件,再到自动化构建过程的多个部分。假设您熟悉 Android 平台的开发。
惯例
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和推特用户名应如下显示:“每个 build.gradle 文件代表一个项目。”
代码块应如下设置:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
任何命令行输入或输出都应如下编写:
$ gradlew tasks
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中如下显示:“您可以通过点击启动屏幕上的开始新的 Android Studio 项目来在 Android Studio 中启动一个新项目。”
注意
警告或重要注意事项如下显示。
小贴士
小技巧和窍门如下显示。
读者反馈
我们欢迎读者的反馈。告诉我们您对本书的看法——您喜欢或不喜欢的地方。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中受益的标题。
要向我们发送一般反馈,只需发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及本书的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为本书做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在,您已成为 Packt 书籍的骄傲拥有者,我们有一些东西可以帮助您从购买中获得最大收益。
下载示例代码
您可以从您在www.packtpub.com的账户下载示例代码文件,适用于您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
勘误
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即向我们提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过mailto:copyright@packtpub.com copyright@packtpub.com>与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面所提供的帮助。
问题
如果您对本书的任何方面有问题,您可以通过mailto:questions@packtpub.com questions@packtpub.com>与我们联系,我们将尽力解决问题。
第一章. 使用 Gradle 和 Android Studio 入门
当 Google 引入 Gradle 和 Android Studio 时,他们心中有一些目标。他们希望使代码重用、创建构建变体以及配置和自定义构建过程变得更加容易。在此基础上,他们还希望有良好的 IDE 集成,但又不希望构建系统依赖于 IDE。从命令行或持续集成服务器运行 Gradle 将始终产生与从 Android Studio 运行构建相同的结果。
在本书中,我们将不时提及 Android Studio,因为它经常提供一种更简单的方式来设置项目、处理更改等。如果你还没有安装 Android Studio,你可以从 Android 开发者网站下载它(developer.android.com/sdk/index.html)。
在本章中,我们将涵盖以下主题:
-
了解 Android Studio
-
理解 Gradle 基础知识
-
创建新项目
-
使用 Gradle 包装器入门
-
从 Eclipse 迁移
Android Studio
Android Studio 于 2013 年 5 月由 Google 宣布并发布(作为一个早期访问预览),同时支持 Gradle。Android Studio 基于 JetBrains 的 IntelliJ IDEA,但专门为 Android 开发设计。它对 Linux、Mac OS X 和 Microsoft Windows 免费提供。
与 Eclipse 相比,Android Studio 拥有改进的用户界面设计师、更好的内存监控器、用于字符串翻译的出色编辑器、针对可能的 Android 特定问题的警告以及许多针对 Android 开发者的功能。它还特别为 Android 项目提供了一个项目结构视图,除了 IntelliJ IDEA 中存在的常规项目视图和包视图之外。这个特殊视图以方便的方式将 Gradle 脚本、可绘制资源和其它资源分组。一旦 Android Studio 的稳定版 1.0 发布,Google 就废弃了 Eclipse 的Android 开发者工具(ADT),并建议所有开发者切换到 Android Studio。这意味着 Google 将不再为 Eclipse 提供新功能,所有与 IDE 相关的工具开发现在都集中在 Android Studio 上。如果你仍在使用 Eclipse,现在是时候更换了,否则你可能会落后。
这张截图显示了 Android Studio 对于一个简单的 Android 应用程序项目的样子:

保持更新
Android Studio 有四个不同的更新通道:
-
Canary 版本带来最新的更新,但可能包含一些错误
-
Dev 通道大约每个月更新一次
-
Beta 版本使用功能完整的更新,但这些更新可能仍然包含错误
-
稳定通道(默认通道)提供经过彻底测试的发布版本,应该没有错误
默认情况下,Android Studio 每次启动时都会检查是否有可用的更新,并通知你。
当你第一次启动 Android Studio 时,它会启动一个向导来设置你的环境,并确保你有最新的 Android SDK 和必要的 Google 仓库。它还提供了创建 安卓虚拟设备(AVD)的选项,这样你就可以在模拟器上运行应用。
理解 Gradle 基础
为了使用 Gradle 构建安卓项目,你需要设置一个构建脚本。按照惯例,这个脚本总是被命名为 build.gradle。当你学习基础知识时,你会注意到 Gradle 优先考虑惯例而非配置,并且通常为设置和属性提供默认值。这使得与 Ant 或 Maven 等系统相比,配置更少,更容易开始使用,而 Ant 和 Maven 一直是安卓项目的实际构建系统。尽管如此,你不必绝对遵守这些惯例,因为通常在需要时可以覆盖它们。
Gradle 构建脚本不是用传统的 XML 编写的,而是基于 Groovy 的 领域特定语言(DSL),Groovy 是一种用于 Java 虚拟机(JVM)的动态语言。Gradle 背后的团队认为,使用基于动态语言的声明式、DSL 风格的方法比使用 Ant 的更程序化、自由浮动的风格或许多其他构建系统使用的基于 XML 的方法具有显著优势。
这并不意味着你需要了解 Groovy 才能开始编写构建脚本。它很容易阅读,如果你已经熟悉 Java,学习曲线也不会太陡峭。如果你想开始创建自己的任务和插件(我们将在后面的章节中讨论),对 Groovy 有更深入的了解是有用的。然而,由于它基于 JVM,你可以用 Java 或任何其他 JVM 语言编写自定义插件的代码。
项目和任务
在 Gradle 中,最重要的两个概念是项目和任务。每个构建至少包含一个项目,每个项目包含一个或多个任务。每个 build.gradle 文件代表一个项目。任务在构建脚本内部简单地定义。在初始化构建过程时,Gradle 根据构建文件组装 Project 和 Task 对象。一个 Task 对象由需要按顺序执行的 Action 对象列表组成。Action 对象是一段被执行的代码块,类似于 Java 中的方法。
构建生命周期
执行 Gradle 构建,在最简单的形式中,只是对任务执行操作,这些任务依赖于其他任务。为了简化构建过程,构建工具创建了一个工作流程的动态模型,作为一个有向无环图(DAG)。这意味着所有任务都是依次处理的,并且不可能有循环。一旦任务被执行,它将不会被再次调用。没有依赖关系的任务将始终在其他任务之前运行。依赖关系图是在构建配置阶段生成的。Gradle 构建有三个阶段:
-
初始化:这是创建
Project实例的地方。如果有多个模块,每个模块都有自己的build.gradle文件,则会创建多个项目。 -
配置:在这个阶段,执行构建脚本,为每个项目对象创建和配置所有任务。
-
执行:这是 Gradle 确定哪些任务应该被执行的阶段。应该执行哪些任务取决于启动构建时传递的参数以及当前目录是什么。
构建配置文件
为了让 Gradle 构建项目,总需要有一个 build.gradle 文件。Android 的构建文件有几个必需的元素:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
这是在这里配置实际的构建。在仓库块中,JCenter 仓库被配置为构建脚本的依赖项来源。JCenter 是一个预配置的 Maven 仓库,无需额外设置;Gradle 已经为你准备好了。Gradle 提供了几个直接可用的仓库,并且添加自己的仓库(无论是本地还是远程的)都很简单。
构建脚本块还定义了对 Android 构建工具的依赖,作为一个类路径 Maven 艺术品。这就是 Android 插件的来源。Android 插件提供了构建和测试应用程序所需的一切。每个 Android 项目都需要使用以下行应用 Android 插件:
apply plugin: 'com.android.application'
插件用于扩展 Gradle 构建脚本的特性。将插件应用于项目使得构建脚本能够定义属性和使用插件中定义的任务。
注意
如果你正在构建一个库,你需要应用 'com.android.library'。你无法在同一个模块中使用两者,因为这会导致构建错误。一个模块可以是 Android 应用程序或 Android 库,但不能同时是两者。
当使用 Android 插件时,可以配置 Android 特定的约定,并且只会生成适用于 Android 的任务。以下代码片段中的 Android 块是由插件定义的,并且可以按项目进行配置:
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
这是配置 Android 构建特定部分的区域。Android 插件提供了一个针对 Android 需求定制的 DSL。唯一必需的属性是编译目标和构建工具。编译目标由 compileSdkVersion 指定,是用于编译应用应使用的 SDK 版本。使用最新的 Android API 版本作为编译目标是良好的实践。
build.gradle 文件中有许多可自定义的属性。我们将在第二章基本构建自定义中讨论最重要的属性,并在本书的其余部分探讨更多可能性。
项目结构
与旧版本的 Eclipse 项目相比,Android 项目的文件夹结构已经发生了很大变化。如前所述,Gradle 倾向于使用约定而非配置,这同样适用于文件夹结构。
这是 Gradle 预期的一个简单应用的文件夹结构:
MyApp
├── build.gradle
├── settings.gradle
└── app
├── build.gradle
├── build
├── libs
└── src
└── main
├── java
│ └── com.package.myapp
└── res
├── drawable
├── layout
└── etc.
Gradle 项目通常在根目录有一个额外的层级。这使得在以后添加额外的模块变得更容易。所有应用源代码都放入 app 文件夹中。默认情况下,该文件夹也是模块的名称,不需要命名为 app。例如,如果您使用 Android Studio 创建一个包含移动应用和 Android Wear 智能手表应用的项目,默认情况下模块名称为 application 和 wearable。
Gradle 使用一个称为源集的概念。官方 Gradle 文档解释说,源集是一组源文件,它们一起编译和执行。对于 Android 项目,main 是包含默认版本应用所有源代码和资源的源集。当您开始为 Android 应用编写测试时,您将测试的源代码放入一个名为 androidTest 的单独源集中,该源集只包含测试。
下面是 Android 应用最重要的文件夹的简要概述:
| 目录 | 内容 |
|---|---|
/src/main/java |
应用的源代码 |
/src/main/res |
这些是与应用相关的资源(可绘制元素、布局、字符串等) |
/libs |
这些是外部库(.jar 或 .aar) |
/build |
构建过程输出 |
创建新项目
您可以通过点击启动屏幕上的开始新的 Android Studio 项目或在 IDE 本身导航到文件 | 新建项目…来在 Android Studio 中启动一个新项目。
在 Android Studio 中创建新项目从向导开始,该向导帮助设置一切。第一个屏幕是设置应用程序名称和公司域名。应用程序名称是安装时用作应用名称的名称,默认情况下用作工具栏标题。公司域名与应用程序名称结合使用,以确定包名,这是任何 Android 应用的唯一标识符。如果您希望包名不同,您仍然可以通过点击 编辑 来更改它。您还可以更改项目在您的硬盘上的位置。

在完成向导的所有步骤之前,不会生成任何文件,因为接下来的几个步骤将定义需要创建哪些文件。
Android 不仅在手机和平板电脑上运行,还支持广泛的设备形态,例如电视、手表和眼镜。下一个屏幕帮助您设置项目中想要针对的所有设备形态。根据您的选择,将包括用于开发的依赖项和构建插件。这就是您决定是否只想制作手机和平板电脑应用,或者是否还想包括 Android TV 模块、Android Wear 模块或 Google Glass 模块的地方。您仍然可以在以后添加这些模块,但向导通过添加所有必要的文件和配置使这个过程变得简单。这也是您选择想要支持的 Android 版本的地方。如果您选择低于 21 的 API 版本,Android Support Library(包括 appcompat 库)将自动添加为依赖项。

小贴士
下载示例代码
您可以从 www.packtpub.com 下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
以下屏幕建议添加一个活动并提供许多选项,所有这些选项都会生成代码,使开始工作变得更容易。如果您选择让 Android Studio 为您生成活动,下一步是输入活动类的名称、布局文件和菜单资源,并为活动提供一个标题:

在您完成整个向导后,Android Studio 会根据您在向导期间所做的选择生成活动(activity)和片段(fragment)的源代码。Android Studio 还会生成基本的 Gradle 文件以使项目可构建。您将在项目的顶层找到一个名为 settings.gradle 的文件和一个名为 build.gradle 的文件。在应用模块文件夹内,还有一个 build.gradle 文件。我们将在 第二章 基本构建自定义 中更详细地介绍这些文件的内容和目的。
您现在有几种选项可以在 Android Studio 内触发构建:
-
在 构建 菜单中,您可以点击 构建项目,或者可以使用键盘快捷键,在 PC 上是 Ctrl + F9,在 Mac OS X 上是 Cmd + F9。
-
工具栏有一个与相同的 构建项目 的快捷键
-
列出所有可用 Gradle 任务的 Gradle 工具窗口
在 Gradle 工具窗口中,您可以尝试执行 assembleDebug 来构建,或者执行 installDebug 来在设备或模拟器上安装应用。我们将在本章的下一部分讨论这些任务,该部分涉及 Gradle Wrapper。
开始使用 Gradle Wrapper
Gradle 是一个持续发展的工具,新版本可能会破坏向后兼容性。使用 Gradle Wrapper 是避免问题和确保构建可重复的好方法。
Gradle Wrapper 在 Microsoft Windows 上提供了一个批处理文件,在其他操作系统上提供了一个 shell 脚本。当您运行脚本时,所需的 Gradle 版本将被下载(如果尚未存在)并自动用于构建。背后的想法是,每个需要构建应用的开发人员或自动化系统都可以运行 Wrapper,然后它会处理其余部分。这样,就不需要在开发机器或构建服务器上手动安装正确的 Gradle 版本。因此,也建议将 Wrapper 文件添加到您的版本控制系统中。
运行 Gradle Wrapper 与直接运行 Gradle 并无太大区别。您只需在 Linux 和 Mac OS X 上执行 gradlew,在 Microsoft Windows 上执行 gradlew.bat 而不是常规的 gradle 命令。
获取 Gradle Wrapper
为了方便起见,每个新的 Android 项目都包含 Gradle Wrapper,因此当您创建一个新项目时,您根本不需要做任何事情就可以获取必要的文件。当然,您可以在计算机上手动安装 Gradle 并将其用于您的项目,但 Gradle Wrapper 可以做同样的事情,并确保使用正确的 Gradle 版本。在没有 Android Studio 的情况下使用 Gradle 时,没有不使用 Wrapper 的好理由。
您可以通过导航到项目文件夹并在终端中运行./gradlew –v或从命令提示符中运行gradlew.bat –v来检查 Gradle 包装器是否存在于您的项目中。运行此命令将显示 Gradle 的版本以及有关您设置的额外信息。如果您正在转换 Eclipse 项目,默认情况下包装器将不存在。在这种情况下,您可以使用 Gradle 生成它,但您需要首先安装 Gradle 以获取包装器。
注意
Gradle 下载页面(gradle.org/downloads)提供了二进制文件和源代码的链接,如果您在 Mac OS X 上,可以使用包管理器,如 Homebrew。所有安装说明都在安装页面(gradle.org/installation)上。
在您下载并安装 Gradle 并将其添加到您的PATH后,创建一个包含以下三行的build.gradle文件:
task wrapper(type: Wrapper) {
gradleVersion = '2.4'
}
之后,运行gradle wrapper以生成包装文件。
在 Gradle 的较新版本中,您也可以不修改build.gradle文件就运行包装任务,因为它默认包含为任务。在这种情况下,您可以使用--gradle-version参数指定版本,如下所示:
$ gradle wrapper --gradle-version 2.4
如果您没有指定版本号,包装器配置为使用执行任务时使用的 Gradle 版本。
这些都是由包装任务生成的文件:
myapp/
├── gradlew
├── gradlew.bat
└── gradle/wrapper/
├── gradle-wrapper.jar
└── gradle-wrapper.properties
您可以看到,Gradle 包装器有三个部分:
-
在 Microsoft Windows 上的批处理文件和在 Linux 以及 Mac OS X 上的 shell 脚本
-
一个由批处理文件和 shell 脚本使用的 JAR 文件
-
一个
properties文件
gradle-wrapper.properties文件是包含配置并确定使用 Gradle 哪个版本的文件:
#Sat May 30 17:41:49 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
如果您想使用定制的 Gradle 分发,您可以更改分发 URL。这也意味着您使用的任何应用程序或库都可能具有不同的 Gradle URL,所以在运行包装器之前,请确保您信任这些属性。
Android Studio 非常体贴,当项目中使用的 Gradle 版本不是最新版本时,它会显示一个通知,并会自动建议为您更新它。基本上,Android Studio 会更改gradle-wrapper.properties文件中的配置并触发构建,以便下载最新版本。
注意
Android Studio 使用属性中的信息来确定使用哪个版本的 Gradle,并从您项目中的 Gradle Wrapper 目录运行包装器。然而,它并不使用 shell 或 bash 脚本,因此您不应自定义这些脚本。
运行基本构建任务
在终端或命令提示符中,导航到项目目录并使用tasks命令运行 Gradle 包装器:
$ gradlew tasks
这将打印出所有可用任务的列表。如果您添加--all参数,您将获得每个任务的依赖关系的更详细概览。
注意
在 Microsoft Windows 上,你需要运行 gradlew.bat,在 Linux 和 Mac OS X 上,完整的命令是 ./gradlew。为了简洁起见,我们将在整本书中只写 gradlew。
在开发过程中构建项目时,使用调试配置运行 assemble 任务:
$ gradlew assembleDebug
此任务将创建一个包含应用调试版本的 APK。默认情况下,Gradle 的 Android 插件将 APK 保存到目录 MyApp/app/build/outputs/apk。
小贴士
缩写任务名称
为了避免在终端中输入太多,Gradle 还提供了缩写的驼峰式任务名称作为快捷方式。例如,你可以通过运行 gradlew assDeb 来执行 assembleDebug,或者甚至从命令行界面运行 gradlew aD。
虽然如此,这里有一个需要注意的地方。只有当驼峰式缩写是唯一的时候,这个方法才会工作。一旦有其他任务使用了相同的缩写,这个技巧就不再适用于这些任务了。
除了 assemble 之外,还有三个其他基本任务:
-
check执行所有检查,这通常意味着在连接的设备或模拟器上运行测试 -
build触发assemble和check -
clean清除项目的输出
我们将在第二章基本构建自定义中详细讨论这些任务。
从 Eclipse 迁移
从 Eclipse 项目迁移到基于 Gradle 的项目有两种方式:
-
在 Android Studio 中使用导入向导来自动处理迁移
-
将 Gradle 脚本添加到 Eclipse 项目中,并手动设置一切
大多数项目足够简单,导入向导能够自动转换所有内容。如果向导无法确定某些内容,它甚至可能会给你一些提示,告诉你需要更改什么才能使其工作。
然而,有些项目可能非常复杂,需要手动转换。如果你有一个很大的项目,并且你更喜欢分步转换项目而不是一次性转换,你可以执行 Ant 任务,甚至从 Gradle 执行整个 Ant 构建。这样做,你可以按照你喜欢的节奏进行过渡,并缓慢地转换所有组件。
使用导入向导
要启动导入向导,你需要打开 Android Studio,点击 文件 菜单然后点击 导入项目...,或者在 Android Studio 启动屏幕上点击 导入非 Android Studio 项目。
如果你将包含 JAR 文件或库源的项目进行转换,导入向导会建议用 Gradle 依赖项替换它们。这些依赖项可以来自本地 Google 仓库(例如 Android 支持库)或甚至来自已知的在线仓库中心。如果没有找到匹配的 Google 或在线依赖项,将使用 JAR 文件,就像之前一样。导入向导至少会为你的应用创建一个模块。如果你在项目中还有包含源代码的库,这些库也会被转换为模块。
这就是导入向导的外观:

Studio 创建了一个新文件夹,以确保在转换过程中您不会丢失任何内容,您可以轻松地比较导入向导的结果与原始内容。转换完成后,Android Studio 将打开项目并显示导入摘要。
摘要列出了导入向导决定忽略的任何文件,并且没有复制到新项目中。如果您仍然想包含这些文件,您必须手动将它们复制到新项目中。在忽略的文件下方,摘要显示了导入向导能够用 Gradle 依赖项替换的任何 JAR 文件。Android Studio 尝试在 JCenter 上找到这些依赖项。如果您正在使用 Support Library,它现在包含在通过 SDK 管理器下载到您机器上的 Google 仓库中,而不是 JAR 文件。最后,摘要列出了导入向导移动的所有文件,显示了它们的来源和目的地。
导入向导还会添加三个 Gradle 文件:根目录下的 settings.gradle 和 build.gradle,以及模块中的另一个 build.gradle。
如果您有任何包含源代码的库,导入向导也会将其转换为 Gradle 项目,并根据需要将所有内容链接在一起。
项目现在应该可以无问题地构建,但请注意,您可能需要互联网连接来下载一些必要的依赖项。
对于更复杂的项目可能需要额外的工作,因此接下来我们将探讨如何手动进行转换。
小贴士
Eclipse 导出向导
Eclipse 也有一个导出向导,但由于 Google 的 Android Tools 团队停止了 Eclipse Android 开发工具的开发,因此它已经完全过时。因此,建议始终在 Android Studio 中使用导入向导。
手动迁移
有多种方法可以手动将项目迁移到基于 Gradle 的 Android 项目。不需要更改到新的目录结构,甚至可以从 Gradle 脚本中运行 Ant 脚本。这使得迁移过程非常灵活,并且可以使大型项目的过渡更加容易。我们将在第九章 高级构建自定义中查看运行 Ant 任务。
保持旧项目结构
如果您不想移动文件,可以在项目中保留 Eclipse 文件夹结构。为此,您需要更改源集配置。我们在讨论项目结构时提到了源集。Gradle 和 Android 插件有它们的默认设置,但通常情况下,您也可以覆盖这些设置。
您需要做的第一件事是在项目目录中创建一个 build.gradle 文件。此文件应应用 Android 插件并定义 Gradle 和 Android 插件所需的所有属性。在其最简单的形式中,它看起来像这样:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
然后,你可以从更改源集开始。通常,覆盖主源集以符合 Eclipse 结构看起来像这样:
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest.setRoot('tests')
}
}
在 Eclipse 文件夹结构中,所有源文件都将位于同一个文件夹中,因此你需要告诉 Gradle 所有这些组件都可以在 src 文件夹中找到。你只需要包含你项目中的组件,但添加所有组件并不会造成伤害。
如果你依赖于 JAR 文件,你需要告诉 Gradle 依赖项的位置。假设 JAR 文件位于名为 libs 的文件夹中,配置看起来像这样:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
这行代码将 libs 目录中所有扩展名为 .jar 的文件作为依赖项包含在内。
转换到新的项目结构
如果你决定手动转换为新的项目结构,你需要创建一些文件夹并移动一些文件。此表概述了最重要的文件和文件夹,以及你需要将它们移动到何处以转换为新的项目结构:
| 旧位置 | 新位置 |
|---|---|
src/ |
app/src/main/java/ |
res/ |
app/src/main/res/ |
assets/ |
app/src/main/assets/ |
AndroidManifest.xml |
app/src/main/AndroidManifest.xml |
如果你有任何单元测试,你需要将这些测试的源代码移动到 app/src/test/java/ 以便 Gradle 自动识别。功能测试应位于 app/src/androidTest/java/ 文件夹中。
下一步是在项目的根目录中创建一个 settings.gradle 文件。此文件只需包含一行,其目的是告诉 Gradle 包含 app 模块:
include: ':app'
当这一切准备就绪后,你需要两个 build.gradle 文件才能成功进行 Gradle 构建。第一个文件位于项目的根目录(与 settings.gradle 处于同一级别)并用于定义项目级别的设置:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
这为项目中的所有模块设置了一些属性。第二个 build.gradle 文件位于 app 文件夹中,包含模块特定的设置:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
这些是绝对的基础。如果你有一个不依赖于第三方代码的简单 Android 应用程序,这将足够。如果你有任何依赖项,你也需要将这些依赖项迁移到 Gradle。
迁移库
如果你项目中的任何库包含 Android 特定的代码,这些库也需要使用 Gradle,以便它们能与应用程序模块良好地协同工作。基本原理相同,但你需要使用 Android 库插件而不是 Android 应用程序插件。此过程的详细信息在 第五章 管理多模块构建 中讨论。
摘要
我们从本章开始,探讨了 Gradle 的优势以及为什么它比目前使用的其他构建系统更有用。我们简要地介绍了 Android Studio 以及它是如何通过生成构建文件来帮助我们。
在介绍之后,我们查看了一下 Gradle Wrapper,它使得维护和共享项目变得容易得多。我们在 Android Studio 中创建了一个新项目,现在您已经知道如何将 Eclipse 项目迁移到 Android Studio 和 Gradle,无论是自动还是手动。您还能够在 Android Studio 中使用 Gradle 构建项目,或者直接从命令行界面进行构建。
在接下来的几章中,我们将探讨如何自定义构建过程,以便您可以进一步自动化构建流程,并使维护变得更加简单。我们首先将检查所有标准 Gradle 文件,探索基本的构建任务,并在下一章中定制构建的部分。
第二章. 基本构建自定义
我们一开始是查看 Gradle 的用途,创建和转换 Android 项目。现在,是时候更好地理解构建文件,查看一些有用的任务,并探索 Gradle 和 Android 插件的潜力。
在本章中,我们将探讨以下主题:
-
理解 Gradle 文件
-
开始使用构建任务
-
自定义构建
理解 Gradle 文件
当使用 Android Studio 创建新项目时,默认会生成三个 Gradle 文件。其中两个文件,settings.gradle和build.gradle,最终位于项目的顶层。另一个build.gradle文件在 Android 应用程序模块中创建。这就是 Gradle 文件在项目中的放置方式:
MyApp
├── build.gradle
├── settings.gradle
└── app
└── build.gradle
这三个文件各自有其用途,我们将在接下来的章节中进一步探讨。
设置文件
对于仅包含 Android 应用程序的新项目,settings.gradle看起来像这样:
include ':app'
设置文件在初始化阶段执行,并定义了哪些模块应包含在构建中。在这个例子中,app模块被包含在内。单模块项目不一定需要设置文件,但多模块项目则需要;否则,Gradle 不知道要包含哪些模块。
在幕后,Gradle 为每个设置文件创建一个Settings对象,并从该对象调用必要的方法。您不需要了解Settings类的详细信息,但了解这一点是好的。
注意
对Settings类的完整解释超出了本书的范围。如果您想了解更多信息,可以在 Gradle 文档中找到大量信息(gradle.org/docs/current/dsl/org.gradle.api.initialization.Settings.html)。
顶层构建文件
顶层build.gradle文件是您可以配置需要应用于项目中所有模块的选项的地方。它默认包含两个块:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
buildscript 块是实际构建配置的地方。我们曾在第一章中简要介绍了这一点,即使用 Gradle 和 Android Studio 入门。repositories 块配置了 JCenter 作为仓库。在这种情况下,仓库意味着依赖项的来源,换句话说,是一个我们可以用于我们的应用程序和库的可下载库的列表。JCenter 是一个知名的 Maven 仓库。
dependencies 块用于配置构建过程的依赖项。这意味着您不应该在顶层构建文件中包含您需要用于您的应用程序或库的依赖项。默认定义的唯一依赖项是 Gradle 的 Android 插件。这对于每个 Android 模块都是必需的,因为正是这个插件使得执行 Android 相关任务成为可能。
allprojects块可以用来定义需要应用于所有模块的属性。您甚至可以在allprojects块中创建任务。这些任务随后将在所有模块中可用。
注意
一旦使用allprojects,模块就会与项目耦合。这意味着可能无法在没有主项目构建文件的情况下单独构建模块。一开始可能看起来不是问题,但后来您可能决定将内部库分离成自己的项目,那时您将需要重构您的构建文件。
模块构建文件
模块级别的build.gradle文件包含仅适用于 Android 应用程序模块的选项。它还可以覆盖顶级build.gradle文件中的任何选项。模块构建文件看起来是这样的:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.gradleforandroid.gettingstarted"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
我们将详细探讨三个主要部分。
插件
第一行应用 Android 应用程序插件,该插件在顶级构建文件中配置为依赖项,我们之前已经讨论过。Android 插件由谷歌的 Android Tools 团队编写和维护,提供构建、测试和打包 Android 应用程序和库所需的所有任务。
Android
构建文件的最大部分是android块。此块包含整个 Android 特定配置,通过我们之前应用的 Android 插件提供。
只需的属性是compileSdkVersion和buildToolsVersion:
-
第一个,
compileSdkVersion,是您想要用于编译应用程序的 Android API 版本 -
第二个,
buildToolsVersion,是构建工具和编译器的版本
构建工具包含命令行实用工具,例如 aapt、zipalign、dx 和 renderscript;这些工具用于生成构成您应用程序的各种中间文件。您可以通过 SDK 管理器下载构建工具。
defaultConfig块配置应用程序的核心属性。此块中的属性将覆盖AndroidManifest.xml清单文件中的相应条目:
defaultConfig {
applicationId "com.gradleforandroid.gettingstarted"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
此块中的第一个属性是applicationId。它覆盖了清单文件中的包名,但applicationId和包名之间有一些区别。在 Gradle 被用作默认 Android 构建系统之前,AndroidManifest.xml中的包名有两个用途:它作为应用的唯一标识符,并且用作 R 资源类中的包名。Gradle 使得创建不同版本的应用变得更容易,使用构建变体。例如,制作免费版和付费版非常容易。这两个版本需要具有不同的标识符,以便在 Google Play Store 上作为不同的应用出现,并且可以同时安装。然而,源代码和生成的 R 类必须始终保留相同的包名。否则,所有源文件都需要根据你构建的版本进行更改。这就是为什么 Android 工具团队已经解耦了这两个不同的包名用法。在清单文件中定义的包名继续在你的源代码和 R 类中使用,而设备和 Google Play 用作唯一标识符的包名现在被称为应用程序 ID。随着我们开始尝试不同的构建类型,这个应用程序 ID 将变得更有趣。
defaultConfig中的下一个两个属性是minSdkVersion和targetSdkVersion。这两个属性看起来应该很熟悉,因为它们始终在<uses-sdk>元素中作为清单的一部分被定义。minSdkVersion设置用于配置运行应用所需的最小 API 级别。targetSdkVersion设置通知系统该应用已在特定的 Android 版本上进行了测试,并且操作系统不需要启用任何向前兼容的行为。这与我们之前看到的compileSdkVersion没有关系。
versionCode和versionName在清单文件中也具有相同的功能,并为你的应用定义了一个版本号和一个用户友好的版本名称。
构建文件中的所有值都将覆盖清单文件中的值。因此,如果你在build.gradle中定义了它们,就不需要在清单文件中定义它们。如果构建文件不包含值,则将使用清单值作为后备。
buildTypes块是定义如何构建和打包你的应用不同构建类型的部分。我们将在第四章创建构建变体中详细探讨构建类型。
依赖项
dependencies 块是标准 Gradle 配置的一部分(这就是为什么它放在 android 块之外),并定义了一个应用程序或库的所有依赖项。默认情况下,一个新的 Android 应用程序依赖于 libs 目录中的所有 JAR 文件。根据你在新项目向导中选择的选项,它可能还依赖于 AppCompat 库。我们将在第三章管理依赖项中讨论依赖项。
开始使用任务
要了解项目上可用的任务,你可以运行 gradlew tasks,这将打印出所有可用任务的列表。在一个新创建的 Android 项目中,这包括 Android 任务、构建任务、构建设置任务、帮助任务、安装任务、验证任务和其他任务。如果你想看到任务及其依赖项,你可以运行 gradlew tasks --all。可以进行任务的干运行,这将打印出运行特定任务时执行的所有步骤。这种干运行实际上不会执行这些步骤,因此这是一种安全的方式来查看运行特定任务时可以期待发生什么。你可以通过添加参数 -m 或 --dry-run 来进行干运行。
基础任务
Gradle 的 Android 插件使用了 Java 基础插件,该插件又使用了基础插件。这些添加了标准生命周期任务和一些常见的约定属性。基础插件定义了 assemble 和 clean 任务,Java 基础插件定义了 check 和 build 任务。这些任务在基础插件中没有实现,也不执行任何操作;它们用于定义插件约定,这些插件添加了实际执行工作的任务。
这些任务的约定是:
-
assemble组装项目的输出(s) -
clean清理项目的输出 -
check运行所有检查,通常是单元测试和仪器测试 -
build运行assemble和check
Java 基础插件还添加了源集的概念。Android 插件基于这些约定构建,因此暴露了经验丰富的 Gradle 用户习惯看到的任务。在这些基础任务之上,Android 插件还添加了许多特定于 Android 的任务。
Android 任务
Android 插件扩展了基础任务并实现了它们的行为。这就是在 Android 环境中任务所做的事情:
-
assemble为每个构建类型创建一个 APK -
clean删除所有构建工件,例如 APK 文件 -
check执行 Lint 检查,如果 Lint 检测到问题则可以终止构建 -
build运行assemble和check
默认情况下,assemble 任务依赖于 assembleDebug 和 assembleRelease,如果你添加了更多构建类型,则还有更多任务。这意味着运行 assemble 将触发每个构建类型的构建。
除了扩展这些任务外,Android 插件还添加了一些新的任务。这些是最重要的新任务:
-
connectedCheck在连接的设备或模拟器上运行测试 -
deviceCheck是其他插件在远程设备上运行测试的占位符任务 -
installDebug和installRelease将特定版本安装到连接的设备或模拟器 -
所有
install任务都有对应的uninstall任务
build 任务依赖于 check,但不依赖于 connectedCheck 或 deviceCheck。这是为了确保常规检查不需要连接的设备或正在运行的模拟器。运行检查任务会生成一个包含所有警告和错误的 Lint 报告,其中包含详细说明和相关文档的链接。此报告可以在 app/build/outputs 中找到,并称为 lint-results.html。它看起来像这样:

当您构建发布版本时,Lint 会检查可能导致应用程序崩溃的致命问题。如果找到任何问题,它将中止构建并将错误打印到命令行界面。Lint 还会在 app/build/outputs 目录中生成一个名为 lint-results-release-fatal.html 的报告。如果您有多个问题,查看 HTML 报告比在命令行界面中来回滚动更愉快。提供的链接也非常有用,因为它们会带您到问题的详细解释。
Android Studio 内部
您不一定要从命令行界面运行 Gradle 任务。Android Studio 有一个包含所有可用任务的工具窗口。此工具窗口称为 Gradle,看起来像这样:

从此工具窗口,您可以通过双击其名称来运行任务。您可以在 Gradle 控制台 工具窗口中跟踪任何正在运行的任务的进度。如果您找不到这些工具窗口,您可以在 视图 菜单下的 工具窗口 中打开它们。这是 Gradle 控制台工具窗口的外观:

您还可以在 Android Studio 内部的命令行界面中运行任务,因此如果您喜欢,可以在 IDE 内完成所有与应用程序相关的操作。要运行命令,您需要打开 终端 工具窗口。这是一个完整的终端,因此您可以从它运行任何命令。您可能需要首先导航到项目的顶层,以便使用 Gradle 包装器。
小贴士
更改 Android Studio 终端
您可以在 Android Studio 内部配置终端以使用不同的 shell。例如,在 Microsoft Windows 上,默认情况下终端是命令提示符。如果您更喜欢使用 Git Bash(或任何其他 shell),请打开 Android Studio 设置(在 文件 和 设置 之下)并查找 终端。在那里您可以更改 shell 路径。对于 Microsoft Windows 上的 Git Bash,它看起来像这样:C:\Program Files (x86)\Git\bin\sh.exe --login -i。
自定义构建
有很多种方法可以自定义构建过程,当你正在 Android Studio 中编辑构建文件时,建议始终与 Gradle 文件同步项目,无论你正在自定义什么。当你开始添加依赖项或 BuildConfig 字段时,这尤其重要,我们很快就会讨论这个问题。
当你编辑 settings.gradle 或 build.gradle 时,Android Studio 将在编辑器中显示一条消息,并且可以通过导航到 工具 | Android | 与 Gradle 文件同步项目 或工具栏中的相应按钮来在任何时候触发同步。

在底层,Android Studio 同步实际上运行 generateDebugSources 任务来生成所有必要的类,基于构建文件中的配置。
操作清单条目
我们已经看到,可以直接从构建文件而不是在清单文件中配置 applicationId、minSdkVersion、targetSdkVersion、versionCode 和 versionName。还有一些其他属性可以操作:
-
testApplicationId是仪器测试 APK 的应用程序 ID -
testInstrumentationRunner是用于运行测试的 JUnit 测试运行器的名称(参见第六章,运行测试) -
signingConfig(参见第四章,创建构建变体) -
proguardFile和proguardFiles(参见第九章,高级构建自定义)
在 Android Studio 内部
除了在构建文件中手动进行更改外,你还可以在 Android Studio 的 项目结构 对话框中更改基本设置。你可以从 文件 菜单打开对话框,它允许你编辑项目范围的设置和每个模块的设置。对于每个 Android 模块,你可以更改标准的 Android 插件属性和所有清单属性。在下面的屏幕截图中,你可以看到在 项目结构 对话框中应用程序模块发布版本的属性:

注意,如果你在 项目结构 对话框中进行了任何更改,Android Studio 将将更改写入 Gradle 构建配置文件。
BuildConfig 和资源
自 SDK 工具版本 17 以来,构建工具生成一个名为 BuildConfig 的类,其中包含一个根据构建类型设置的 DEBUG 常量。如果你只想在调试时运行某些代码,例如日志记录,这很有用。通过 Gradle 可以扩展该文件,以便你可以拥有在调试和发布中包含不同值的常量。
这些常量对于切换功能或设置服务器 URL 等很有用:
android {
buildTypes {
debug {
buildConfigField "String", "API_URL", "\"http://test.example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "true"
}
release {
buildConfigField "String", "API_URL", "\"http://example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "false"
}
}
}
字符串值周围需要转义的双引号,以便将其生成为一个实际的字符串。在添加 buildConfigField 行之后,就可以在真实的 Java 代码中使用 BuildConfig.API_URL 和 BuildConfig.LOG_HTTP。
最近,Android 工具团队也添加了以类似方式配置资源的功能:
android {
buildTypes {
debug {
resValue "string", "app_name", "Example DEBUG"
}
release {
resValue "string", "app_name", "Example"
}
}
}
在这里不需要转义的双引号,因为资源值默认总是用 value="" 包裹。
项目级设置
如果你在一个项目中有一个以上的 Android 模块,将设置应用到所有模块而不必手动更改每个模块的构建文件可能很有用。我们已经在生成的顶层构建文件中看到了如何使用 allprojects 块来定义仓库,你也可以使用相同的策略来应用 Android 特定的设置:
allprojects {
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
}
但是,这仅在你所有的模块都是 Android 应用项目时才有效,因为你需要应用 Android 插件来访问 Android 特定的设置。实现此行为更好的方法是,在顶层构建文件中定义值,然后在模块中应用它们。在 Gradle 中,可以在 Project 对象上添加额外的临时属性。这意味着任何 build.gradle 文件都可以定义额外的属性,这通常发生在 ext 块中。
你可以在顶层构建文件中添加一个带有自定义属性的 ext 块:
ext {
compileSdkVersion = 22
buildToolsVersion = "22.0.1"
}
这使得在模块级构建文件中使用 rootProject 来使用属性成为可能:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
项目属性
之前示例中的 ext 块是定义额外属性的一种方式。你可以使用属性来动态地自定义构建过程,我们将在第七章创建任务和插件中开始编写自定义任务时使用它们。定义属性有几种方法,但我们只会查看最常用的三种:
-
ext块 -
gradle.properties文件 -
-P命令行参数
下面是一个包含这三种添加额外属性方式的 build.gradle 文件示例:
ext {
local = 'Hello from build.gradle'
}
task printProperties << {
println local // Local extra property
println propertiesFile // Property from file
if (project.hasProperty('cmd')) {
println cmd // Command line property
}
}
这是伴随的 gradle.properties 文件(在同一文件夹中):
propertiesFile = Hello from gradle.properties
注意
在示例中,我们创建了一个新的任务。我们将在第七章创建任务和插件中查看任务并解释语法,创建任务和插件。
如果你使用命令行参数运行 printProperties 任务,输出将看起来像这样:
$ gradlew printProperties -Pcmd='Hello from the command line'
:printProperties
Hello from build.gradle
Hello from gradle.properties
Hello from the command line
多亏了自定义属性,改变构建配置就像更改单个属性一样简单,甚至只需添加一个命令行参数即可。
注意
可以在顶层构建文件和模块构建文件中定义属性。如果模块定义了一个在顶层文件中已经存在的属性,它将简单地覆盖它。
默认任务
如果你运行 Gradle 而没有指定任务,它将运行help任务,该任务会打印有关如何使用 Gradle 的一些信息。这是因为帮助任务被设置为默认任务。可以覆盖默认任务,并在每次执行 Gradle 而不明确指定任务时运行一个非常常见的任务,甚至多个任务。
要指定默认任务,请将此行添加到顶级build.gradle文件中:
defaultTasks 'clean', 'assembleDebug'
现在,当你不带任何参数运行 Gradle 包装器时,它将运行clean和assembleDebug。通过运行tasks任务并过滤输出,很容易看到哪些任务被设置为默认任务。
$ gradlew tasks | grep "Default tasks"
Default tasks: clean, assembleDebug
摘要
在本章中,我们详细研究了 Android Studio 自动生成的不同 Gradle 文件。你现在可以自己创建构建文件,并添加所有必需的字段以及配置关键属性。
我们从基本的构建任务开始,学习了 Android 插件如何在基础插件的基础上构建,并扩展了新的 Android 特定任务。我们还看到了如何从命令行界面和 Android Studio 内部运行构建任务。
在本章的最后部分,我们探讨了影响构建输出的一些方法,以及配置构建过程本身部分的方法。
在过去的几年里,Android 开发者生态系统得到了巨大的发展,许多有趣的库都变得可供每个人使用。在下一章中,我们将探讨几种向项目添加依赖项的方法,以便我们可以利用这些丰富的资源。
第三章:管理依赖项
依赖管理是 Gradle 真正发光的领域之一。在最佳情况下,您只需在构建文件中添加一行,Gradle 就会从远程仓库下载依赖项,并确保其类对您的项目可用。Gradle 甚至更进一步。如果您的项目依赖项有自己的依赖项,Gradle 将解决这些依赖项,并处理一切。这些依赖项的依赖项被称为传递依赖项。
本章介绍了依赖管理的概念,并解释了将依赖项添加到 Android 项目的多种方式。以下是我们将要讨论的主要主题:
-
仓库
-
本地依赖项
-
依赖概念
仓库
当我们讨论依赖项时,通常指的是外部依赖项,例如由其他开发者提供的库。手动管理依赖项可能是一项繁琐的工作。您需要找到库,下载 JAR 文件,将其复制到您的项目中,并引用它。通常这些 JAR 文件在其名称中没有版本号,因此您需要记住自己添加它,以便知道何时更新。您还需要确保库存储在源控制系统中,这样团队成员就可以在没有手动下载依赖项的情况下与代码库一起工作。
使用仓库可以解决这些问题。仓库可以看作是一组文件的集合。Gradle 默认不会为您的项目定义任何仓库,因此您需要将它们添加到repositories块中。如果您使用 Android Studio,这会为您完成。我们在前面的章节中简要提到了repositories块;它看起来像这样:
repositories {
jcenter()
}
Gradle 支持三种不同类型的仓库:Maven、Ivy 和静态文件或目录。依赖项在构建执行阶段从仓库中检索。Gradle 还保留了一个本地缓存,因此特定版本的依赖项只需下载到您的机器一次。
依赖项由三个元素标识:组、名称和版本。组指定了创建库的组织,通常是一个反向域名。名称是库的唯一标识符。版本指定了您想要使用的库版本。使用这三个元素,可以在dependencies块中使用以下结构声明依赖项:
dependencies {
compile 'com.google.code.gson:gson:2.3'
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
这是一种简写形式,完整的 Groovy 映射表示法看起来像这样:
dependencies {
compile group: 'com.google.code.gson', name: 'gson', version: '2.3'
compile group: 'com.squareup.retrofit', name: 'retrofit' version: '1.9.0'
}
注意
依赖项的唯一必需字段是名称。组和版本是可选元素。尽管如此,建议添加组以提高清晰度,并添加版本以确保库不会自动更新,这可能会导致构建失败。
预配置的仓库
为了方便起见,Gradle 已经预配置了三个 Maven 仓库:JCenter、Maven Central 和本地 Maven 仓库。要将它们包含到你的构建脚本中,你需要包含以下这些行:
repositories {
mavenCentral()
jcenter()
mavenLocal()
}
Maven Central 和 JCenter 是两个知名的在线仓库。没有理由同时使用它们,并且始终建议使用 JCenter,这也是使用 Android Studio 创建的 Android 项目的默认仓库。JCenter 是 Maven Central 的超集,所以当你切换时,你可以保留已经定义的依赖项不变。此外,它支持 HTTPS,而 Maven Central 不支持。
本地 Maven 仓库是你使用过的所有依赖项的本地缓存,你也可以添加自己的依赖项。默认情况下,仓库可以在主目录下的一个名为.m2的文件夹中找到。在 Linux 或 Mac OS X 上,路径是~/.m2。在 Microsoft Windows 上,它是%UserProfile%\.m2。
除了这些预配置的仓库外,你还可以添加其他公共的,甚至是私有的仓库。
远程仓库
一些组织创建了有趣的插件或库,并倾向于将它们托管在自己的 Maven 或 Ivy 服务器上,而不是发布到 Maven Central 或 JCenter。要将这些仓库添加到你的构建中,你只需要将 URL 添加到一个maven块中。
repositories {
maven {
url "http://repo.acmecorp.com/maven2"
}
}
对于 Ivy 仓库也是如此。Apache Ivy 是 Ant 世界中流行的依赖管理器。Gradle 支持这些仓库的格式与 Maven 仓库使用的格式相同。将仓库 URL 添加到ivy块中,你就可以开始了:
repositories {
ivy {
url "http://repo.acmecorp.com/repo"
}
}
如果你的组织运行自己的仓库,那么它很可能是受保护的,并且你需要凭证来访问它。这是为仓库添加凭证的方法:
repositories {
maven {
url "http://repo.acmecorp.com/maven2"
credentials {
username 'user'
password 'secretpassword'
}
}
}
Maven 和 Ivy 的方法在这里也是一样的。你可以在 Ivy 仓库的配置中添加一个格式相同的credentials块。
小贴士
存储凭证
将凭证存储在构建配置文件中不是一个好主意。构建配置文件是纯文本,并且会被提交到源代码控制系统中。一个更好的主意是使用一个单独的 Gradle 属性文件,正如我们在第二章中看到的,基本构建自定义。
本地仓库
你可以在自己的硬盘或网络驱动器上运行 Maven 和 Ivy 仓库。要将这些添加到构建中,你只需要配置到驱动器上位置的相对或绝对路径的 URL:
repositories {
maven {
url "../repo"
}
}
新的 Android 项目默认依赖于 Android Support Library。当使用 SDK 管理器安装 Google 仓库时,会在你的硬盘上创建两个 Maven 仓库ANDROID_SDK/extras/google/m2repository和ANDROID_SDK/extras/android/m2repository。这就是 Gradle 获取 Google 提供的库的地方,例如 Android Support Library 和 Google Play Services。
您还可以使用 flatDirs 将常规目录作为存储库添加,这使您能够在 dependency 块中添加该目录中的文件。
repositories {
flatDir {
dirs 'aars'
}
}
在本章的后面部分,当我们讨论库项目时,我们将查看一个示例,说明如何使用它。
本地依赖
在某些情况下,您可能仍然需要手动下载 JAR 文件或本地库。也许您想创建自己的库,您可以在几个项目中重复使用它,而无需将其发布到公共或私有仓库。在这些情况下,无法使用任何在线资源,您将不得不使用不同的方式来添加依赖项。我们将描述如何使用文件依赖项,如何包含本地库,以及如何在您的项目中包含库项目。
文件依赖
要将 JAR 文件作为依赖项添加,您可以使用 Gradle 提供的 files 方法。它看起来是这样的:
dependencies {
compile files('libs/domoarigato.jar')
}
如果您有很多 JAR 文件,这可能会变得很繁琐,所以一次性添加整个文件夹可能更容易:
dependencies {
compile fileTree('libs')
}
默认情况下,新创建的 Android 项目将有一个名为 libs 的文件夹,并声明它用于依赖项。而不是简单地依赖文件夹中的所有文件,这里有一个过滤器确保只使用 JAR 文件:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
这意味着在 Android Studio 中创建的任何 Android 项目中,您可以将 JAR 文件放入 libs 文件夹,它将自动包含在编译类路径和最终的 APK 中。
原生库
用 C 或 C++ 编写的库可以编译成特定平台的本地代码。这些库通常由几个 .so 文件组成,每个平台一个。Android 插件默认支持本地库,您需要做的只是在该模块级别创建一个名为 jniLibs 的目录,并为每个平台创建子目录。将 .so 文件放入相应的目录,然后您就可以使用了。
您的结构应该如下所示:
app
├── AndroidManifest.xml
└── jniLibs
├── armeabi
│ └── nativelib.so
├── armeabi-v7a
│ └── nativelib.so
├── mips
│ └── nativelib.so
└── x86
└── nativelib.so
如果这个约定不适合您,您可以直接在构建文件中设置位置:
android {
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
}
}
库项目
如果您想共享一个使用 Android API 或包含 Android 资源的库,您需要创建一个库项目。库项目通常与应用程序项目表现相同。您可以使用相同的任务来构建和测试库项目,并且它们可以有不同的构建变体。区别在于输出。应用程序项目生成一个可以在 Android 设备上安装和运行的 APK,而库项目生成一个 .aar 文件。这个文件可以用作 Android 应用程序项目的库。
创建和使用库项目模块
与应用 Android 应用程序插件不同,构建脚本应用 Android 库插件:
apply plugin: 'com.android.library'
在您的应用程序中包含库项目有两种方式。一种是将它作为项目内部的一个模块;另一种是创建一个 .aar 文件,该文件可以在多个应用程序中重复使用。
如果你将库项目作为你的项目中的一个模块设置,你需要将模块添加到 settings.gradle 中,并将其作为依赖项添加到应用程序模块中。设置文件应该看起来像这样:
include ':app', ':library'
在这种情况下,库模块被命名为 library,这对应于一个具有相同名称的文件夹。要在 Android 模块中使用库,需要在 Android 模块的 build.gradle 文件中添加一个依赖项:
dependencies {
compile project(':library')
}
这将把库的输出包含在应用程序模块的类路径中。我们将在第五章 管理多模块构建 中更详细地探讨这种方法。
使用 .aar 文件
如果你创建了一个你希望在多个 Android 应用程序中重用的库,你可以构建一个 .aar 文件,并将其作为依赖项添加到你的项目中。当构建库时,.aar 文件将生成在模块目录的 build/output/aar/ 文件夹中。要将 .aar 文件作为依赖项添加,你需要在应用程序模块中创建一个文件夹,将 .aar 文件复制到其中,并将该文件夹作为仓库添加:
repositories {
flatDir {
dirs 'aars'
}
}
这将使得可以添加该文件夹内的任何文件作为依赖项。你可以按照以下方式引用依赖项:
dependencies {
compile(name:'libraryname', ext:'aar')
}
这告诉 Gradle 查找具有特定名称且具有 .aar 扩展名的库。
依赖项概念
即使你可能今天不需要使用它们,也有一些与依赖项相关的概念值得理解。其中之一是配置的概念,它解释了我们在这章中一直使用的用于依赖项的 compile 关键字。
配置
有时你可能需要与仅在某些设备上存在的 SDK 一起工作,例如,一个特定供应商的蓝牙 SDK。为了能够编译代码,你需要将 SDK 添加到你的编译类路径中。不过,你不需要将 SDK 包含在 APK 中,因为它已经存在于设备上了。这就是依赖配置发挥作用的地方。
Gradle 将依赖项分组到配置中,这些配置只是命名文件集。这些是 Android 应用程序或库的标准配置:
-
compile -
apk -
provided -
testCompile -
androidTestCompile
compile 配置是默认配置,包含编译主应用程序所需的所有依赖项。此配置中的所有内容不仅被添加到类路径中,还被添加到生成的 APK 中。
apk 配置中的依赖项只会添加到包中,而不会添加到编译类路径中。provided 配置则相反,其依赖项不会被打包。这两个配置仅接受 JAR 依赖项。尝试将库项目添加到它们中会导致错误。
最后,testCompile 和 androidTestCompile 配置添加了专门用于测试的额外库。这些配置在运行与测试相关的任务时使用,当添加测试框架如 JUnit 或 Espresso 时非常有用。你只希望这些框架存在于测试 APK 中,而不是发布 APK 中。
除了这些标准配置之外,Android 插件还为每个构建变体生成配置,使得可以将依赖项添加到 debugCompile、releaseProvided 等配置中。如果你只想将日志框架添加到调试构建中,这非常有用。你可以在第四章 创建构建变体 中找到更多相关信息。
语义版本控制
版本控制是依赖项管理的重要方面。添加到 JCenter 等仓库的依赖项假定遵循一组版本控制规则,称为语义版本控制。在语义版本控制中,版本号始终具有 major.minor.patch 的格式,并且数字根据以下规则递增:
-
当你进行不兼容的 API 变更时,主版本号会上升
-
当你以向后兼容的方式添加功能时,次要版本会更新
-
当你进行错误修复时,补丁版本会增加
动态版本
在某些情况下,你可能希望在构建你的应用程序或库时每次都获取依赖项的最新版本。实现这一目标的最佳方式是使用动态版本。应用动态版本有几种方法,以下是一些示例:
dependencies {
compile 'com.android.support:support-v4:22.2.+'
compile 'com.android.support:appcompat-v7:22.2+'
compile 'com.android.support:recyclerview-v7:+'
}
在第一行,我们告诉 Gradle 获取最新的补丁版本。在第二行,我们指定我们想要获取每个新的次要版本,并且它至少要是次要版本 2。在最后一行,我们告诉 Gradle 总是获取库的最新版本。
使用动态版本时应该小心。如果你允许 Gradle 选择最新版本,它可能会选择一个不稳定的依赖项版本,导致构建失败。更糟糕的是,你可能会在构建服务器和个人机器上得到依赖项的不同版本,导致你的应用程序行为不一致。
当你在构建文件中尝试使用动态版本时,Android Studio 会警告你有关动态版本可能存在的问题,如以下截图所示:

Android Studio 内部
添加新依赖项最简单的方法是使用 Android Studio 的 项目结构 对话框。从 文件 菜单打开对话框,并导航到 依赖项 选项卡以查看当前依赖项的概述:

从此对话框,你可以通过点击绿色加号图标添加新的依赖项。你可以添加其他模块、文件,甚至可以在 JCenter 中搜索库:

使用 Android Studio 对话框可以轻松地查看项目中依赖项的概览,并添加新的库。你不需要手动向 build.gradle 文件中添加行,并且可以直接从 IDE 中搜索 JCenter。
摘要
在本章中,我们探讨了向 Android 项目添加依赖项的几种方法。我们学习了仓库、它们可能出现的所有形式,以及我们如何在不需要使用仓库的情况下依赖文件。
现在,你也已经了解了一些关于依赖项的重要概念,即配置、语义版本和动态版本。
我们已经多次提到构建变体,在下一章中,我们将最终解释什么是构建变体,以及它们为什么有用。构建变体可以使开发、测试和分发应用变得更加容易。了解变体的工作原理可以显著加快你的开发和分发过程。
第四章:创建构建变体
当你在开发一个应用时,你通常有几个不同的版本。最常见的情况是,你有一个用于手动测试应用并确保其质量的预发布版本,以及一个生产版本。这些版本通常有不同的设置。例如,预发布 API 的 URL 可能不同于生产 API。此外,你可能有一个免费的基本版本的应用,以及一个具有一些额外功能的付费版本。在这种情况下,你已经在处理四个不同的版本:预发布免费、预发布付费、生产免费和生产付费。为每个版本设置不同的配置可能会变得非常复杂。
Gradle 有一些方便且可扩展的概念来解决这个常见问题。我们之前已经提到了 Android Studio 为每个新项目创建的debug和release构建类型。还有一个叫做产品风味的概念,它为管理应用或库的多个版本提供了更多的可能性。构建类型和产品风味总是结合使用,使得处理预发布和发布应用免费和付费版本的情况变得容易。将构建类型和产品风味结合的结果称为构建变体。
我们将从这个章节开始,先看看构建类型,它们如何让开发者的生活变得更轻松,以及如何充分利用它们。然后,我们将讨论构建类型和产品风味的区别以及它们的使用方法。我们还将探讨签名配置,这是发布应用所必需的,以及我们如何为每个构建变体设置不同的签名配置。
在本章中,我们将涵盖以下主题:
-
构建类型
-
产品风味
-
构建变体
-
签名配置
构建类型
在 Gradle 的 Android 插件中,构建类型用于定义应用或库应该如何构建。每个构建类型都可以指定是否包含调试符号,应用程序 ID 必须是什么,是否应该删除未使用的资源,等等。你可以在buildTypes块内定义构建类型。这是由 Android Studio 创建的构建文件中的标准buildTypes块的样子:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
新模块的默认build.gradle文件配置了一个名为release的构建类型。这个构建类型除了禁用删除未使用资源(通过将minifyEnabled设置为false)和定义默认 ProGuard 配置文件的位置之外,没有做更多的事情。这是为了让开发者能够方便地开始使用 ProGuard 进行他们的生产构建,无论何时他们准备好了。
虽然release构建类型不是为你的项目预先创建的唯一构建类型。默认情况下,每个模块都有一个debug构建类型。它被设置为合理的默认值,但你可以通过将其包含在buildTypes块中并覆盖你想要更改的属性来更改其配置。
注意
debug构建类型有自己的默认设置,以便于调试。当您创建自己的构建类型时,将应用不同的默认设置。例如,debuggable属性对于debug构建类型设置为true,但在您创建的任何其他构建类型中都设置为false。
创建构建类型
当默认设置不足时,创建自己的自定义构建类型很容易。对于新的构建类型,只需要在buildTypes块内创建一个新的对象。以下是一个名为staging的自定义构建类型的示例:
android {
buildTypes {
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_URL", "\"http://staging.example.com/api\""
}
}
}
staging构建类型为应用程序 ID 定义了一个新的后缀,使其与调试和发布版本的应用程序 ID 不同。假设您有默认的构建配置,以及staging构建类型,构建类型的应用程序 ID 看起来像这样:
-
调试:
com.package -
发布:
com.package -
阶段配置:
com.package.staging
这意味着您可以在同一设备上安装预发布版本和发布版本,而不会引起任何冲突。staging构建类型还有一个版本名称后缀,这有助于在同一设备上区分应用程序的多个版本。buildConfigField属性定义了一个用于 API 的自定义 URL,使用构建配置字段,正如我们在第二章中看到的,基本构建自定义。
在创建新的构建类型时,您不必总是从头开始。可以初始化一个构建类型,该类型复制另一个构建类型的属性:
android {
buildTypes {
staging.initWith(buildTypes.debug)
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
debuggable = false
}
}
}
initWith()方法创建一个新的构建类型,并将所有属性从现有构建类型复制到新创建的构建类型中。您可以通过在新的构建类型对象中简单地定义它们来覆盖属性或定义额外的属性。
源集
当您创建一个新的构建类型时,Gradle 也会创建一个新的源集。默认情况下,源集目录假定与构建类型具有相同的名称。尽管如此,当您定义一个新的构建类型时,目录不会自动创建。您必须自己创建源集目录,然后才能为构建类型使用自定义源代码和资源。
这就是标准debug和release构建类型,加上额外的预发布构建类型的目录结构可能看起来像这样:
app
└── src
├── debug
│ ├── java
│ │ └── com.package
│ │ └── Constants.java
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
├── main
│ ├── java
│ │ └── com.package
│ │ └── MainActivity.java
│ ├── res
│ │ ├── drawable
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
├── staging
│ ├── java
│ │ └── com.package
│ │ └── Constants.java
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
└── release
├── java
│ └── com.package
│ └── Constants.java
└── AndroidManifest.xml
这些源集打开了一个无限可能的世界。例如,您可以针对特定的构建类型覆盖某些属性,为某些构建类型添加自定义代码,以及为不同的构建类型添加自定义布局或字符串。
注意
在向构建类型添加 Java 类时,重要的是要注意这个过程是互斥的。这意味着如果您将CustomLogic.java类添加到预发布源集,您将能够将该类添加到调试和发布源集,但不能添加到主源集。类将被定义两次,当您尝试构建时将抛出异常。
当使用不同的源集时,资源将以特殊方式处理。可绘制资源和布局文件将完全覆盖主源集中具有相同名称的资源,但values目录中的文件(如strings.xml)则不会。Gradle 将合并构建类型资源的内容与主资源。
例如,如果您在主源集中有一个strings.xml文件,如下所示:
<resources>
<string name="app_name">TypesAndFlavors</string>
<string name="hello_world">Hello world!</string>
</resources>
如果您在staging构建类型源集中有一个strings.xml文件,如下所示:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
</resources>
然后,合并后的strings.xml文件将看起来像这样:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
<string name="hello_world">Hello world!</string>
</resources>
当您构建非staging的构建类型时,最终的strings.xml文件将只是主源集中的strings.xml文件。
对于清单文件也是如此。如果您为构建类型创建了一个清单文件,您不需要从主源集中复制整个清单文件;您只需添加所需的标签即可。Android 插件将合并清单。
我们将在本章的后面部分更详细地讨论合并。
依赖项
每个构建类型都可以有自己的依赖项。Gradle 会自动为每个构建类型创建新的依赖配置。例如,如果您只想为debug构建添加日志框架,您可以这样做:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}
您可以使用这种方式将任何构建类型与任何依赖配置组合在一起。这使您能够对依赖项进行非常具体的配置。
产品风味
与用于配置同一应用或库的多个不同构建的构建类型不同,产品风味用于创建同一应用的不同版本。一个典型的例子是具有免费版和付费版的应用。另一个常见的场景是,一家机构为几个客户构建具有相同功能的应用,其中只有品牌发生变化。这在出租车行业或银行应用中非常常见,其中一家公司创建的应用可以用于同一类别的所有客户。唯一改变的是主要颜色、标志和后端 URL。产品风味极大地简化了基于相同代码的不同版本应用的过程。
如果您不确定是否需要新的构建类型或新的产品风味,您应该问自己是否想要为内部使用创建同一应用的新的构建版本,或者为发布到 Google Play 创建新的 APK。如果您需要完全新的应用,该应用需要独立于您已有的内容发布,那么产品风味就是您应该选择的方式。否则,您应该坚持使用构建类型。
创建产品风味
创建产品风味与创建构建类型非常相似。您可以通过将其添加到productFlavor块中来创建一个新的产品风味,如下所示:
android {
productFlavors {
red {
applicationId 'com.gradleforandroid.red'
versionCode 3
}
blue {
applicationId 'com.gradleforandroid.blue'
minSdkVersion 14
versionCode 4
}
}
}
产品香味与构建类型具有不同的属性。这是因为产品香味是 ProductFlavor 类的对象,就像所有构建脚本中存在的 defaultConfig 对象一样。这意味着 defaultConfig 和你的所有产品香味共享相同的属性。
源集
就像构建类型一样,产品香味可以有自己的源集目录。为特定香味创建一个文件夹就像创建一个带有香味名称的文件夹一样简单。你甚至可以更进一步,为特定构建类型和香味的组合创建一个文件夹。文件夹的名称将是香味名称后跟构建类型名称。例如,如果你想为蓝色香味的发布版本有特定的应用程序图标,文件夹名称必须是 blueRelease。组合文件夹的组件将比构建类型文件夹和产品香味文件夹的组件具有更高的优先级。
多香味变体
在某些情况下,你可能希望更进一步,创建产品香味的组合。例如,客户 A 和客户 B 可能各自希望他们的应用程序有免费和付费版本,这些应用程序基于相同的代码库,但有不同的品牌。创建四种不同的香味意味着会有几个重复的设置,所以这不是正确的方法。使用香味维度以高效的方式组合香味是可能的,如下所示:
android {
flavorDimensions "color", "price"
productFlavors {
red {
flavorDimension "color"
}
blue {
flavorDimension "color"
}
free {
flavorDimension "price"
}
paid {
flavorDimension "price"
}
}
}
一旦你添加了香味维度,Gradle 就期望你为每个香味指定一个维度。如果你忘记了,你会得到一个带有解释问题的构建错误。flavorDimensions 数组定义了维度,维度的顺序非常重要。当组合两个香味时,它们可能已经定义了相同的属性或资源。在这种情况下,香味维度数组的顺序决定了哪个香味配置覆盖了另一个。在早期示例中,颜色维度覆盖了价格维度。顺序还决定了构建变体的名称。在早期示例中,颜色维度覆盖了价格维度。顺序还决定了构建变体的名称。
假设默认的构建配置与调试和发布构建类型,按照前一个示例定义的香味将生成所有这些构建变体:
-
blueFreeDebug和blueFreeRelease -
bluePaidDebug和bluePaidRelease -
redFreeDebug和redFreeRelease -
redPaidDebug和redPaidRelease
构建变体
构建变体仅仅是构建类型和产品香味的组合结果。每次你创建一个构建类型或产品香味时,也会创建新的变体。例如,如果你有标准的 debug 和 release 构建类型,并且创建了一个红色和蓝色产品香味,以下构建变体将被生成:

这是 Android Studio 中构建变体工具窗口的截图。你可以在编辑器的左下角找到该工具窗口,或者从视图 | 工具窗口 | 构建变体打开它。此工具窗口列出了所有构建变体,同时也允许你在它们之间切换。在此处更改选定的构建变体将影响点击运行按钮时运行的变体。
如果你没有产品风味,变体将仅由构建类型组成。不可能没有任何构建类型。即使你自己没有定义任何构建类型,Gradle 的 Android 插件也会为你的应用或库创建一个调试构建类型。
任务
Gradle 的 Android 插件将为你配置的每个构建变体创建任务。新的 Android 应用默认有debug和release构建类型,因此你已经有了assembleDebug和assembleRelease来分别构建 APK,以及assemble来通过单个命令创建它们。当你添加新的构建类型时,也会创建新的任务。一旦你开始添加风味,就会创建一系列全新的任务,因为每个构建类型的任务都会与每个产品风味的任务相结合。这意味着对于只有一个构建类型和一个风味的简单设置,你已经有了三个任务来构建所有变体:
-
assembleBlue使用蓝色风味配置,并组装BlueRelease和BlueDebug。 -
assembleDebug使用调试构建类型配置,并为每个产品风味组装一个调试版本。 -
assembleBlueDebug将风味配置与构建类型配置相结合,并且风味设置会覆盖构建类型设置。
为每个构建类型、每个产品风味以及每个构建类型和产品风味的组合创建新任务。
源集
组合了构建类型和一个或多个产品风味的构建变体也可以有自己的源集目录。例如,由debug构建类型、蓝色风味和免费风味创建的变体可以在src/blueFreeDebug/java/中拥有自己的源集。可以使用我们在第一章中看到的sourceSets块来覆盖目录的位置,即使用 Gradle 和 Android Studio 入门。
资源和清单合并
源集的引入给构建过程增加了额外的复杂性。Gradle 的 Android 插件需要在打包应用程序之前将主源集和构建类型源集合并在一起。此外,库项目也可以提供额外的资源,这些资源也需要合并。清单文件也是如此。例如,你可能需要在应用程序的调试变体中声明额外的 Android 权限来存储日志文件。你不想在主源集上声明这个权限,因为这可能会吓到潜在的用户。相反,你会在debug构建类型源集中添加一个额外的清单文件来声明额外的权限。
资源和清单的优先级顺序如下:

如果一个资源在风味和主源集中声明,那么来自风味的资源将具有更高的优先级。在这种情况下,风味源集中的资源将被打包,而不是主源集中的资源。在库项目中声明的资源始终具有最低优先级。
注意
关于资源清单合并,还有很多东西要学习。这是一个极其复杂的话题,如果我们想要解释其细节,我们需要为它专门写一整章。相反,如果你想了解更多,阅读关于该主题的官方文档是一个好主意,请参阅tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger。
创建构建变体
Gradle 使得处理构建变体的复杂性变得简单。即使创建和配置两个构建类型和两个产品风味,构建文件仍然简洁:
android {
buildTypes {
debug {
buildConfigField "String", "API_URL", "\"http://test.example.com/api\""
}
staging.initWith(android.buildTypes.debug)
staging {
buildConfigField "String", "API_URL", "\"http://staging.example.com/api\""
applicationIdSuffix ".staging"
}
}
productFlavors {
red {
applicationId "com.gradleforandroid.red"
resValue "color", "flavor_color", "#ff0000"
}
blue {
applicationId "com.gradleforandroid.blue"
resValue "color", "flavor_color", "#0000ff"
}
}
}
在这个例子中,我们创建了四个不同的构建变体:blueDebug、blueStaging、redDebug和redStaging。每个变体都有自己的 API URL 和风味颜色的组合。这是blueDebug在手机上的样子:

这是同一应用程序的redStaging变体:

第一张截图显示了blueDebug变体,它使用debug构建类型中定义的 URL,并根据为蓝色产品风味定义的flavor_color将文本设置为蓝色。第二张截图显示了redStaging,具有预发布 URL 和红色文本。红色预发布版本还有一个不同的应用程序图标,因为staging构建类型的源集中的可绘制文件夹有自己的应用程序图标图像。
变体过滤器
在构建过程中,你可以完全忽略某些变体。这样,你可以通过通用的assemble命令加快构建所有变体的过程,并且你的任务列表不会被不应该执行的任务所污染。这也确保了构建变体不会出现在 Android Studio 的构建变体切换器中。
您可以使用以下代码在应用的build.gradle文件根级别过滤变体:
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')) {
variant.getFlavors().each() { flavor ->
if (flavor.name.equals('blue')) {
variant.setIgnore(true);
}
}
}
}
在此示例中,我们首先检查变体的构建类型是否有名称release。然后,我们提取所有产品变体的名称。当使用没有维度的变体时,变体数组中只有一个产品变体。一旦开始应用变体维度,变体数组将包含与维度数量一样多的变体。在示例脚本中,我们检查蓝色产品变体,并告诉构建脚本忽略这个特定的变体。
这是 Android Studio 中构建变体切换器中变体过滤器的结果:

您可以看到,两个蓝色的发布变体(blueFreeRelease和bluePaidRelease)被过滤出了构建变体列表。如果您现在运行gradlew tasks,您会注意到与这些变体相关的所有任务都不再存在。
签名配置
在您可以在 Google Play 或任何其他应用商店发布应用之前,您需要使用私钥对其进行签名。如果您有一个付费版和免费版或针对不同客户的不同应用,您需要为每个版本使用不同的密钥进行签名。这就是签名配置派上用场的地方。
签名配置可以定义如下:
android {
signingConfigs {
staging.initWith(signingConfigs.debug)
release {
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
在此示例中,我们创建了两个不同的签名配置。
调试配置由 Android 插件自动设置,并使用一个具有已知密码的通用密钥库,因此不需要为这种构建类型创建签名配置。
示例中的预发布配置使用initWith(),它从另一个签名配置复制所有属性。这意味着预发布构建使用的是调试密钥,而不是定义自己的密钥。
发布配置使用storeFile指定密钥库文件的路径,然后定义密钥别名和两个密码。
注意
如前所述,将凭据存储在构建配置文件中不是一个好主意。更好的办法是使用 Gradle 属性文件。在第七章中,专门有一个部分介绍如何处理签名配置密码的任务。
在您定义签名配置后,您需要将它们应用到您的构建类型或变体上。构建类型和变体都有一个名为signingConfig的属性,可以像这样使用:
android {
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
此示例使用构建类型,但如果您想为每个创建的变体使用不同的证书,您需要创建不同的签名配置。您可以用完全相同的方式定义它们:
android {
productFlavors {
blue {
signingConfig signingConfigs.release
}
}
}
以这种方式使用签名配置会导致问题。当将配置分配给版本时,您实际上是在覆盖构建类型的签名配置。在使用版本时,您实际上想要为每个版本和构建类型使用不同的密钥:
android {
buildTypes {
release {
productFlavors.red.signingConfig signingConfigs.red
productFlavors.blue.signingConfig signingConfigs.blue
}
}
}
示例展示了如何为使用发布构建类型的红色和蓝色版本使用不同的签名配置,而不会影响调试和预发布构建类型。
摘要
在本章中,我们讨论了构建类型、产品版本以及它们所有可能的组合。这些是非常强大的工具,可以在任何应用程序中使用。从具有不同 URL 和密钥的简单设置到更复杂的共享相同源代码和资源但具有不同品牌和多个版本的应用程序;构建类型和产品版本可以使您的生活大大简化。
我们还讨论了签名配置及其应用,并提到了在签名产品版本时常见的陷阱。
接下来,您将了解多模块构建。当您想要将代码提取到库或库项目中,或者想要将例如 Android Wear 模块包含到您的应用中时,这些非常有用。
第五章。管理多模块构建
Android Studio 允许你创建不仅适用于应用和库,还适用于 Android Wear、Android TV、Google App Engine 等模块。所有这些模块都可以在单个项目中一起使用。例如,你可能想要创建一个使用 Google Cloud Endpoints 作为后端并包含 Android Wear 集成的应用。在这种情况下,你可以有一个包含三个不同模块的项目:一个用于应用,一个用于后端,一个用于 Android Wear 集成。了解多模块项目的结构和构建方式可以显著加快你的开发周期。
注意
Gradle 和 Gradle Android 插件的文档都使用了“多项目构建”这个术语。然而,在 Android Studio 中,模块和项目之间有一个区别。一个模块可以是 Android 应用或 Google App Engine 后端,例如。另一方面,项目是一组模块的集合。在这本书中,我们使用模块和项目这两个术语的方式与 IDE 相同,以避免混淆。当你浏览文档时,请记住这一点。
在本章中,我们将介绍多模块构建的理论,然后展示一些在实际项目中可能有用的示例:
-
多模块构建的解剖结构
-
向项目中添加模块
-
小贴士和最佳实践
多模块构建的解剖结构
通常,多模块项目通过一个根目录来工作,该目录包含所有模块的子目录。为了告诉 Gradle 项目的结构以及哪些目录包含模块,你需要在项目的根目录中提供一个 settings.gradle 文件。然后,每个模块可以提供自己的 build.gradle 文件。我们已经在 第二章 中学习了 settings.gradle 和 build.gradle 文件的工作方式,基本构建定制,所以在这里我们将只关注如何为多模块项目使用它们。
这就是多模块项目可能的样子:
project
├─── setting.gradle
├─── build.gradle
├─── app
│ └─── build.gradle
└─── library
└─── build.gradle
这是设置具有多个模块的项目最简单、最直接的方式。settings.gradle 文件声明了项目中的所有模块,其外观如下:
include ':app', ':library'
这确保了应用和库模块被包含在构建配置中。你所需做的只是添加模块目录的名称。
要将库模块作为依赖添加到应用模块中,你需要在应用模块的 build.gradle 文件中添加以下内容:
dependencies {
compile project(':library')
}
为了添加对模块的依赖,你需要使用 project() 方法,并将模块路径作为参数。
如果你想要使用子目录来组织你的模块,Gradle 可以配置以适应你的需求。例如,你可以有一个看起来像这样的目录结构:
project
├─── setting.gradle
├─── build.gradle
├─── app
│ └─── build.gradle
└─── libraries
├─── library1
│ └─── build.gradle
└─── library2
└─── build.gradle
应用模块仍然位于根目录,就像之前一样,但现在项目有两个不同的库。这些库模块并不位于项目根目录中,而是在一个特定的库目录中。有了这种目录结构,你可以在settings.gradle中这样声明应用和库模块:
include ':app', ':libraries:library1', ':libraries:library2'
注意声明子目录内模块的简单性。所有路径都是相对于根目录的(settings.gradle文件所在的目录)。冒号用作路径中正斜杠的替代。
当在子目录中添加一个模块作为另一个模块的依赖项时,你应该始终从根目录引用它。这意味着如果前一个示例中的应用模块依赖于library1,那么应用模块的build.gradle文件应该看起来像这样:
dependencies {
compile project(':libraries:library1')
}
如果你在一个子目录中声明依赖项,所有路径都应该相对于根目录。这是因为 Gradle 从项目的根目录开始构建你的项目的依赖模型。
重新审视构建生命周期
了解构建过程模型是如何构建的,使得理解多模块项目是如何组成的变得更容易。我们已经在第一章中讨论了构建生命周期,使用 Gradle 和 Android Studio 入门,所以你已经知道了基础知识,但一些细节对于多模块构建尤为重要。
在第一个阶段,初始化阶段,Gradle 寻找一个settings.gradle文件。如果这个文件不存在,Gradle 假设你有一个单模块构建。如果你有多个模块,设置文件就是你可以定义包含单个模块的子目录的地方。如果那些子目录包含它们自己的build.gradle文件,Gradle 将处理它们,并将它们合并到构建过程模型中。这就是为什么你应该始终使用相对于根目录的路径来声明模块依赖的原因。Gradle 总是会尝试从根目录中确定依赖项。
一旦你理解了构建过程模型是如何构建的,就会变得清楚,有几种策略可以配置多模块项目的构建。你可以在根目录的build.gradle文件中为所有模块配置设置。这使得你可以轻松地获得整个项目的构建配置概览,但可能会变得非常混乱,尤其是当你有需要不同插件的模块时,每个插件都有自己的 DSL。另一种方法是为每个模块单独创建build.gradle文件。这种策略确保模块之间不是紧密耦合的。它还使得跟踪构建更改变得更容易,因为你不需要弄清楚哪个更改适用于哪个模块。
最后一种策略是混合方法。你可以在项目根目录中有一个构建文件来定义所有模块的通用属性,并为每个模块有一个构建文件来配置仅适用于该特定模块的设置。Android Studio 遵循这种方法。它在根目录中创建一个build.gradle文件,并为每个模块创建另一个build.gradle文件。
模块任务
一旦你的项目中包含多个模块,在运行任务之前你需要三思。当你从项目根目录在命令行界面中运行任务时,Gradle 会确定哪些模块具有该名称的任务,并为每个模块执行它。例如,如果你有一个移动应用模块和一个 Android Wear 模块,运行gradlew assembleDebug将构建移动应用模块和 Android Wear 模块的调试版本。然而,当你更改目录到其中一个模块时,即使你在项目根目录中使用 Gradle 包装器,Gradle 也只会为该特定模块运行任务。例如,从 Android Wear 模块目录运行../gradlew assembleDebug将仅构建 Android Wear 模块。
切换目录以运行特定模块的任务可能会让人感到烦恼。幸运的是,还有另一种方法。你可以在任务名称前加上模块名称,以便只在该特定模块上运行该任务。例如,要仅构建 Android Wear 模块,可以使用gradlew :wear:assembleDebug命令。
将模块添加到项目中
添加新模块就像在 Android Studio 中通过向导一样简单。向导还会设置构建文件的基本设置。在某些情况下,添加模块甚至会导致 Android Studio 编辑你的应用模块的构建文件。例如,当添加 Android Wear 模块时,IDE 假设你想将其用于你的 Android 应用,并在构建文件中添加一行以引用 Android Wear 模块。
这就是 Android Studio 中的新建模块对话框的样子:

在以下章节中,我们将展示如何使用 Android Studio 将不同的模块添加到 Android 项目中,解释它们的自定义属性,并指定它们如何改变构建过程。
添加 Java 库
当你添加一个新的 Java 库模块时,Android Studio 生成的build.gradle文件看起来像这样:
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
Java 库模块使用 Java 插件而不是我们习惯看到的 Android 插件。这意味着许多 Android 特定的属性和任务不可用,但无论如何你都不需要这些属性来创建 Java 库。
构建文件还设置了基本的依赖管理,因此你可以将 JAR 文件添加到你的libs文件夹中,而无需任何特殊配置。你可以添加更多依赖项,使用你在第三章中学到的知识,管理依赖项。依赖项配置不依赖于 Android 插件。
要将名为 javalib 的 Java 库模块作为依赖项添加到您的应用模块中,例如,只需将此行添加到应用模块的构建配置文件中:
dependencies {
compile project(':javalib')
}
这告诉 Gradle 在构建中导入名为 javalib 的模块。如果您在应用模块中添加此依赖项,则 javalib 模块将在启动应用模块本身的构建之前始终被构建。
添加 Android 库
我们在第三章中简要提到了 Android 库,称为库项目。这两个名称在文档和各个教程中都被使用。在本节中,我们将使用 Androidlibrary 这个名称,因为在 Android Studio 的 新建模块 对话框中使用的是这个名称。
Android 库的默认 build.gradle 文件以这一行开始:
apply plugin: 'com.android.library'
以与 Java 库相同的方式添加对 Android 库模块的依赖项:
dependencies {
compile project(':androidlib')
}
Android 库不仅包含库的 Java 代码,还包含所有 Android 资源,如清单、字符串和布局。在您的应用中引用 Android 库后,您可以在应用中使用库的所有类和资源。
集成 Android Wear
如果您想将您的应用深度集成到 Android Wear 中,您需要添加一个 Android Wear 模块。值得注意的是,Android Wear 模块也使用 Android 应用程序插件。这意味着所有构建属性和任务都是可用的。
与常规 Android 应用模块不同的 build.gradle 文件的部分是依赖项配置:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:1.1.0'
compile 'com.google.android.gms:play-services-wearable:6.5.87'
}
每个 Android Wear 应用都依赖于 Google 提供的几个特定于 Android Wear 的库。为了使用 Android Wear 应用与您的 Android 应用一起使用,您需要将其打包到应用中。您可以通过在 Android 应用中添加依赖项来实现这一点:
dependencies {
wearApp project(':wear')
}
wearApp 配置确保 Wear 模块的 APK 被添加到 Android 应用的最终 APK 中,并为您执行必要的配置。
使用 Google App Engine
Google App Engine 是一个云平台,您可以使用它来托管 Web 应用,而无需设置自己的服务器。在达到一定使用量之前,它是免费的,这使得它成为实验的好环境。Google App Engine 还提供了一种名为 Cloud Endpoints 的服务,用于创建 RESTful 服务。使用 Google App Engine 与 Cloud Endpoints 一起使用,可以轻松构建应用的后端。App Engine Gradle 插件通过为您的 Android 应用生成客户端库,使这变得更加容易,这意味着您不需要自己编写任何 API 相关的代码。这使得 Google App Engine 成为应用后端的一个有趣选择,因此,在下一节中,我们将探讨 App Engine Gradle 插件的工作原理以及我们如何利用 Cloud Endpoints。
要创建一个新的带有 Cloud Endpoints 的 Google App Engine 模块,请从 文件 | 新建模块… 打开 新建模块 对话框,并选择 Google Cloud 模块。在设置模块时,您可以更改类型以包括 Cloud Endpoints。然后,选择将使用此后端的客户端模块。

对 Google App Engine 和 Cloud Endpoints 的全面解释超出了本书的范围;我们只会查看 App Engine 模块和客户端应用模块中的 Gradle 集成。
分析构建文件
此模块的 build.gradle 文件变得相当大,所以我们只看最有趣的部分,从新的构建脚本依赖项开始:
buildscript {
dependencies {
classpath 'com.google.appengine:gradle-appengine-plugin:1.9.18'
}
}
App Engine 插件需要在构建脚本的 classpath 中定义。我们之前在添加 Android 插件时已经看到过这一点。当它就位后,我们可以应用 App Engine 插件以及另外两个插件:
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'
Java 插件主要用于为 Cloud Endpoints 生成 JAR 文件。WAR 插件对于运行和分发整个后端是必要的。WAR 插件生成 WAR 文件,这是 Java 网络应用程序的分发方式。最后,App Engine 插件添加了构建、运行和部署整个后端的任务。
下一个重要的块定义了 App Engine 模块的依赖项:
dependencies {
appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.18'
compile 'com.google.appengine:appengine-endpoints:1.9.18'
compile 'com.google.appengine:appengine-endpoints-deps:1.9.18'
compile 'javax.servlet:servlet-api:2.5'
}
第一个依赖项使用 appengineSdk 来指定在此模块中应使用哪个 SDK。endpoints 依赖项对于 Cloud Endpoints 的工作是必要的。这些依赖项只有在您选择在模块中使用 Cloud Endpoints 时才会添加。servlet 依赖项是任何 Google App Engine 模块的要求。
在 appengine 块中配置任何特定于 App Engine 的设置:
appengine {
downloadSdk = true
appcfg {
oauth2 = true
}
endpoints {
getClientLibsOnBuild = true
getDiscoveryDocsOnBuild = true
}
}
将 downloadSdk 属性设置为 true 可以轻松运行本地开发服务器,因为它会自动下载 SDK,如果它不存在的话。如果您已经在设备上设置了 Google App Engine SDK,可以将 downloadSdk 属性设置为 false。
appcfg 块用于配置 App Engine SDK。在典型的 Google App Engine 安装中,您可以使用 appcfg 命令行工具手动配置一些设置。使用 appcfg 块而不是命令行工具可以使配置更加便携,因为任何构建过此模块的人都将拥有相同的配置,而无需执行任何外部命令。
endpoints 块包含一些 Cloud Endpoints 特定的设置。
注意
本书中不包含对 Google App Engine 和 Cloud Endpoints 配置的详细解释。如果您想了解更多信息,请查看cloud.google.com/appengine/docs上的文档。
在应用中使用后端
当您创建 App Engine 模块时,Android Studio 会自动将依赖项添加到 Android 应用模块的构建文件中。这个依赖项看起来是这样的:
dependencies {
compile project(path: ':backend', configuration: 'android-endpoints')
}
我们之前看到过这样的语法(在引用 Java 和 Android 库时),使用 project 来定义依赖关系,但有两个参数而不是一个。path 参数是默认参数。我们之前使用过它,但没有指定其名称。Google App Engine 模块可以有不同的输出类型。你可以使用 configuration 参数指定你想要的输出。我们需要 App Engine 模块生成 Cloud Endpoints,所以我们使用 android-endpoints 配置。内部,此配置运行 _appengineEndpointsAndroidArtifact 任务。此任务生成一个包含你可以用于你的 Android 应用模块的类的 JAR 文件。此 JAR 文件不仅包含 Cloud Endpoints 中使用的模型,还包括 API 方法。这种集成使得多模块项目易于工作,因为它可以加快开发时间。App Engine 模块中的 Gradle 任务也使得运行和部署你的后端变得容易。
自定义任务
App Engine 插件添加了许多任务,但你最常使用的是 appengineRun 和 appengineUpdate。
appengineRun 任务用于启动一个本地开发服务器,你可以用它来在将代码上传到 Google App Engine 之前本地测试你的整个后端。第一次运行此任务时,构建可能需要一些时间,因为 Gradle 需要下载 App Engine SDK。我们之前通过 downloadSdk = true 设置了这种行为。要停止服务器,你可以使用 appengineStop。
一旦你准备好将后端部署到 Google App Engine 并在生产中使用它,你可以使用 appengineUpdate。此任务处理所有部署细节。如果你在 appengine 配置块中设置了 oauth2 = true,此任务将打开一个浏览器窗口,以便你可以登录到你的 Google 账户并获取一个身份验证令牌。如果你不希望每次部署时都这样做,你可以使用你的 Google 账户登录到 Android Studio,并使用 IDE 部署后端。Android Studio 会运行相同的 Gradle 任务,但它将为你处理身份验证。
小贴士和最佳实践
有几种方法可以使处理多模块项目更容易,当与多个模块一起工作时,有一些事情需要记住。意识到这些可以节省你的时间和挫败感。
从 Android Studio 运行模块任务
正如我们在第二章中看到的,基本构建自定义,你可以直接从 Android Studio 运行 Gradle 任务。当你有多个模块时,Android Studio 会识别它们,并显示所有可用任务的分组概览。

Gradle 工具窗口使运行特定模块的任务变得更容易。没有选项可以同时为所有模块运行任务,所以如果你希望这样做,命令行界面仍然更快。
加速多模块构建
当你构建一个多模块项目时,Gradle 会按顺序处理所有模块。随着计算机中可用核心数量的增加,我们可以通过并行构建模块来使构建过程更快。这个特性在 Gradle 中已经存在,但默认情况下并未启用。
如果你想将并行构建执行应用于你的项目,你需要在项目根目录下的gradle.properties文件中配置parallel属性:
org.gradle.parallel=true
Gradle 会尝试根据可用的 CPU 核心数选择合适的线程数。为了避免在并行执行同一模块的两个任务时可能出现的問題,每个线程都“拥有”整个模块。
注意
并行构建执行是一个孵化特性。这意味着它正在积极开发中,实现可能会随时更改。然而,这个特性已经存在于 Gradle 中一段时间了,并且已经被广泛使用。因此,可以假设实现不会消失或发生重大变化。
你的体验可能会有所不同,但通过简单地启用并行构建执行,你可能会从构建中节省大量的时间。然而,有一个注意事项。为了有效地工作,你需要确保你的模块是解耦的。
模块耦合
正如我们在第二章中看到的,基本构建定制,你可以使用build.gradle文件中的allprojects来为项目中的所有模块定义属性。当你有一个包含多个模块的项目时,你可以在任何模块中使用allprojects来将属性应用于项目中的所有模块。Gradle 甚至使得一个模块可以引用另一个模块的属性。这些强大的功能可以使多模块构建的维护变得更加容易。然而,缺点是,你的模块变得耦合了。
两个模块一旦互相访问对方的任务或属性,就被认为是耦合的。这有几个后果。例如,你放弃了可移植性。如果你决定从项目中提取库,你必须先复制所有全局设置,然后才能构建库。模块耦合也会影响并行构建。在任何一个模块中使用allprojects块将使并行构建执行变得无用。当你向任何模块添加全局属性时,请注意这一点。
你可以通过不直接从其他模块访问任务或属性来避免耦合。如果你需要这种行为,你可以使用根模块作为中介,这样模块就只与根模块耦合,而不是彼此耦合。
摘要
我们从查看多模块构建的结构开始本章。然后,我们探讨了如何在单个项目中设置多个模块。我们还看到,添加新模块会影响构建任务的执行方式。
我们随后查看了一些新模块的实际示例,以及它们如何被整合到项目中。最后,我们提到了一些技巧和窍门,这些技巧和窍门使得在一个项目中使用多个模块变得更加容易。
在下一章中,我们将设置各种类型的测试,并探讨如何使用 Gradle 来简化这些测试的运行。我们将查看如何在 Java 虚拟机上直接运行单元测试,同时也会探讨如何在真实设备或模拟器上运行测试。
第六章。运行测试
为了确保任何应用程序或库的质量,拥有自动化测试是非常重要的。长期以来,Android 开发工具缺乏对自动化测试的支持,但最近,谷歌投入了大量努力,使开发者更容易开始测试。一些旧框架已经更新,并且添加了新框架,以确保我们可以彻底测试应用程序和库。我们不仅可以从 Android Studio 运行它们,还可以直接从命令行界面使用 Gradle 运行。
在本章中,我们将探讨测试 Android 应用程序和库的不同方法。我们还将探讨 Gradle 如何帮助自动化测试过程。
在本章中,我们将涵盖以下主题:
-
单元测试
-
功能测试
-
测试覆盖率
单元测试
在你的项目中拥有编写良好的单元测试不仅保证了质量,还使得检查新代码是否破坏了任何功能变得容易。Android Studio 和 Gradle Android 插件对单元测试有原生支持,但在使用它们之前,你需要配置一些设置。
JUnit
JUnit 是一个极其流行的单元测试库,它已经存在十多年了。它使得编写测试变得容易,同时确保它们也易于阅读。请注意,这些特定的单元测试仅适用于测试业务逻辑,而不是与 Android SDK 相关的代码。
在你可以开始为你的 Android 项目编写 JUnit 测试之前,你需要为测试创建一个目录。按照惯例,这个目录被称为 test,它应该与你的主目录在同一级别。目录结构应该如下所示:
app
└─── src
├─── main
│ ├─── java
│ │ └─── com.example.app
│ └───res
└─── test
└─── java
└─── com.example.app
你可以在 src/test/java/com.example.app 中创建测试类。
要使用 JUnit 的最新功能,请使用 JUnit 版本 4。你可以通过添加测试构建的依赖项来确保这一点:
dependencies {
testCompile 'junit:junit:4.12'
}
注意,我们在这里使用的是 testCompile 而不是 compile。我们使用这种配置来确保依赖项仅在运行测试时构建,而不是在打包应用程序进行分发时构建。使用 testCompile 添加的依赖项永远不会包含在常规的 assemble 任务生成的 APK 发布中。
如果你在一个构建类型或产品风味中有一个特殊条件,你可以为那个特定的构建添加一个仅测试的依赖项。例如,如果你只想将 JUnit 测试添加到你的付费风味中,你可以这样做:
dependencies {
testPaidCompile 'junit:junit:4.12'
}
当一切准备就绪时,就是开始编写测试的时候了。以下是一个测试添加两个数字方法的简单类示例:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class LogicTest {
@Test
public void addingNegativeNumberShouldSubtract() {
Logic logic = new Logic();
assertEquals("6 + -2 must be 4", 4, logic.add(6, -2));
assertEquals("2 + -5 must be -3", -3, logic.add(2, -5));
}
}
要使用 Gradle 运行所有测试,只需执行gradlew test。如果你只想在特定的构建变体上运行测试,只需简单地添加变体的名称。如果你想只在调试变体上运行测试,例如,执行gradlew testDebug。如果测试失败,Gradle 将在命令行界面中打印错误消息。如果所有测试都顺利运行,Gradle 将显示常规的BUILD SUCCESSFUL消息。
单个失败的测试会导致test任务失败,立即停止整个过程。这意味着在出现失败的情况下,并不是所有测试都会被执行。如果你想确保所有构建变体都执行整个测试套件,请使用continue标志:
$ gradlew test --continue
你也可以通过将测试类存储在相应的目录中来为特定的构建变体编写测试。例如,如果你想测试应用程序付费版本中的特定行为,将测试类放在src/testPaid/java/com.example.app。
如果你不想运行整个测试套件,只想运行特定类的测试,可以使用测试标志,如下所示:
$ gradlew testDebug --tests="*.LogicTest"
执行测试任务不仅运行所有测试,还会创建一个测试报告,该报告可以在app/build/reports/tests/debug/index.html找到。如果出现任何失败,这个报告可以轻松地找到问题,在测试自动执行的情况下尤其有用。Gradle 将为你在其上运行测试的每个构建变体创建一个报告。
如果所有测试都成功运行,你的单元测试报告将看起来像这样:

你也可以在 Android Studio 中运行测试。当你这样做时,你会在 IDE 中获得即时反馈,并且可以点击失败的测试导航到相应的代码。如果你的所有测试都通过了,运行工具窗口将看起来像这样:

如果你想要测试包含对 Android 特定类或资源的引用的代码部分,常规单元测试并不是理想的选择。你可能已经尝试过并遇到了java.lang.RuntimeException: Stub!错误。为了修复这个问题,你需要自己实现 Android SDK 中的每个方法,或者使用模拟框架。幸运的是,存在几个已经处理了 Android SDK 的库。其中最受欢迎的库是 Robolectric,它提供了一种简单的方式来测试 Android 功能,而无需设备或模拟器。
Robolectric
使用 Robolectric,你可以在 Java 虚拟机内部运行测试,同时利用 Android SDK 和资源。这意味着你不需要运行中的设备或模拟器来在测试中使用 Android 资源,因此可以大大加快测试应用程序或库的 UI 组件行为。
要开始使用 Robolectric,你需要添加一些测试依赖项。除了 Robolectric 本身之外,你还需要包括 JUnit,如果你使用了支持库,还需要 Robolectric 阴影类来使用它:
apply plugin: 'org.robolectric'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
testCompile 'junit:junit:4.12'
testCompile'org.robolectric:robolectric:3.0'
testCompile'org.robolectric:shadows-support:3.0'
}
Robolectric 测试类应该创建在 src/test/java/com.example.app 目录下,就像常规单元测试一样。区别在于你现在可以编写涉及 Android 类和资源的测试。例如,这个测试验证了在点击特定的 Button 后,某个 TextView 的文本是否发生了变化:
@RunWith(RobolectricTestRunner.class)
@Config(manifest = "app/src/main/AndroidManifest.xml", sdk = 18)
public class MainActivityTest {
@Test
public void clickingButtonShouldChangeText() {
AppCompatActivity activity = Robolectric.buildActivity(MainActivity.class).create().get();
Button button = (Button) activity.findViewById(R.id.button);
TextView textView = (TextView) activity.findViewById(R.id.label);
button.performClick();
assertThat(textView.getText().toString(), equalTo(activity.getString(R.string.hello_robolectric)));
}
}
注意
Robolectric 与 Android Lollipop 和兼容性库存在一些已知问题。如果你遇到提到缺少与兼容性库相关的资源的错误,有一个修复方案。
你需要向模块中添加一个名为 project.properties 的文件,并将以下行添加到其中:
android.library.reference.1=../../build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.0
android.library.reference.2=../../build/intermediates/exploded-aar/com.android.support/support-v4/22.2.0
这将帮助 Robolectric 找到兼容性库资源。
功能测试
功能测试用于测试应用中几个组件是否按预期协同工作。例如,你可以创建一个功能测试来确认点击某个按钮是否会打开一个新的 Activity。Android 有几个功能测试框架,但开始功能测试最简单的方法是使用 Espresso 框架。
Espresso
Google 创建了 Espresso 以便开发者更容易编写功能测试。该库通过 Android 支持仓库提供,因此你可以使用 SDK 管理器进行安装。
为了在设备上运行测试,你需要定义一个测试运行器。通过测试支持库,Google 提供了 AndroidJUnitRunner 测试运行器,它可以帮助你在 Android 设备上运行 JUnit 测试类。测试运行器会将应用 APK 和测试 APK 加载到设备上,运行所有测试,然后根据测试结果生成报告。
假设你已经下载了测试支持库,这是设置测试运行器的方法:
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
在开始使用 Espresso 之前,你还需要设置一些依赖项:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test:rules:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2'
}
你需要引用测试支持库和 espresso-core 以开始使用 Espresso。最后一个依赖项 espresso-contrib 是一个具有补充 Espresso 功能但不是核心库一部分的库。
注意到这些依赖项使用的是 androidTestCompile 配置,而不是我们之前使用的 testCompile 配置。这是为了区分单元测试和功能测试。
如果你此时尝试运行测试构建,你会遇到这个错误:
Error: duplicate files during packaging of APK app-androidTest.apk
Path in archive: LICENSE.txt
Origin 1: ...\hamcrest-library-1.1.jar
Origin 2: ...\junit-dep-4.10.jar
错误本身非常详细。Gradle 由于文件重复无法完成构建。幸运的是,这只是一个许可描述,我们可以将其从构建中移除。错误本身还包含了如何做到这一点的信息:
You can ignore those files in your build.gradle:
android {
packagingOptions {
exclude 'LICENSE.txt'
}
}
一旦构建文件设置完成,你就可以开始添加测试。功能测试放置在与常规单元测试不同的目录中。就像依赖配置一样,你需要使用androidTest而不是仅仅test,因此功能测试的正确目录是src/androidTest/java/com.example.app。以下是一个测试类的示例,该类检查MainActivity中TextView的文本是否正确:
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestingEspressoMainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void testHelloWorldIsShown() {
onView(withText("Hello world!")).check(matches(isDisplayed()));
}
}
在运行 Espresso 测试之前,你需要确保你有一个设备或模拟器。如果你忘记连接设备,尝试执行测试任务将抛出此异常:
Execution failed for task ':app:connectedAndroidTest'.
>com.android.builder.testing.api.DeviceException: java.lang.RuntimeException: No connected devices!
一旦你连接了设备或启动了模拟器,你可以使用gradlewconnectedCheck来运行你的 Espresso 测试。此任务将执行connectedAndroidTest来在所有连接的设备上运行调试构建的所有测试,以及createDebugCoverageReport来创建测试报告。
你可以在应用目录下的build/outputs/reports/androidTests/connected中找到生成的测试报告。打开index.html来查看报告,它看起来像这样:

功能测试报告显示测试是在哪个设备和 Android 版本上运行的。你可以同时运行多个设备上的这些测试,因此设备信息使得查找设备或版本特定的错误变得更加容易。
如果你想在 Android Studio 中对你的测试获得反馈,设置一个运行/调试配置来直接从 IDE 中运行测试。运行/调试配置代表一组运行/调试启动属性。Android Studio 工具栏有一个配置选择器,你可以从中选择你想要使用的运行/调试配置。

要设置新的配置,通过点击编辑配置…来打开配置编辑器,然后创建一个新的 Android 测试配置。选择模块并指定仪器运行器为AndroidJUnitRunner,如下面的截图所示:

保存此新配置后,你可以在配置选择器中选择它,然后点击运行按钮来运行所有测试。
注意
从 Android Studio 运行 Espresso 测试有一个缺点:不会生成测试报告。原因是 Android Studio 执行的是connectedAndroidTest任务而不是connectedCheck,而connectedCheck是负责生成测试报告的任务。
测试覆盖率
一旦你开始为你的 Android 项目编写测试,了解你的代码库中有多少被测试覆盖是很重要的。Java 有很多测试覆盖率工具,但Jacoco是最受欢迎的一个。它默认包含在内,这使得入门变得容易。
Jacoco
启用覆盖率报告非常简单。你只需在你要测试的构建类型上设置testCoverageEnabled = true。像这样启用调试构建类型的测试覆盖率:
buildTypes {
debug {
testCoverageEnabled = true
}
}
当你启用测试覆盖率时,当执行 gradlew connectedCheck 命令时将创建覆盖率报告。创建报告的任务本身是 createDebugCoverageReport。尽管它没有文档说明,且在运行 gradlew tasks 命令时不会出现在任务列表中,但你可以直接运行它。然而,由于 createCoverageReport 依赖于 connectedCheck,你不能单独执行它们。对 connectedCheck 的依赖还意味着你需要一个连接的设备或模拟器来生成测试覆盖率报告。
任务执行完成后,你可以在 app/build/outputs/reports/coverage/debug/index.html 目录中找到覆盖率报告。每个构建变体都有自己的报告目录,因为每个变体都可以有不同的测试。测试覆盖率报告看起来可能像这样:

报告显示了类级别的覆盖率概览,你可以点击查看更多信息。在最详细的视图中,你可以看到哪些行被测试了,哪些行没有被测试,以及在一个有用的颜色编码的文件视图中。
如果你想要指定 Jacoco 的特定版本,只需在构建类型中添加一个 Jacoco 配置块,并定义版本:
jacoco {
toolVersion = "0.7.1.201405082137"
}
然而,明确指定版本不是必要的;Jacoco 仍然可以正常工作。
摘要
在本章中,我们探讨了测试 Android 应用和库的几种选项。我们首先从简单的单元测试开始,然后探讨了使用 Robolectric 的更具体的 Android 测试。接着我们介绍了功能测试和 Espresso 的入门,最后我们探讨了启用测试覆盖率报告以查看测试套件需要改进的地方。现在你已经知道了如何使用 Gradle 和 Android Studio 运行整个测试套件,并且可以生成覆盖率报告,没有理由不编写测试。在 第八章 中,我们将探讨使用持续集成工具自动化测试的更多方法。
下一章将涵盖自定义构建过程最重要的一个方面:创建自定义任务和插件。本章还包括 Groovy 的简要介绍。这不仅有助于创建任务和插件,而且也有助于更容易地理解 Gradle 的工作原理。
第七章:创建任务和插件
到目前为止,我们一直在操作 Gradle 构建的属性,并学习如何运行任务。在本章中,我们将更深入地了解这些属性,并开始创建自己的任务。一旦我们知道了如何编写自己的任务,我们就可以更进一步,看看如何创建可以在多个项目中重用的自定义插件。
在我们查看如何创建自定义任务之前,我们需要学习一些重要的 Groovy 概念。这是因为对 Groovy 工作方式的基本理解使得开始创建自定义任务和插件变得容易得多。了解 Groovy 还有助于理解 Gradle 是如何工作的,以及为什么构建配置文件看起来是这个样子。
在本章中,我们将探讨以下主题:
-
理解 Groovy
-
开始使用任务
-
集成到 Android 插件中
-
创建自己的插件
理解 Groovy
由于大多数 Android 开发者都是熟练的 Java 开发者,因此观察 Groovy 与 Java 相比的工作方式很有趣。如果你是 Java 开发者,Groovy 的阅读起来相当简单,但如果没有一点介绍,编写自己的 Groovy 代码将是一项艰巨的任务。
小贴士
使用 Groovy 进行实验的一个好方法是使用 Groovy 控制台。这个应用程序包含在 Groovy SDK 中,使得尝试 Groovy 语句并获得即时响应变得容易。Groovy 控制台还能够处理纯 Java 代码,这使得比较 Java 和 Groovy 代码变得容易。您可以从 Groovy 网站groovy-lang.org/download.html下载 Groovy SDK,包括 Groovy 控制台。
简介
Groovy 是从 Java 衍生出来的,并在 Java 虚拟机上运行。它的目标是成为一个更简单、更直接的编程语言,既可以作为脚本语言使用,也可以作为完整的编程语言使用。在本节中,我们将比较 Groovy 和 Java,以便更容易理解 Groovy 的工作方式,并清楚地看到两种语言之间的区别。
在 Java 中,将字符串打印到屏幕上的样子如下:
System.out.println("Hello, world!");
在 Groovy 中,你可以用这一行代码完成相同的事情:
println 'Hello, world!'
你会立即注意到几个关键的区别:
-
没有使用
System.out命名空间 -
方法参数周围没有括号
-
行尾没有分号
示例还使用了字符串的单引号。你可以为字符串使用单引号或双引号,但它们有不同的用法。双引号字符串还可以包含插值表达式。插值是评估包含占位符的字符串的过程,并用它们的值替换这些占位符。这些占位符表达式可以是变量,甚至是方法。包含方法或多个变量的占位符表达式需要用花括号包围并以前缀$开头。只包含单个变量的占位符表达式只需以前缀$开头。以下是一些 Groovy 中字符串插值的示例:
def name = 'Andy'
def greeting = "Hello, $name!"
def name_size "Your name is ${name.size()} characters long."
greeting变量包含字符串"Hello, Andy",而name_size是"您的名字长度为 4 个字符。"。
字符串插值允许您动态执行代码。以下是一个有效的代码示例,它会打印当前日期:
def method = 'toString'
new Date()."$method"()
当您习惯于 Java 时,这看起来非常奇怪,但在动态编程语言中,这是一种正常的语法和行为。
类和成员
在 Groovy 中创建类的方式与在 Java 中创建类的方式非常相似。以下是一个包含一个成员的简单类的示例:
class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}
注意,类和成员都没有显式的访问修饰符。Groovy 中的默认访问修饰符与 Java 不同。类本身是公开的,就像方法一样,而类成员是私有的。
要使用MyGroovyClass,请创建其新实例:
def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()
您可以使用def关键字创建新变量。一旦您有一个类的实例,您就可以操作其成员。Groovy 会自动添加访问器。您仍然可以像我们在MyGroovyClass的定义中使用getGreeting()那样覆盖它们。如果您不指定任何内容,您仍然可以在类中的每个成员上使用 getter 和 setter。
如果您尝试直接调用成员,实际上会调用 getter。这意味着您不需要键入instance.getGreeting(),您可以直接使用更短的instance.greeting:
println instance.getGreeting()
println instance.greeting
上述代码示例中的两行打印出完全相同的内容。
方法
就像变量一样,您不需要为您的函数定义特定的返回类型。您仍然可以这样做,即使只是为了清晰起见。Java 和 Groovy 方法之间的另一个区别是,在 Groovy 中,方法的最后一行默认返回,即使没有使用return关键字。
为了展示 Java 和 Groovy 之间的差异,考虑以下 Java 示例,该方法返回一个数字的平方:
public int square(int num) {
return num * num;
}
square(2);
您需要指定该方法是否公开可访问,返回类型是什么,以及参数的类型。在方法末尾,您需要返回一个与返回类型相对应的值。
在 Groovy 中,相同的方法定义看起来像这样:
def square(def num) {
num * num
}
square 4
返回类型和参数类型都没有明确定义。使用def关键字代替显式类型,并且方法隐式返回一个值,而不使用return关键字。然而,为了清晰起见,仍然建议使用return关键字。当您调用方法时,不需要括号或分号。
在 Groovy 中,还有另一种定义新方法的更简短的方法。相同的square方法也可以这样表示:
def square = { num ->
num * num
}
square 8
这不是一个普通的方法,而是一个闭包。闭包的概念在 Java 中并不以相同的方式存在,但在 Groovy 和 Gradle 中起着重要作用。
闭包
闭包是接受参数并可以返回值的匿名代码块。它们可以被分配给变量,也可以作为参数传递给方法。
你可以通过在花括号之间添加一个代码块来定义一个闭包,就像你在前面的例子中看到的那样。如果你想更明确一些,你可以在定义中添加类型,如下所示:
Closure square = {
it * it
}
square 16
添加 Closure 类型可以让所有与代码工作的人清楚地知道正在定义一个闭包。前面的例子还介绍了名为 it 的隐式无类型参数的概念。如果你没有显式地向闭包添加参数,Groovy 会自动添加一个。这个参数始终被称为 it,你可以在所有闭包中使用它。如果调用者没有指定任何参数,it 是 null。这可以使你的代码更加简洁,但只有在闭包只接受一个单一参数时才有用。
在 Gradle 的上下文中,我们一直在使用闭包。在这本书中,我们一直把闭包称为块。这意味着,例如,android 块和 dependencies 块都是闭包。
集合
在使用 Groovy 的 Gradle 上下文中,有两个重要的集合类型:列表和映射。
在 Groovy 中创建一个新的列表非常简单。不需要特殊的初始化器;你可以简单地创建一个列表,如下所示:
List list = [1, 2, 3, 4, 5]
遍历列表也非常容易。你可以使用 each 方法遍历列表中的每个元素:
list.each() { element ->
println element
}
each 方法允许你访问列表中的每个元素。你可以通过使用前面提到的 it 变量来使这段代码更短。
list.each() {
println it
}
在 Gradle 的上下文中,另一种重要的集合类型是 Map。映射在多个 Gradle 设置和方法中使用。简单来说,映射是一个包含键值对的列表。你可以这样定义一个映射:
Map pizzaPrices = [margherita:10, pepperoni:12]
要访问映射中的特定项,请使用 get 方法或方括号:
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']
Groovy 也为此功能提供了一个快捷方式。你可以使用点符号来表示映射元素,使用键来检索值:
pizzaPrices.pepperoni
Groovy 在 Gradle 中
现在你已经了解了 Groovy 的基础知识,回顾一个 Gradle 构建文件并阅读它是一个有趣的练习。注意,配置的语法为什么是这样的已经变得更容易理解了。例如,看看应用 Android 插件的那一行:
apply plugin: 'com.android.application'
这段代码充满了 Groovy 快捷方式。如果你不使用任何快捷方式来写它,它看起来像这样:
project.apply([plugin: 'com.android.application'])
不使用 Groovy 快捷方式重写这一行,可以清楚地看出 apply() 是 Project 类的一个方法,这是每个 Gradle 构建的基本构建块。apply() 方法接受一个参数,它是一个包含键 plugin 和值 com.android.application 的 Map。
另一个例子是 dependencies 块。之前,我们是这样定义依赖项的:
dependencies {
compile 'com.google.code.gson:gson:2.3'
}
我们现在知道这个块是一个闭包,它被传递给Project对象上的dependencies()方法。这个闭包被传递给一个DependencyHandler,它包含add()方法。该方法接受三个参数:一个定义配置的字符串,一个定义依赖项表示法的对象,以及一个包含特定于此依赖项的属性的闭包。当你完整地写出来时,它看起来像这样:
project.dependencies({
add('compile', 'com.google.code.gson:gson:2.3', {
// Configuration statements
})
})
我们之前一直在看的构建配置文件,现在你应该已经对它们有了更多的了解,因为你已经知道了幕后的样子。
注意
如果你想了解更多关于 Gradle 在底层如何使用 Groovy 的信息,你可以将Project的官方文档作为起点。你可以在这里找到它:gradle.org/docs/current/javadoc/org/gradle/api/Project.html。
开始使用任务
自定义 Gradle 任务可以显著提高开发者的日常工作效率。任务可以操作现有的构建过程,添加新的构建步骤,或影响构建的输出。你可以执行简单的任务,例如通过挂钩到 Gradle 的 Android 插件来重命名生成的 APK。任务还允许你运行更复杂的代码,例如在应用打包之前为多个密度生成图像。一旦你知道如何创建自己的任务,你就会发现自己能够改变构建过程的每一个方面。这尤其在你学习了如何挂钩到 Android 插件时更为明显。
定义任务
任务属于一个Project对象,并且每个任务都实现了Task接口。定义一个新任务的最简单方法是通过执行task方法,并将任务名称作为其参数:
task hello
这会创建任务,但当你执行它时,它不会做任何事情。要创建一个稍微有用一些的任务,你需要向它添加一些动作。一个常见的初学者错误是创建像这样的任务:
task hello {
println 'Hello, world!'
}
当你执行此任务时,你会看到以下输出:
$ gradlew hello
Hello, world!
:hello
从输出中,你可能会得到这样的印象,这好像是在工作,但实际上,“Hello, world!”在任务执行之前就已经打印出来了。为了理解这里发生了什么,我们需要回到基础。在第一章中,我们讨论了 Gradle 构建的生命周期。任何 Gradle 构建都有三个阶段:初始化阶段、配置阶段和执行阶段。当你以与上一个示例相同的方式向任务添加代码时,你实际上是在设置任务的配置。即使你要执行不同的任务,"Hello, world!"消息仍然会显示出来。
如果你想在任务的执行阶段添加动作,请使用以下标记:
task hello << {
println 'Hello, world!'
}
这里的唯一区别是闭包前的 <<。这告诉 Gradle 代码是用于执行阶段,而不是配置阶段。
为了展示差异,考虑以下构建文件:
task hello << {
println 'Execution'
}
hello {
println 'Configuration'
}
我们定义了一个名为 hello 的任务,当它执行时会在屏幕上打印。我们还定义了 hello 任务的配置阶段代码,它会在屏幕上打印 Configuration。尽管配置块是在实际任务代码定义之后定义的,但它仍然会首先执行。这是前面示例的输出:
$ gradlew hello
Configuration
:hello
Execution
小贴士
由于配置阶段的不当使用而导致任务失败是一个常见的错误。当你开始创建自己的任务时,请记住这一点。
由于 Groovy 有很多快捷方式,Gradle 中定义任务有几种方式:
task(hello) << {
println 'Hello, world!'
}
task('hello') << {
println 'Hello, world!'
}
tasks.create(name: 'hello') << {
println 'Hello, world!'
}
前两个块只是使用 Groovy 实现相同功能的不同方式。你可以使用括号,但不是必须的。参数周围也不需要单引号。在这两个块中,我们调用 task() 方法,它接受两个参数:一个用于任务名称的字符串和一个闭包。task() 方法是 Gradle 的 Project 类的一部分。
最后一个块没有使用 task() 方法。相反,它使用了一个名为 tasks 的对象,这是一个 TaskContainer 的实例,并且存在于每个 Project 对象中。这个类提供了一个 create() 方法,它接受一个 Map 和一个闭包作为参数,并返回一个 Task。
写简短的形式很方便,大多数在线示例和教程都会使用它们。然而,在学习的初期,写较长的形式可能很有用。这样,Gradle 似乎就不会那么像魔法一样,而且理解正在发生的事情会容易得多。
任务结构
Task 接口是所有任务的基础,定义了一系列属性和方法。所有这些都被一个名为 DefaultTask 的类实现。这是标准的任务类型实现,当你创建一个新的任务时,它基于 DefaultTask。
注意
从技术上来说,DefaultTask 并不是实现 Task 接口中所有方法的类。Gradle 有一个内部类型名为 AbstractTask,它包含了所有方法的实现。因为 AbstractTask 是内部的,所以我们不能覆盖它。因此,我们关注 DefaultTask,它继承自 AbstractTask,并且可以被覆盖。
每个 Task 都包含一个 Action 对象的集合。当任务执行时,所有这些动作都会按顺序执行。要向任务添加动作,你可以使用 doFirst() 和 doLast() 方法。这两个方法都接受一个闭包作为参数,然后为你将其包装成一个 Action 对象。
你总是需要使用 doFirst() 或 doLast() 来添加代码到任务中,如果你想这段代码成为执行阶段的一部分。我们之前用来定义任务的左移运算符 (<<) 是 doFirst() 方法的快捷方式。
下面是doFirst()和doLast()使用的一个示例:
task hello {
println 'Configuration'
doLast {
println 'Goodbye'
}
doFirst {
println 'Hello'
}
}
当你执行hello任务时,这是输出结果:
$ gradlew hello
Configuration
:hello
Hello
Goodbye
即使打印"Goodbye"的代码行定义在打印"Hello"的代码行之前,但在任务执行时它们会以正确的顺序出现。你甚至可以使用doFirst()和doLast()多次,如本示例所示:
task mindTheOrder {
doFirst {
println 'Not really first.'
}
doFirst {
println 'First!'
}
doLast {
println 'Not really last.'
}
doLast {
println 'Last!'
}
}
执行此任务将返回以下输出:
$ gradlew mindTheOrder
:mindTheOrder
First!
Not really first.
Not really last.
Last!
注意doFirst()总是将操作添加到任务的开始部分,而doLast()则将操作添加到任务的末尾。这意味着在使用这些方法时,特别是当顺序很重要时,你需要格外小心。
在任务排序方面,你可以使用mustRunAfter()方法。此方法允许你影响 Gradle 构建依赖图的方式。当你使用mustRunAfter()时,你指定如果两个任务被执行,其中一个必须始终在另一个之前执行:
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.mustRunAfter task1
同时运行task1和task2将始终导致task1在task2之前执行,无论你指定的顺序如何:
$ gradlew task2 task1
:task1
task1
:task2
task2
mustRunAfter()方法不会在任务之间添加依赖关系;仍然可以在不执行task1的情况下执行task2。如果你需要一个任务依赖于另一个任务,请使用dependsOn()方法。mustRunAfter()和dependsOn()之间的区别最好通过一个示例来解释:
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.dependsOn task1
这就是当你尝试在不执行task1的情况下执行task2时的样子:
$ gradlew task2
:task1
task1
:task2
task2
使用mustRunAfter(),当你同时运行task1和task2时,task1总是先于task2执行,但它们仍然可以独立执行。使用dependsOn(),task2的执行总是触发task1,即使它没有被明确提及。这是一个重要的区别。
使用任务简化发布过程
在你能够将 Android 应用程序发布到 Google Play 商店之前,你需要使用证书对其进行签名。为此,你需要创建自己的密钥库,其中包含一组私钥。当你拥有密钥库和应用程序的私钥时,你可以在 Gradle 中定义签名配置,如下所示:
android {
signingConfigs {
release {
storeFile file("release.keystore")
storePassword "password"
keyAlias "ReleaseKey"
keyPassword "password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
这种方法的缺点是,你的密钥库密码以纯文本形式存储在仓库中。如果你正在从事开源项目,这绝对是不允许的;任何能够访问密钥库文件和密钥库密码的人都可以使用你的身份发布应用程序。为了防止这种情况,你可以在每次构建发布包时创建一个任务来请求发布密码。但这会稍微有些繁琐,并且使得你的构建服务器无法自动生成发布构建。存储密钥库密码的一个好方法是创建一个不包括在仓库中的配置文件。
首先,在项目的根目录下创建一个名为private.properties的文件,并将其添加到该文件中:
release.password = thepassword
我们假设密钥库和密钥本身的密码是相同的。如果你有两个不同的密码,很容易添加第二个属性。
一旦设置好,你可以定义一个名为getReleasePassword的新任务:
task getReleasePassword << {
def password = ''
if (rootProject.file('private.properties').exists()) {
Properties properties = new Properties();
properties.load( rootProject.file('private.properties').newDataInputStream())
password = properties.getProperty('release.password')
}
}
此任务将在项目的根目录中查找名为private.properties的文件。如果此文件存在,任务将加载其内容中的所有属性。properties.load()方法查找键值对,例如在属性文件中定义的release.password。
为了确保任何人都可以在没有私有属性文件的情况下运行脚本,或者处理属性文件存在但密码属性不存在的情况,请添加一个回退。如果密码仍然为空,请在控制台中请求密码:
if (!password?.trim()) {
password = new String(System.console().readPassword("\nWhat's the secret password? "))
}
使用 Groovy 检查一个字符串是否不为空或空是一个非常简洁的过程。password?.trim()中的问号执行空检查,如果password为空,则不会调用trim()方法。我们不需要显式检查空或空字符串,因为在 if 子句的上下文中,null 和空字符串都等于 false。
使用new String()是必要的,因为System.readPassword()返回一个字符数组,需要显式地将其转换为字符串。
一旦我们有了密钥库密码,我们可以配置发布构建的签名配置:
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
现在我们已经完成了任务,我们需要确保在执行发布构建时任务能够被执行。为此,请将这些行添加到build.gradle文件中:
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}
此代码通过添加一个需要在任务被添加到依赖图时运行的闭包来集成到 Gradle 和 Android 插件中。密码不是必需的,直到执行packageRelease任务。因此,我们确保packageRelease依赖于我们的getReleasePassword任务。我们不能直接使用packageRelease.dependsOn()的原因是,Gradle 的 Android 插件根据构建变体动态生成打包任务。这意味着packageRelease任务在 Android 插件发现所有构建变体之前不存在。这个过程在每次构建之前启动。
添加任务和构建钩子后,执行gradlew assembleRelease的结果如下:

如前一个截图所示,private.properties文件不可用,因此任务在控制台中请求密码。在这种情况下,我们还添加了一条消息,解释如何创建属性文件并将密码属性添加到其中,以便使未来的构建更容易。一旦我们的任务获取了密钥库密码,Gradle 就能够打包我们的应用程序并完成构建。
为了使这个任务能够工作,集成到 Gradle 和 Android 插件中是至关重要的。这是一个强大的概念,因此我们将详细探讨这一点。
集成到 Android 插件中
在为 Android 开发时,我们想要影响的大多数任务都与 Android 插件相关。通过挂钩到构建过程,可以增强任务的行为。在前面的例子中,我们已经看到了如何添加一个自定义任务的依赖项,以便将其包含在常规构建过程中。在本节中,我们将探讨一些针对 Android 特定的构建钩子的可能性。
将钩子连接到 Android 插件的一种方法是通过操作构建变体。这样做相当直接;你只需要以下代码片段来遍历应用的所有构建变体:
android.applicationVariants.all { variant ->
// Do something
}
要获取构建变体的集合,你可以使用 applicationVariants 对象。一旦你有一个构建变体的引用,你可以访问并操作其属性,例如名称、描述等。如果你想要为 Android 库使用相同的逻辑,请使用 libraryVariants 而不是 applicationVariants。
注意
注意,我们使用 all() 方法而不是之前提到的 each() 方法来遍历构建变体。这是必要的,因为 each() 方法在构建变体由 Android 插件创建之前的评估阶段被触发。另一方面,all() 方法每次向集合中添加新项目时都会被触发。
此钩子可用于在 APK 保存之前更改其名称,将版本号添加到文件名中。这使得在没有手动编辑文件名的情况下维护 APK 存档变得容易。在下一节中,我们将看到如何实现这一点。
自动重命名 APKs
在操作构建过程的一个常见用例是将打包后的 APKs 重命名为包含版本号。你可以通过遍历应用的不同构建变体来实现这一点,并更改其输出的 outputFile 属性,如下面的代码片段所示:
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
output.outputFile = new File(file.parent, file.name.replace(".apk", "-${variant.versionName}.apk"))
}
}
每个构建变体都有一个输出集合。Android 应用的输出只是一个 APK。每个输出对象都有一个名为 outputFile 的 File 类型属性。一旦你知道输出路径,你就可以对其进行操作。在这个例子中,我们将变体的版本名称添加到文件名中。这将导致一个名为 app-debug-1.0.apk 的 APK,而不是 app-debug.apk。
将 Android 插件的构建钩子与 Gradle 任务的简单性相结合,打开了一个无限可能的世界。在下一节中,我们将看到如何为应用的所有构建变体创建任务。
动态创建新任务
由于 Gradle 的工作方式和任务的构建方式,我们可以在配置阶段轻松创建自己的任务,基于 Android 构建变体。为了演示这个强大的概念,我们将创建一个任务,不仅安装,还可以运行 Android 应用程序的任何构建变体。install 任务是 Android 插件的一部分,但如果您使用 installDebug 任务从命令行界面安装应用程序,那么在安装完成后,您仍然需要手动启动它。本节中我们将创建的任务将消除最后一步。
首先通过连接到我们之前使用的 applicationVariants 属性开始:
android.applicationVariants.all { variant ->
if (variant.install) {
tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
description "Installs the ${variant.description} and runs the main launcher activity."
}
}
}
对于每个变体,我们检查它是否有一个有效的 install 任务。这是因为我们正在创建的新 run 任务将依赖于 install 任务。一旦我们验证了 install 任务的存在,我们就创建一个新的任务,并根据变体的名称命名它。我们还使我们的新任务依赖于 variant.install。这将触发在执行我们的任务之前执行 install 任务。在 tasks.create() 闭包内部,首先添加一个描述,当您执行 gradlew tasks 时会显示。
除了添加描述外,我们还需要添加实际的任务操作。在这个例子中,我们想要启动应用程序。您可以使用 Android 调试工具(ADB)在连接的设备或模拟器上启动应用程序:
$ adb shell am start -n com.package.name/com.package.name.Activity
Gradle 有一个名为 exec() 的方法,它使得执行命令行进程成为可能。为了让 exec() 工作,我们需要提供一个存在于 PATH 环境变量中的可执行文件。我们还需要通过 args 属性传递所有参数,该属性接受一个字符串列表。下面是这个样子的:
doFirst {
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n', "${variant.applicationId}/.MainActivity"]
}
}
要获取完整的包名,请使用包含后缀(如果提供)的构建变体的应用程序 ID。不过,在这种情况下,后缀有一个问题。即使我们添加了后缀,活动的类路径仍然是相同的。例如,考虑以下配置:
android {
defaultConfig {
applicationId 'com.gradleforandroid'
}
buildTypes {
debug {
applicationIdSuffix '.debug'
}
}
包名是 com.gradleforandroid.debug,但活动的路径仍然是 com.gradleforandroid.Activity。为了确保我们得到正确的类到活动,从应用程序 ID 中移除后缀:
doFirst {
def classpath = variant.applicationId
if(variant.buildType.applicationIdSuffix) {
classpath -= "${variant.buildType.applicationIdSuffix}"
}
def launchClass = "${variant.applicationId}/${classpath}.MainActivity"
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n', launchClass]
}
}
首先,我们创建一个名为 classpath 的变量,基于应用程序 ID。然后我们找到由 buildType.applicationIdSuffix 属性提供的后缀。在 Groovy 中,可以使用减号运算符从一个字符串中减去另一个字符串。这些更改确保在安装后运行应用程序时,使用后缀不会失败。
创建您自己的插件
如果您有一系列 Gradle 任务想要在多个项目中重用,将这些任务提取到一个自定义插件中是有意义的。这使得您可以自己重用构建逻辑,并与他人共享。
插件可以用 Groovy 编写,也可以用其他利用 JVM 的语言编写,例如 Java 和 Scala。实际上,Gradle 的 Android 插件的大部分代码是用 Java 和 Groovy 结合编写的。
创建一个简单的插件
要提取已存储在构建配置文件中的构建逻辑,您可以在 build.gradle 文件中创建一个插件。这是开始使用自定义插件的最简单方法。
要创建一个插件,创建一个新的类来实现 Plugin 接口。我们将使用本章之前编写的代码,该代码动态创建 run 任务。我们的插件类看起来像这样:
class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
if (variant.install) {
project.tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
// Task definition
}
}
}
}
}
Plugin 接口定义了一个 apply() 方法。当插件在构建文件中使用时,Gradle 会调用此方法。项目作为参数传递,以便插件可以配置项目或使用其方法和属性。在前面的示例中,我们不能直接调用 Android 插件中的属性。相反,我们需要首先访问项目对象。请注意,这要求在应用我们的自定义插件之前,必须先应用 Android 插件。否则,project.android 将引发异常。
任务的代码与之前相同,除了一个方法调用:我们不再调用 exec(),而是现在需要调用 project.exec()。
为了确保插件应用于我们的构建配置,将此行添加到 build.gradle:
apply plugin: RunPlugin
分发插件
为了分发插件并与他人共享,您需要将其移动到一个独立的模块(或项目)中。一个独立的插件有自己的构建文件来配置依赖项和分发方式。此模块生成一个 JAR 文件,其中包含插件类和属性。您可以使用此 JAR 文件在多个模块和项目中应用插件,并与他人共享。
与任何 Gradle 项目一样,创建一个 build.gradle 文件来配置构建:
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
由于我们是用 Groovy 编写插件的,我们需要应用 Groovy 插件。Groovy 插件扩展了 Java 插件,并使我们能够构建和打包 Groovy 类。Groovy 和纯 Java 都受支持,因此如果您喜欢,可以混合使用。您甚至可以使用 Groovy 扩展 Java 类,或者相反。这使得即使您不自信使用 Groovy 做所有事情,也能轻松开始。
我们的构建配置文件包含两个依赖项:gradleApi() 和 localGroovy()。Gradle API 是从我们的自定义插件访问 Gradle 命名空间所必需的,而 localGroovy() 是与 Gradle 安装一起提供的 Groovy SDK 的分发。Gradle 默认为我们提供了这些依赖项,以便于使用。如果 Gradle 没有提供这些依赖项,我们就必须手动下载并引用它们。
注意
如果您计划公开分发您的插件,请确保在构建配置文件中指定组和版本信息,如下所示:
group = 'com.gradleforandroid'
version = '1.0'
要开始使用我们独立模块中的代码,我们首先需要确保使用正确的目录结构:
plugin
└── src
└── main
├── groovy
│ └── com
│ └── package
│ └── name
└── resources
└── META-INF
└── gradle-plugins
与任何其他 Gradle 模块一样,我们需要提供一个src/main目录。因为这是一个 Groovy 项目,所以main的子目录被称为groovy而不是java。还有一个名为resources的main子目录,我们将用它来指定插件的属性。
我们在包目录中创建一个名为RunPlugin.groovy的文件,在其中我们定义了插件对应的类:
package com.gradleforandroid
import org.gradle.api.Project
import org.gradle.api.Plugin
class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
// Task code
}
}
}
为了让 Gradle 能够找到插件,我们需要提供一个属性文件。将此属性文件添加到src/main/resources/META-INF/gradle-plugins/目录中。文件名需要与我们的插件 ID 匹配。对于RunPlugin,文件名为com.gradleforandroid.run.properties,以下是其内容:
implementation-class=com.gradleforandroid.RunPlugin
属性文件中只包含实现Plugin接口的类的包和名称。
当插件和属性文件准备就绪后,我们可以使用gradlew assemble命令来构建插件。这将在构建输出目录中创建一个 JAR 文件。如果你想要将插件推送到 Maven 仓库,首先需要应用 Maven 插件:
apply plugin: 'maven'
接下来,你需要配置uploadArchives任务,如下所示:
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('repository_url'))
}
}
}
uploadArchives任务是一个预定义的任务。一旦你在任务中配置了仓库,你就可以执行它来发布你的插件。本书不会涵盖如何设置 Maven 仓库。
如果你想要使你的插件公开可用,考虑将其发布到 Gradleware 的插件门户(plugins.gradle.org)。插件门户拥有大量的 Gradle 插件(不仅限于 Android 开发),当你想要扩展 Gradle 的默认行为时,这里是必去之地。你可以在plugins.gradle.org/docs/submit的文档中找到有关如何发布插件的信息。
小贴士
本书不会涵盖为自定义插件编写测试,但如果你计划使你的插件公开可用,则强烈建议这样做。你可以在 Gradle 用户指南gradle.org/docs/current/userguide/custom_plugins.html#N16CE1中找到有关为插件编写测试的更多信息。
使用自定义插件
要使用插件,我们需要将其添加到buildscript块中作为依赖项。首先,我们需要配置一个新的仓库。仓库的配置取决于插件是如何分发的。其次,我们需要在dependencies块中配置插件的类路径。
如果我们想要包含在早期示例中创建的 JAR 文件,我们可以定义一个flatDir仓库:
buildscript {
repositories {
flatDir { dirs 'build_libs' }
}
dependencies {
classpath 'com.gradleforandroid:plugin'
}
}
如果我们将插件上传到 Maven 或 Ivy 仓库,这会有所不同。我们已经在 第三章 中介绍了依赖管理,即 管理依赖,因此我们在此不会重复不同的选项。
在设置依赖之后,我们需要应用插件:
apply plugin: com.gradleforandroid.RunPlugin
当使用 apply() 方法时,Gradle 会创建插件类的实例,并执行插件自己的 apply() 方法。
摘要
在本章中,我们发现了 Groovy 与 Java 的不同之处,以及 Groovy 在 Gradle 中的使用方法。我们看到了如何创建自己的任务,以及如何挂钩到 Android 插件,这给了我们很多操作构建过程或动态添加自己任务的权力。
在本章的最后部分,我们探讨了创建插件,并确保通过创建独立插件可以在多个项目中重用。关于插件还有很多东西要学习,但不幸的是,我们无法在这本书中涵盖所有内容。幸运的是,Gradle 用户指南在 gradle.org/docs/current/userguide/custom_plugins.html 中对所有可能性进行了详尽的描述。
在下一章中,我们将讨论持续集成(CI)的重要性。有了良好的持续集成系统,我们可以通过一键操作来构建、测试和部署应用程序和库。因此,持续集成是构建自动化的一般重要部分。
第八章. 设置持续集成
持续集成 (CI) 是一种开发实践,要求团队中的开发人员定期集成他们的工作,通常每天多次。每次向主仓库的推送都会通过自动构建进行验证。这种做法有助于尽早发现问题,从而加快开发速度,并提高代码质量。伟大的马丁·福勒(Martin Fowler)就这一主题撰写了一篇文章,解释了概念并描述了最佳实践(martinfowler.com/articles/continuousIntegration.html)
为 Android 设置 CI 有几种选择。最常用的有 Jenkins、TeamCity 和 Travis CI。Jenkins 拥有最大的生态系统,大约有一千个可用的插件。它也是一个拥有众多贡献者的开源项目。TeamCity 是 JetBrains 公司的产品,该公司还创建了 IntelliJ IDEA。Travis CI 是一个相对较新的参与者,主要关注开源项目。
我们将探讨这些 CI 系统,以及如何让 Gradle 在这些系统上工作。在本章结束时,我们将提到一些 Gradle 技巧,以使 CI 更容易,无论选择的 CI 系统是什么。
在本章中,我们将涵盖以下主题:
-
Jenkins
-
TeamCity
-
Travis CI
-
进一步自动化
Jenkins
Jenkins 最初于 2005 年由 Sun Microsystems 以 Hudson 的名称发布。多年来,它已成为 Java 社区中最受欢迎的 CI 系统。在 Sun Microsystems 被甲骨文公司收购后不久,Oracle 与 Java 社区在 Hudson 问题上发生了冲突。当这个问题无法解决时,社区继续以 Jenkins 的名义在该项目上工作,因为 Hudson 这个名字归 Oracle 所有。
Jenkins 的强大之处在于其插件系统。任何需要构建系统新功能的人都可以创建一个新的插件,以扩展 Jenkins 的功能。这也是为什么为 Android 应用程序或库设置自动构建相当简单的原因。
设置 Jenkins
如果你还没有在你的构建机器上安装并运行 Jenkins,请从网站 (jenkins-ci.org) 下载它,并按照步骤操作。
在开始实际的 Jenkins 设置之前,你需要确保你有构建 Android 应用程序和库所需的全部库。要在 Java 中构建任何内容,你首先需要下载并安装 JDK,它可以从 Java 网站 (www.oracle.com/technetwork/java/javase/downloads/index.html) 下载。
你还需要确保你已经安装了 Android SDK 和构建工具。在你的构建服务器上安装 IDE 不是必需的,除非你计划在构建机器上打开项目。如果你只想安装 SDK 工具,你可以从 Android 开发者网站下载它们(developer.android.com/sdk/index.html#Other)。一旦你下载并安装了包,你需要在 SDK 目录中运行 android 可执行文件,这样你就可以安装所需的 API 和构建工具。
一旦安装了 Java 和 Android SDK,你需要在 Jenkins 中配置这些。首先打开你的网络浏览器,导航到构建服务器上的 Jenkins 主页。转到 管理 Jenkins | 配置系统 并滚动到 全局属性。添加两个环境变量 ANDROID_HOME 和 JAVA_HOME,并将它们的值设置为正确的目录,如图所示:

你还需要安装 Gradle 插件。转到 管理 Jenkins | 管理插件,导航到 可用 选项卡,并搜索 Gradle。当你找到 Gradle 插件时,只需勾选复选框,然后点击 现在下载并在重启后安装。此插件使得创建涉及 Gradle 的构建步骤成为可能。
配置构建
一旦安装了所有必需的组件,你就可以在 Jenkins 中创建一个 CI 项目。你应该做的第一件事是设置 VCS 仓库,这样 Jenkins 就知道从哪里获取你项目的源代码。你可以设置 Jenkins 根据仓库活动自动构建你的应用程序或库,使用构建触发器,或者你可以选择只进行手动构建。要执行实际的构建,你需要添加一个新的构建步骤来调用 Gradle 脚本。你可以配置 Jenkins 使用默认存在于 Android 项目中的 Gradle Wrapper。使用 Gradle Wrapper 不仅消除了在构建服务器上手动安装 Gradle 的需要,还确保了 Gradle 的任何更新都会自动处理。检查 使 gradlew 可执行 复选框也是一个好主意。这解决了在 Microsoft Windows 机器上创建项目时执行 Gradle Wrapper 的权限问题。
你可以为构建步骤输入一个不错的描述,并可选地添加两个开关 info 和 stacktrace。info 开关用于打印构建过程的更多信息,这在出现问题时可能很有用。如果构建引发异常,stacktrace 开关将打印出此异常的堆栈跟踪。有时你可能需要更详细的信息,在这种情况下,你可以使用 full-stacktrace 开关。
为了最终确定配置,请指定您想要执行的任务。首先,执行clean任务,以确保没有留下任何来自之前构建的输出。其次,执行build任务,这将触发所有变体的构建。Jenkins 配置应如下所示:

保存项目配置后,您可以运行构建。
注意
如果您的构建服务器安装在 64 位 Linux 机器上,您可能会遇到这个异常java.io.IOException: Cannot run program "aapt": error=2, No such file or directory。这是因为 AAPT 是一个 32 位应用程序,需要在 64 位机器上运行时需要一些额外的库。要安装必要的库,请使用以下命令:
$ sudo apt-get install lib32stdc++6 lib32z1
如果构建没有问题完成,它将为所有构建变体创建 APK。您可以使用特定的 Gradle 任务来分发这些 APK。我们将在本章末尾提到自动分发,因为它并不特定于任何构建系统。

TeamCity
与 Jenkins 不同,TeamCity 是一个专有产品,仅对开源项目免费使用。它由 JetBrains 创建和管理。这家公司还创建了 IntelliJ IDEA,这是 Android Studio 基于的 IDE。TeamCity 默认支持使用 Gradle 进行 Android 构建。
设置 TeamCity
如果您还没有安装 TeamCity,请从 JetBrains 网站下载它(www.jetbrains.com/teamcity)并按照步骤操作。
要开始使用 TeamCity 构建 Android 应用程序和库,您需要确保 JDK、Android SDK 和 Android 构建工具已安装到您的构建服务器上。您可以在Jenkins部分找到这些说明。您还需要将ANDROID_HOME添加到机器的环境变量中,并指向 Android SDK 目录。
与 Jenkins 不同,TeamCity 不需要任何插件来触发 Gradle 构建,因为 TeamCity 内置了对运行 Gradle 的支持。
配置构建
要设置 Android 构建,您首先需要创建一个新的项目。您需要提供的只是名称。一旦项目创建完成,您就可以开始配置它。首先,您需要添加一个 VCS 根,以便 TeamCity 可以找到您项目的源代码。然后您需要创建一个新的构建配置。您还需要将 VCS 根附加到构建配置上。设置完成后,您可以添加一个新的构建步骤。如果您按下自动检测构建步骤按钮,TeamCity 将尝试根据项目的内容确定必要的构建步骤。对于基于 Gradle 的 Android 项目,结果看起来像这样:

TeamCity 会检测到项目使用 Gradle,甚至 Gradle Wrapper 也存在。你只需选择 Gradle 构建步骤,并将其添加到构建配置中。如果你不需要进行任何高级操作,这足以确保你的 Android 应用被构建。你可以通过打开项目概览并点击 Android 项目的 运行… 按钮来测试配置。
Travis CI
如果你的项目仓库托管在 GitHub 上,你可以为你的自动化构建使用 Travis CI。Travis CI (travis-ci.org) 是一个开源的托管持续集成系统,对于公共仓库是免费的。对于私有仓库有一个付费计划,但在这本书中,我们只会查看免费选项。
Travis 会检测到当有新的提交推送到仓库时,并自动启动新的构建。默认情况下,Travis 会构建所有分支,而不仅仅是 master 分支。它还会自动构建拉取请求;这对于开源项目来说是一个有用的功能。
由于 Travis 内部的工作方式,你不能配置构建服务器本身。相反,你需要创建一个包含 Travis 构建应用或库所需的所有信息的配置文件。
配置构建
如果你想为你的项目启用 Travis 构建,首先你需要登录到 Travis CI 并将你的账户与 GitHub 连接。一旦完成,你需要在设置中启用你想要构建的项目。
为了配置构建过程,Travis 需要你创建一个名为 .travis.yml 的文件,该文件包含整个设置。为了配置 Android 项目,你需要定义语言并添加一些 Android 特定的属性:
language: android
android:
components:
# The build tools version used by your project
- build-tools-22.0.1
# The SDK version used to compile your project
- android-22
# Additional components
- extra-android-m2repository
语言设置指定了你想要运行哪种构建过程。在这种情况下,你正在构建一个 Android 应用。Android 特定的属性包括需要使用的构建工具版本和 Android SDK 版本。Travis 将在运行 build 任务之前下载这些工具。如果你使用了支持库或 Google Play 服务,你需要明确指定这一点,因为 Travis 需要下载这些依赖项的仓库。
注意
配置构建工具和 SDK 版本不是强制性的,但如果你确保版本与你在 build.gradle 文件中指定的版本一致,你会遇到更少的问题。
如果你在一个 Microsoft Windows 上创建 Android 项目,Gradle Wrapper 文件已知存在权限问题。因此,在运行实际的构建脚本之前修复权限是一个好主意。你可以添加一个预构建步骤,如下所示:
before_script:
# Change Gradle wrapper permissions
- chmod +x gradlew
要开始实际的构建,将以下行添加到 Travis 配置文件中:
# Let's build
script: ./gradlew clean build
这个命令将运行 Gradle Wrapper,就像你在开发机上做的那样,并执行 clean 和 build 任务。
当你完成 Travis 构建的配置后,你可以提交并将文件推送到项目的 GitHub 仓库。如果一切设置正确,Travis 将开始构建过程,你可以在 Travis 网站上跟踪。以下是项目成功构建时的样子:

Travis 也会在每次构建后发送电子邮件报告。如果你是定期收到拉取请求的开源库的维护者,这可能特别有用。当构建成功时,Travis 的报告电子邮件看起来是这样的:

你会很快注意到 Travis 有一个很大的缺点,那就是速度。Travis 不会给你一台特定的机器,而是为每个你触发的构建启动一个纯净的虚拟机。这意味着对于每个新的构建,Travis 都必须下载和安装 Android SDK 和构建工具,然后才能开始构建你的应用或库。
从积极的一面来看,Travis 是免费且公开的,这使得它非常适合开源项目。Travis 还会自动构建拉取请求,这可以在有人提交补丁到你的代码时给你带来安心。
进一步自动化
大多数现代持续集成系统默认支持 Gradle,或者通过插件支持。这意味着你不仅可以构建你的应用或库,还可以创建各种 Gradle 任务来进一步自动化构建。与在 CI 系统本身中定义额外的构建步骤相比,使用 Gradle 任务定义额外的构建步骤的优势在于,这些额外的构建步骤变得更加便携。在开发机器上运行自定义 Gradle 任务很容易。另一方面,没有安装 Jenkins,自定义 Jenkins 构建步骤是无法运行的。在某个 CI 系统中拥有额外的构建步骤也使得切换到不同的 CI 系统变得更加困难。Gradle 任务也可以轻松地移植到不同的项目中。在本节中,我们将探讨使用 Gradle 任务和插件进一步自动化构建和部署应用和库的几种方法。
SDK 管理插件
你可能会遇到的一个问题是构建服务器上的 Android SDK 没有更新。当你更新你的应用或库的 SDK 版本时,你还需要在构建服务器上安装新的 SDK。如果你有多个构建代理,这会变得非常麻烦。
多亏了社区的努力,有一个 Gradle 插件负责检查构建是否依赖于一个不存在的 Android SDK 版本。如果 SDK 不存在,插件会自动下载它。
SDK 管理插件不仅会下载构建配置文件中指定的编译 SDK,还会下载正确的构建工具和平台工具版本。如果你的项目依赖于支持库或 Google Play 服务,插件还会下载这些指定的版本。
SDK 管理器插件是一个开源插件,你可以在 GitHub 上找到它的源代码(github.com/JakeWharton/sdk-manager-plugin)。
运行测试
如果你想在构建服务器上构建过程中运行单元测试(JUnit 或 Robolectric),你只需将相应的任务添加到 Gradle 执行中。如果你想运行任何功能测试,你需要一个模拟器来安装你的应用,然后你可以使用 gradlew connectedAndroidTest 运行测试。
运行模拟器的最简单方法是直接在构建服务器上启动一个模拟器,并始终保持其开启状态。不幸的是,这不是一个最佳解决方案,因为 Android 模拟器很容易发生随机崩溃,尤其是在你连续多天保持其开启状态时。
如果你正在使用 Jenkins,有一个名为 Android Emulator Plugin (wiki.jenkins-ci.org/display/JENKINS/Android+Emulator+Plugin) 的插件,可以配置为为你的应用或库的每个构建启动一个模拟器。TeamCity 也有一个活跃的插件生态系统,其中有一个名为 Android Emulator 的插件,它以与 Jenkins 插件相同的方式帮助设置模拟器。你可以在官方 TeamCity 插件页面上找到它,以及其他 TeamCity 插件(confluence.jetbrains.com/display/TW/TeamCity+Plugins)。
Travis CI 有启动模拟器的功能,但这是一个实验性功能。如果你想尝试,请将以下片段添加到你的 .travis.yml 配置文件中,以便在 Travis 构建过程中启动 Android 模拟器:
# Emulator Management: Create, Start and Wait
before_script:
- echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
- emulator -avd test -no-skin -no-audio -no-window &
- android-wait-for-emulator
- adb shell input keyevent 82 &
android-wait-for-emulator 指令告诉 Travis 等待模拟器启动。当模拟器启动后,执行 adb shell input keyevent 82 & 来解锁屏幕。之后,你只需告诉 Gradle 运行测试即可。
持续部署
为了帮助开发者自动部署 Android 应用,Google 发布了 Google Play 开发者 API,这是一个用于程序化地将 APK 推送到 Google Play 的 API (developers.google.com/android-publisher)。此 API 消除了你需要打开浏览器、登录 Google Play 并使用网页界面上传 APK 的需求。而不是基于 Google Play 开发者 API 创建自己的发布脚本,你可以在构建成功后直接从构建系统中使用许多插件之一将 APK 推送到 Google Play。
有一个名为Google Play Android Publisher的 Jenkins 插件(wiki.jenkins-ci.org/display/JENKINS/Google+Play+Android+Publisher+Plugin)可以为您处理这些任务。然而,更好的选择是使用 Gradle 插件,这样您就可以从任何设备以及任何类型的持续集成系统中执行发布任务。Android 社区的一些人创建了一个围绕 Google Play 开发者 API 构建的 Gradle 插件,它使您能够配置整个发布过程。您可以在 GitHub 上找到 Gradle Play Publisher Gradle 插件的源代码(github.com/Triple-T/gradle-play-publisher)。它也通过 Maven Central 或 JCenter 提供。
要开始使用此插件,请将以下内容添加到您的main build.gradle文件中:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.triplet.gradle:play-publisher:1.0.4'
}
}
然后在您的 Android 模块的build.gradle文件中应用此插件:
apply plugin: 'play'
当您将 Gradle Play Publisher 插件应用到构建中时,您将获得一些新的可用任务:
-
publishApkRelease上传 APK 和最近的变化 -
publishListingRelease上传描述和图片 -
publishRelease上传所有内容
如果您有不同的构建变体,您还可以执行特定于变体的这些任务的版本,例如publishApkFreeRelease和publishApkPaidRelease。
要获取访问 Google Play 开发者 API 的权限,您需要设置一个服务帐户。此设置超出了本书的范围,但如果您想使用 Gradle Play Publisher 插件,则这是必需的。要开始,请遵循developers.google.com/android-publisher/getting_started中 Google Play 开发者 API 文档中的步骤。
一旦您创建了服务帐户,您可以在构建配置文件中输入凭证,如下所示:
play {
serviceAccountEmail = 'serviceaccount'
pk12File = file('key.p12')
}
play块用于 Gradle Play Publisher 插件特有的属性。除了服务帐户凭证外,您还可以指定 APK 应推送到的轨道:
play {
track = 'production'
}
默认轨道是alpha,但您也可以将其更改为beta或production。
贝塔分布
对于 Android 应用的 beta 测试有很多选项,例如 Google Play 商店本身的 beta 轨道。另一个选项是Crashlytics(crashlytics.com),它与 Gradle 有很好的集成。Crashlytics 团队创建了一个自定义插件,不仅创建新的 Gradle 任务以将构建发布到他们的平台,而且还挂钩到 Android 插件的任务以处理 ProGuard 映射。
要开始使用 Crashlytics,请遵循他们网站上的步骤。一旦设置完成,它将开始连接到你的构建。Crashlytics 插件暴露了一个名为 crashlyticsUploadDistributionInternal 的新任务,可以用来将 APK 上传到 Crashlytics。要推送你应用的新版本,你首先需要使用 build 或 assemble 任务来构建它。一旦 APK 准备就绪,你可以使用 crashlyticsUploadDistributionInternal 任务将其上传到 Crashlytics。Crashlytics 插件为项目中的每个构建变体创建一个上传任务。
多亏了自定义 Gradle 插件,开发者开始使用 Crashlytics 非常容易。它还使得将测试构建上传到 Crashlytics 变得非常简单,因为你在构建过程中只需要执行一个额外的任务。这是一个很好的例子,说明了正确使用 Gradle 的强大之处,以及一个好的 Gradle 插件如何让开发者的生活变得更加轻松。
摘要
在本章中,我们介绍了几种流行的持续集成系统,并解释了如何使用它们来自动化构建 Android 应用和库。你学习了如何配置 CI 系统,使用 Gradle 构建 Android 项目。然后我们探讨了几个 Gradle 插件,以帮助我们进一步自动化构建和部署过程,并解释了如何在构建服务器上自动运行测试。
在下一章中,我们将探讨 Gradle 的一些更高级的功能以及基于 Gradle 的构建优化。我们还将了解如何通过直接使用 Gradle 中的 Ant 任务,并逐步将它们移植到 Gradle 中,来迁移一个大的 Ant 构建配置。
第九章. 高级构建定制
现在你已经了解了 Gradle 的工作原理,如何创建自己的任务和插件,如何运行测试,以及如何设置持续集成,你几乎可以称自己为 Gradle 专家。本章包含了一些我们之前章节中没有提到的技巧和窍门,这些技巧和窍门使得使用 Gradle 构建、开发和部署 Android 项目变得更加容易。
在本章中,我们将涵盖以下主题:
-
减小 APK 文件大小
-
加速构建
-
忽略 Lint
-
从 Gradle 使用 Ant
-
高级应用部署
我们将首先探讨如何减小构建输出的尺寸以及为什么这很有用。
减小 APK 文件大小
在过去几年中,APK 文件的大小急剧增加。这有几个原因——更多的库对 Android 开发者可用,增加了更多的密度,并且应用在功能上总体上变得更加丰富。
尽量保持 APK 尽可能小是一个好主意。这不仅因为 Google Play 上的 APK 文件有 50 MB 的限制,而且较小的 APK 也意味着用户可以更快地下载和安装应用,并且可以降低内存占用。
在本节中,我们将查看 Gradle 构建配置文件中的一些属性,我们可以通过操作这些属性来减小 APK 文件。
ProGuard
ProGuard 是一个 Java 工具,它不仅可以在编译时缩小、优化、混淆和预验证你的代码,还可以在编译时进行这些操作。它遍历你的应用中的所有代码路径,以找到未使用的代码并将其删除。ProGuard 还会重命名你的类和字段。这个过程可以降低应用的占用空间,并使代码更难以逆向工程。
Android 插件 Gradle 有一个布尔属性,称为 minifyEnabled,在构建类型上需要将其设置为 true 以启用 ProGuard:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
当你将 minifyEnabled 设置为 true 时,proguardRelease 任务将被执行,并在构建过程中调用 ProGuard。
在启用 ProGuard 后重新测试你的整个应用是一个好主意,因为它可能会删除你仍然需要的某些代码。这是一个让许多开发者对 ProGuard 产生警惕的问题。为了解决这个问题,你可以定义 ProGuard 规则以排除某些类被删除或混淆。proguardFiles 属性用于定义包含 ProGuard 规则的文件。例如,为了保留一个类,你可以添加一个简单的规则,如下所示:
-keep public class <MyClass>
getDefaultProguardFile('proguard-android.txt') 方法从名为 proguard-android.txt 的文件中获取默认的 ProGuard 设置,该文件位于 Android SDK 的 tools/proguard 文件夹中。proguard-rules.pro 文件默认情况下会被 Android Studio 添加到新的 Android 模块中,因此你可以在该文件中简单地添加针对模块的特定规则。
注意
ProGuard 规则对您构建的每个应用或库都不同,因此在这本书中我们不会过多地详细介绍。如果您想了解更多关于 ProGuard 和 ProGuard 规则的信息,请查看官方 Android ProGuard 文档developer.android.com/tools/help/proguard.html。
除了压缩 Java 代码外,压缩使用的资源也是一个好主意。
资源压缩
Gradle 和 Gradle 的 Android 插件可以在构建时移除所有未使用的资源,当应用正在打包时。如果您忘记移除旧资源,这可能会很有用。另一个用例是当您导入一个包含大量资源的库,但您只使用其中的一小部分时。您可以通过启用资源压缩来修复这个问题。有自动或手动两种方法来压缩资源。
自动压缩
最简单的方法是配置构建中的shrinkResources属性。如果您将此属性设置为true,Android 构建工具将自动尝试确定哪些资源未使用,并且不会将它们包含在 APK 中。
虽然您必须启用 ProGuard,但使用此功能有一个要求。这是由于资源压缩的工作方式,因为 Android 构建工具无法确定哪些资源未使用,直到引用这些资源的代码被移除。
下面的代码片段显示了如何在特定构建类型上配置自动资源压缩:
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}
如果您想查看启用自动资源压缩后 APK 变得多小,您可以运行shrinkReleaseResources任务。此任务会打印出它减少了多少包的大小:
:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB to 354KB: Removed 18%
您可以通过在构建命令中添加--info标志来获取从 APK 中移除的资源详细概述:
$ gradlew clean assembleRelease --info
当您使用此标志时,Gradle 会打印出大量有关构建过程的额外信息,包括它未包含在最终构建输出中的每个资源。
自动资源压缩的一个问题是可能会移除过多的资源。特别是动态使用的资源可能会意外被移除。为了防止这种情况,您可以在res/raw/目录下放置一个名为keep.xml的文件中定义异常。一个简单的keep.xml文件看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<resources
tools:keep="@layout/keep_me,@layout/also_used_*"/>
keep.xml文件本身也将从最终结果中移除。
手动压缩
一种不那么激进的移除资源的方法是移除某些语言文件或特定密度的图像。一些库,如 Google Play Services,包含很多语言。如果您的应用只支持一到两种语言,那么在最终的 APK 中包含这些库的所有语言文件就没有意义了。您可以使用resConfigs属性来配置您想要保留的资源,其余的将被丢弃。
如果你只想保留英语、丹麦语和荷兰语字符串,你可以这样使用 resConfigs:
android {
defaultConfig {
resConfigs "en", "da", "nl"
}
}
你也可以为密度桶做同样的事情,如下所示:
android {
defaultConfig {
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}
甚至可以将语言和密度结合起来。事实上,可以使用这个属性限制每种类型的资源。
如果你设置 ProGuard 遇到困难,或者你只是想移除你的应用不支持的语言或密度的资源,那么使用 resConfigs 是开始资源缩减的好方法。
加快构建速度
许多开始使用 Gradle 的 Android 开发者抱怨编译时间过长。构建可能比使用 Ant 更长,因为 Gradle 在每次执行任务时都会经历构建生命周期的三个阶段。这使得整个过程非常可配置,但也相当慢。幸运的是,有几种方法可以加快 Gradle 构建速度。
Gradle 属性
调整 Gradle 构建速度的一种方法是通过更改一些默认设置。我们已经在第五章中提到了并行构建执行,管理多模块构建,但还有一些其他设置你可以调整。
只为了回顾一下,你可以通过在项目根目录中的 gradle.properties 文件中设置一个属性来启用并行构建。你只需要添加以下这一行:
org.gradle.parallel=true
另一个容易实现的改进是启用 Gradle 守护进程,当你第一次运行构建时,它会启动一个后台进程。随后的构建将重用那个后台进程,从而减少了启动成本。只要你在使用 Gradle,进程就会保持活跃,并在空闲三小时后终止。在短时间内多次使用 Gradle 时,使用守护进程特别有用。你可以在 gradle.properties 文件中这样启用守护进程:
org.gradle.daemon=true
在 Android Studio 中,Gradle 守护进程默认是启用的。这意味着在 IDE 内部进行第一次构建之后,接下来的构建会稍微快一些。然而,如果你从命令行界面进行构建;那么 Gradle 守护进程是禁用的,除非你在属性中启用它。
为了加快编译本身的速度,你可以调整 Java 虚拟机(JVM)的参数。有一个名为 jvmargs 的 Gradle 属性,它允许你为 JVM 的内存分配池设置不同的值。有两个参数直接影响你的构建速度,即 Xms 和 Xmx。Xms 参数用于设置要使用的初始内存量,而 Xmx 参数用于设置最大值。你可以在 gradle.properties 文件中手动设置这些值,如下所示:
org.gradle.jvmargs=-Xms256m -Xmx1024m
你需要设置一个数量和一个单位,这个单位可以是 k(千字节)、m(兆字节)或 g(千兆字节)。默认情况下,最大内存分配(Xmx)设置为 256 MB,而起始内存分配(Xms)则没有设置。最佳设置取决于你电脑的能力。
最后一个可以配置以影响构建速度的属性是 org.gradle.configureondemand。如果您有包含多个模块的复杂项目,这个属性尤其有用,因为它试图通过跳过对执行任务不是必需的模块来限制配置阶段的时间。如果您将此属性设置为 true,Gradle 将在运行配置阶段之前尝试确定哪些模块有配置更改,哪些没有。如果您项目中只有一个 Android 应用和一个库,这个功能可能不太有用。如果您有很多松散耦合的模块,这个功能可以为您节省大量的构建时间。
小贴士
系统级 Gradle 属性
如果您想将这些属性系统性地应用到所有基于 Gradle 的项目中,您可以在家目录中的 .gradle 文件夹中创建一个 gradle.properties 文件。在 Microsoft Windows 上,此目录的完整路径是 %UserProfile%\.gradle,在 Linux 和 Mac OS X 上是 ~/.gradle。在您的家目录中设置这些属性是一个好习惯,而不是在项目级别上。这样做的原因是您通常希望降低构建服务器上的内存消耗,而构建时间则不那么重要。
Android Studio
可以更改以加快编译过程的 Gradle 属性也可以在 Android Studio 设置中配置。要查找编译器设置,请打开 设置 对话框,然后导航到 构建、执行、部署 | 编译器。在该屏幕上,您可以找到并行构建、JVM 选项、按需配置等设置。这些设置仅适用于基于 Gradle 的 Android 模块。请查看以下截图:

在 Android Studio 中配置这些设置比在构建配置文件中手动配置要容易,设置对话框使得查找影响构建过程的属性变得简单。
分析
如果您想找出哪些构建部分减慢了进程,您可以分析整个构建过程。您可以通过在执行 Gradle 任务时添加 --profile 标志来实现这一点。当您提供此标志时,Gradle 会创建一个分析报告,该报告可以告诉您构建过程中哪些部分耗时最长。一旦您知道了瓶颈在哪里,您就可以进行必要的更改。报告以 HTML 文件的形式保存在您的模块中的 build/reports/profile 目录下。
这是执行多模块项目构建任务后生成的报告:

性能报告显示了在执行任务时每个阶段花费的时间概述。在以下摘要下方是 Gradle 为每个模块配置阶段花费时间的概述。报告中有两个更多部分在截图中没有显示。依赖解析部分显示了解析依赖项所需的时间,按模块划分。最后,任务执行部分包含了一个非常详细的任务执行概述。这个概述按执行时间从高到低排列了每个任务的计时。
Jack 和 Jill
如果你愿意使用实验性工具,你可以启用 Jack 和 Jill 来加速构建过程。Jack(Java Android Compiler Kit)是一个新的 Android 构建工具链,它可以将 Java 源代码直接编译成 Android Dalvik 可执行文件(dex)格式。它有自己的.jack库格式,并负责打包和压缩。Jill(Jack Intermediate Library Linker)是一个可以将.aar和.jar文件转换为.jack库的工具。这些工具仍然相当实验性,但它们被制作出来是为了提高构建时间和简化 Android 构建过程。不建议在生产版本的项目中开始使用 Jack 和 Jill,但它们被提供出来,以便你可以尝试它们。
要使用 Jack 和 Jill,你需要使用版本 21.1.1 或更高版本的构建工具,以及 Gradle 的 Android 插件版本 1.0.0 或更高。启用 Jack 和 Jill 就像在defaultConfig块中设置一个属性一样简单:
android {
buildToolsRevision '22.0.1'
defaultConfig {
useJack = true
}
}
你还可以在特定的构建类型或产品风味上启用 Jack 和 Jill。这样,你就可以继续使用常规构建工具链,并在旁边进行实验性构建:
android {
productFlavors {
regular {
useJack = false
}
experimental {
useJack = true
}
}
}
一旦将useJack设置为true,最小化和混淆将不再通过 ProGuard 进行,但你仍然可以使用 ProGuard 规则语法来指定某些规则和异常。使用我们之前在讨论 ProGuard 时提到的相同的proguardFiles方法。
忽略 Lint
当你使用 Gradle 执行发布构建时,会对你的代码执行 Lint 检查。Lint 是一个静态代码分析工具,它会标记出布局和 Java 代码中的潜在错误。在某些情况下,它甚至可能阻止构建过程。如果你之前没有在项目中使用过 Lint,并且想要迁移到 Gradle,Lint 可能会出现很多错误。为了至少让构建工作,你可以配置 Gradle 忽略 Lint 错误,并通过禁用abortOnError来防止它们终止构建。这应该只是一种临时解决方案,因为忽略 Lint 错误可能会导致诸如缺失翻译等问题,这些问题可能导致应用崩溃。为了防止 Lint 阻止构建过程,可以像这样禁用abortOnError:
android {
lintOptions {
abortOnError false
}
}
临时禁用 Lint 终止可以更容易地将现有的 Ant 构建过程迁移到 Gradle。使过渡更平滑的另一种方法是直接从 Gradle 执行 Ant 任务。
使用 Gradle 的 Ant
如果您在设置 Ant 构建上投入了大量时间,那么切换到 Gradle 可能听起来有些可怕。在这种情况下,Gradle 不仅能够执行 Ant 任务,还可以扩展它们。这意味着您可以从 Ant 迁移到 Gradle,而不是花费几天时间转换整个构建配置。
Gradle 使用 Groovy 的 AntBuilder 来实现 Ant 集成。AntBuilder 允许您执行任何标准 Ant 任务、您自己的自定义 Ant 任务以及整个 Ant 构建。它还使得在 Gradle 构建配置中定义 Ant 属性成为可能。
从 Gradle 运行 Ant 任务
从 Gradle 运行标准 Ant 任务很简单。您只需在任务名称前加上 ant. 前缀,一切就会自动工作。例如,要创建一个存档,您可以使用此任务:
task archive << {
ant.echo 'Ant is archiving...'
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'zipme')
}
}
该任务在 Gradle 中定义,但使用了两个 Ant 任务,echo 和 zip。
当然,您应该始终考虑 Gradle 的标准 Ant 任务的替代方案。例如,要创建一个与上一个示例中类似的存档,您可以定义一个 Gradle 任务来处理此操作:
task gradleArchive(type:Zip) << {
from 'zipme/'
archiveName 'grarchive.zip'
}
基于 Gradle 的存档任务更加简洁且易于理解。因为它不需要通过 AntBuilder,所以它也比使用 Ant 任务稍微快一些。
导入整个 Ant 脚本
如果您已经创建了一个用于构建您的应用的 Ant 脚本,您可以使用 ant.importBuild 导入整个构建配置。然后,所有 Ant 目标都会自动转换为 Gradle 任务,您可以通过它们原始的名称访问这些任务。
例如,考虑以下 Ant 构建文件:
<project>
<target name="hello">
<echo>Hello, Ant</echo>
</target>
</project>
您可以将此构建文件导入到您的 Gradle 构建中,方法如下:
ant.importBuild 'build.xml'
这将使 hello 任务暴露给您的 Gradle 构建,因此您可以像执行常规 Gradle 任务一样执行它,并将打印出 Hello, Ant:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
因为 Ant 任务被转换为 Gradle 任务,所以您也可以使用 doFirst 和 doLast 块或 << 快捷方式来扩展它。例如,您可以向控制台打印另一行文本:
hello << {
println 'Hello, Ant. It\'s me, Gradle'
}
如果现在执行 hello 任务,它看起来像这样:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
您也可以像通常一样依赖于从 Ant 导入的任务。例如,如果您想创建一个依赖于 hello 任务的新的任务,您可以简单地这样做:
task hi(dependsOn: hello) << {
println 'Hi!'
}
使用 dependsOn 确保在执行 hi 任务时触发 hello 任务:
$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!
如果需要,您甚至可以使 Ant 任务依赖于 Gradle 任务。为了实现这一点,您需要在 build.xml 文件中将 depends 属性添加到任务中,如下所示:
<target name="hi" depends="intro">
<echo>Hi</echo>
</target>
如果您有一个大的 Ant 构建文件,并且想确保没有任务名称重叠,您可以在导入时使用此代码片段重命名所有 Ant 任务:
ant.importBuild('build.xml') { antTargetName ->
'ant-' + antTargetName
}
如果您决定重命名所有 Ant 任务,请记住,如果您有一个依赖于 Gradle 任务的 Ant 任务,那么该 Gradle 任务也需要加上前缀。否则,Gradle 将无法找到它并抛出 UnknownTaskException。
属性
Gradle 和 Ant 不仅可以共享任务,你还可以在 Gradle 中定义可以在你的 Ant 构建文件中使用的属性。考虑以下 Ant 目标,它打印出一个名为version的属性:
<target name="appVersion">
<echo>${version}</echo>
</target>
你可以在 Gradle 构建配置中定义版本属性,就像在任务中一样,在属性名前加上ant.。这是定义 Ant 属性的最短方式:
ant.version = '1.0'
Groovy 在这里隐藏了很多实现细节。如果你完整地编写属性定义,它看起来像这样:
ant.properties['version'] = '1.0'
执行version任务将做你预期的事情,即打印出1.0到控制台:
$ gradlew appVersion
:appVersion
[ant:echo] 1.0
在 Gradle 中深度集成 Ant 使得从基于 Ant 的构建过渡到 Gradle 变得容易得多,你可以按照自己舒适的节奏进行。
高级应用部署
在第四章“创建构建变体”中,我们探讨了使用构建类型和产品风味创建同一应用多个版本的好几种方法。然而,在某些情况下,使用更具体的技巧,如 APK 分割可能更容易。
分割 APK
构建变体可以被视为独立的实体,每个都可以有自己的代码、资源和清单文件。另一方面,APK 分割仅影响应用的打包。编译、缩减、混淆等操作仍然是共享的。这种机制允许你根据密度或应用二进制接口(ABI)来分割 APK。
你可以通过在android配置块内定义一个splits块来配置分割。要配置密度分割,在splits块内创建一个density块。如果你要设置 ABI 分割,使用一个abi块。
如果你启用了密度分割,Gradle 将为每个密度创建一个单独的 APK。如果你不需要某些密度,可以手动排除它们,以加快构建过程。以下示例展示了如何启用密度分割并排除低密度设备:
android {
splits {
density {
enable true
exclude 'ldpi', 'mdpi'
compatibleScreens 'normal', 'large', 'xlarge'
}
}
}
如果你只支持少数几种密度,你可以使用include来创建一个密度的白名单。要使用include,你首先需要使用reset()属性,该属性将包含的密度列表重置为空字符串。
前一个代码片段中的compatibleScreens属性是可选的,并在清单文件中注入一个匹配的节点。示例中的配置适用于支持从正常到超大屏幕的应用,排除了小屏幕设备。
根据 ABI 分割 APK 的方式与此相同,所有属性都与密度分割的属性相同,除了compatibleScreens。ABI 分割与屏幕大小无关,因此没有名为compatibleScreens的属性。
配置密度分割后执行构建的结果是 Gradle 现在创建了一个通用 APK 和几个特定密度的 APK。这意味着你最终会得到一个像这样的 APK 集合:
app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk
尽管使用 APK 分割有一些注意事项。如果您想将多个 APK 推送到 Google Play,您需要确保每个 APK 都有不同的版本码。这意味着每个分割都应该有一个唯一的版本码。幸运的是,到目前为止,您已经能够在 Gradle 中通过查看 applicationVariants 属性来完成这项操作。
以下代码片段直接来自 Gradle Android 插件的文档,展示了如何为每个 APK 生成不同的版本码:
ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
}
}
这个小代码片段检查构建变体中使用了哪种 ABI,然后对版本码应用一个乘数,以确保每个变体都有一个唯一的版本码。
摘要
在阅读本章之后,您将了解如何减小构建输出的体积,以及如何通过配置 Gradle 和 JVM 来加速构建。大型迁移项目再也不应该让您感到害怕了。您还学到了一些使开发和部署更简单的技巧。
有了这些,您已经到达了本书的结尾!现在您已经了解了 Gradle 的可能性,您可以调整和自定义 Android 项目的构建过程,直到您不需要进行任何手动工作,除了执行任务。您可以配置构建变体,管理依赖项,并配置多模块项目。Gradle DSL 对您来说是有意义的,因为您理解 Groovy 语法,并且您对钩入 Android 插件感到舒适。您甚至可以创建任务或插件并共享它们,以帮助他人自动化他们的构建。现在您需要做的就是应用您的新技能!


浙公网安备 33010602011771号