Gradle For Android系列2:自定义Build配置

在上一章节中我们学习了Gradle的用法,以及如何创建Android项目以及如何从Eclipse中将项目转换到Android Studio中。这一章节将介绍构建文件配置的更多细节,以及一些有用的构建任务,并深入Gradle的Android插件。
在本章中,我们将讨论以下主题:

  • 理解Gradle的各种配置文件
  • 初识Build Tasks
  • 自定义构建

理解Gradle的各种配置文件

当使用Android Studio创建新项目时,默认会生成三个Gradle文件。 其中两个文件,settings.gradlebuild.gradle,在项目的根目录。另一个build.gradle文件在Android应用程序模块中。 如下图展示了Gradle文件如何放置在项目中:

1
2
3
4
5
MyApp
|-buid.gradle
|-settings.gradle
|-app
|-buid.grade

这三个文件每个都有其单独的用途,接下来将详细介绍:

settings.gradle文件

对于只包含Android应用模块的项目,settings.gradle文件内容如下所示

1
include ':app'

setting文件在Build初始化阶段执行,定义了哪些模块应改被包含在构建中。 在此示例中,应用程序模块被包含其中。单个模块项目不一定需要setting文件,但是多模块项目必须要包含setting文件;否则,Gradle不知道要将哪些模块包含到构建中。
setting文件的幕后运行机制是:Gradle为每个设置文件创建一个Settings对象,并从该对象调用必要的方法。你不需要知道Settings类的具体细节,但是知道有settings对象存在对深入了解gradle是有帮助的。

Settings类的完整解释超出了本文档的范围。 如果你想知道更多,你可以在Gradle官方文档中找到很多信息。

项目根build.gradle文件

在项目根目录的build.gradle文件中,你可以配置需要应用于项目中所有模块的选项。它默认包含两个代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}

buildscript代码块是配置实际构建的位置。我们在第1章Gradle和Android Studio入门中简要讨论了这一点。可以看到我们在repositories{}中将JCenter配置为依赖库。依赖库意味着依赖的来源,或者换句话说,一个可下载的第三方库列表,我们可以在我们的应用和库中使用。(JCenter是一个著名的Maven依赖库)

dependencies{}用于为构建过程本身配置依赖。这意味着你不应在顶级构建文件中包含你的应用程序或库所需的依赖关系。当前默认定义了唯一的一个依赖是Gradle的Android插件。Android插件是每个Android模块构建所必须的,因为这个插件定义了Android程序的具体构建流程,包含了可以执行的Android相关任务。

allprojects{}可用于定义需要应用于所有模块的属性。你可以在allprojects{}创建任务,那么这些任务将在所有模块中都可用。

因为allprojects是将其中的配置应用到所有模块中,如果不是公共的通用配置,而是单独于某一个模块中的配置,最好是放在模块中的build文件中。

模块的build.gradle文件

模块级的build.gradle文件包含仅适用于Android应用程序或库模块的选项。它可以直接覆盖顶级build.gradle文件中的任何配置选项。模块build.gradle文件内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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'
}

接下来详细介绍各个配置

Plugin

第一行代码应用Android应用程序插件,在前面我们介绍过,Android插件被项目根构建文件配置为build过程的依赖。Android插件提供构建,测试和打包Android应用程序和库所需的所有任务,现由Google Android Tools Team编写和维护。

Android

构建文件的最大部分是android块。 此块包含整个Android特定的构建配置。
android配置块中至少需要配置属性是compileSdkVersion和buildToolsVersion:

  • compileSdkVersion:编译Android程序的sdk版本
  • buildToolsVersion:编译过程中使用的android构建工具的版本

构建工具包含各种命令行程序,例如aapt,zipalign,dx和renderscript; 它们用于生成构成应用程序的各种中间件。你可以通过SDK Manager下载这些构建工具(默认sdk会有一个版本的构建工具)。

defaultConfig配置了应用程序的核心属性。此块中的属性将覆盖AndroidManifest.xml文件中的相应条目:

1
2
3
4
5
6
7
defaultConfig {
applicationId "com.gradleforandroid.gettingstarted"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
}

此块中的第一个属性是applicationId。这将覆盖manifest文件中的包名称,但applicationId和应用包名之间有一些差异。在Gradle用作默认Android构建系统之前,AndroidManifest.xml中的包名称有两个目的:一是用作应用程序的唯一标识符,再就是用作R资源类中包的名称。假设有如下场景:你要发布一款app,有一个免费版本和付费版本。并且这两个版本需要具有单独的标识符,因为它们在Google Play商店中需要显示为不同的应用,并且可以同时安装。然而,在未使用Gradle构建之前,源代码和生成的R类必须始终保持相同的包名称,因此,在创建不同的版本时,所有的源文件将需要更改。在使用Gradle之后applicationId和包名区分开了,这样你可以随意更改项目的应用Id而不必更改源码的包名。manifest文件中定义的程序包名继续在源代码和R类中使用,而设备和Google Play使用applicationId作为唯一标识符。

defaultConfig中接下来的两个属性是minSdkVersiontargetSdkVersion。这两个应该看起来很熟悉,因为它们在manifest文件中被定义为元素的一部分。 minSdkVersion设置用于配置运行应用程序所需的最低API级别。 targetSdkVersion设置通知系统该应用程序在特定版本的Android上测试,并且操作系统不需要启用任何向前兼容性行为。这与我们之前看到的compileSdkVersion无关。

versionCode和versionName也具有与manifest文件中相同的功能,并为您的应用定义版本号和用户友好的版本名称。

构建文件中的所有值都将覆盖清单文件中的值。因此,如果在build.gradle中定义它们,则不需要在清单文件中定义它们。如果构建文件不包含值,则使用manifest文件中定义的值。

buildTypes块是你定义如何构建和打包应用程序的不同构建类型(Debug或者Release)的地方。我们将在第4章:创建构建变体中详细讨论构建Build Variant

Dependencies

依赖块是标准Gradle配置的一部分(这也是为什么它放在了android块之外),并定义了应用模块或库模块的所有依赖关系。默认情况下,新的Android应用程序依赖于libs目录中的所有JAR文件和相应的support包下的库。 我们将在第3章 管理依赖关系中讨论依赖关系。

初识Gradle Task

要知道项目中有哪些任务可用,可以运行gradlew tasks,这将打印出所有可用任务的列表。 在新创建的Android项目中,这包括Android Tasks,Build Tasks,Build Setup Task,Help Task,Install Task,Verification Task和和其他任务。 如果你不仅要看到任务,还要看到它们的依赖,你可以运行gradlew tasks –all。 可以执行dry运行任务,打印特定任务时执行的所有步骤。这个dry运行实际上不会执行任何这些步骤,因此它是一个安全的方式来查看当运行某个任务时是否按照你所期待的那样运行。你可以通过添加参数-m–dry-run来进行dry运行。

基础任务(Base tasks)

Gradle的Android插件使用Java基本插件,同时也使用Android基本插件。这些基本插件添加了标准生命周期任务和一些常见的约定属性。Android基本插件定义了assemble任务和clean任务,Java基本插件定义了check任务和clean任务。这些任务并不在基本插件中实现,并且不执行任何操作; 基本插件只是用于定义插件的约定,用于添实际任务加执行工作的规范。

这些任务的约定是:

  • assemble 将项目集成构建并输出
  • clean 将项目输入完全清除
  • check 运行所有检查,通常包括单元测试和功能测试
  • build 运行assemble任务和check任务

Java基础插件也添加了soruce set的概念。 Android插件基于这些约定,有经验的Gradle用户根据任务名称就能一眼分辨出该任务是什么类型的。除了这些基本任务之外,Android插件还添加了许多Android特定的任务。

Android任务

Android插件扩展了基本任务并实现了它们的行为。如下为各个任务在Android环境中对应的作用:

  • assemble 为每一种构建类型创建APK
  • clean 移除构建产生的各类文件,如:apk
  • check lint检查,如果lint检查失败就会中断构建
  • build 运行assemble和check

assemble任务默认情况下依赖于assembleDebug和assembleRelease,如果添加更多build type,则会依赖更多需要更多build type的assemble任务。这意味着运行assemble将触发一个构建的每一个你有的build type。

除了扩展这些任务,Android插件还添加了一些新的。这些是最重要的新任务:

  • connectedCheck 在模拟器或真机上运行UI测试
  • deviceCheck 其他插件在远程设备上运行测试的占位任务
  • installDebug/installRelease 给连接上的模拟器或真机上安装指定版本Apk
  • 所有install任务都有对应的uninstall任务

build任务取决于check任务,但不包括connectedCheck或deviceCheck。这是为了确保常规检查不需要连接真机设备或运行模拟器。运行check任务将生成一个包含所有警告和错误列表的Lint报告,以及详细说明和指向相关文档的链接。此报告可以在app/build/outputs中找到,名为lint-results.html。它看起来像这样:

当你assemble一个版本,Lint会检查一些可能会导致应用程序崩溃的严重错误问题。一旦发现问题,它将中止构建并将错误打印到命令行界面。Lint还将在app/build/outputs中生成一个名为lint-results-release-fatal.html的报告。如果构建发现有多个问题,通过HTML报告会比在命令行界面中的展示效果更好,每个问题提供的链接也非常有用,因为上面会带有详细解释问题。

Android Studio集成

你不用总是从命令行界面运行Gradle任务。Android Studio有一个工具窗口,其中包含所有可用任务的列表。 这个工具窗口称为Gradle,如下所示:

从此工具窗口中,可以通过双击其名称来运行任务。你可以在Gradle Console工具窗口中跟踪任何正在运行的任务的进度。如果找不到这些工具窗口,可以在Tool Window下的View菜单中打开它们。 这就是Gradle Console工具窗口的样子:

你还可以从Android Studio中的命令行界面运行任务,以便你可以根据需要在IDE中执行所有与应用相关的工作。要运行该命令,需要打开终端工具窗口。这是一个完整的终端,所以可以运行任何命令。 你可能需要首先导航到项目的根目录,以便使用gradlew命令。

更改Android Studio终端
可以将Android Studio中的终端配置为使用不同的shell。 例如,在Windows上,终端默认为命令提示符。 如果你喜欢使用Git Bash(或任何其他shell),打开Android Studio设置(在文件和设置下),并寻找终端。 在那里你可以改变shell路径。 对于Windows上的Git Bash,它看起来像这样:C\Program Files(x86)\Git\bin\sh.exe –login -i。

自定义构建

有很多方法来自定义构建过程,当你在Android Studio中编辑build.gradle文件时,建议始终将项目与Gradle文件同步,无论你要自定义什么。这在添加依赖项或BuildConfig字段时变得尤为重要,我们将在稍后讨论。

一旦您编辑settings,gradle或build.gradle文件时,Android Studio就会在编辑器中显示一条消息,提示需要sync项目。你可以通过导航到Tools | Android | Sync Project with Gradle或工具栏中的相应sync按钮来手动同步。

Sync操作原理:Android Studio Sync实际上会基于构建文件中的配置运行generateDebugSources任务来生成所有必需的类。

Manifest属性操作

我们已经看到,可以直接从构建文件而不是在清单文件中配置applicationId,minSdkVersion,targetSdkVersion,versionCode和versionName。 这里还有一些其他属性可以操作:

  • testApplicationId 测试Apk的applicationId
  • testInstrumentationRunner 应用运城JUnit测试的测试Runner名称
  • signingConfig 打包签名配置
  • proguardFile/proguardFiles 混淆配置

BuildConfig和Resources

从SDK tool revision 17开始,构建工具会生成一个名为BuildConfig的类,其中包含根据Build Type设置的DEBUG常数(BuildType是Debug时,DEBUG为true,反之为false)。如果你希望只有在调试状态时运行指定的代码(例如日志记录),这将非常有用。它可以通过Gradle扩展该文件,以便可以在Debug和Release中包含不同值的常量。

这些常数可用于切换功能或设置服务器URL,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
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"
}
}
}

API_URL为了生成为实际字符串值,在引用是必须在双引号上加上转义符。添加buildConfigField行后,可以在实际的Java代码中使用BuildConfig.API_URL和BuildConfig.LOG_HTTP_CALLS常量。

Android Tools Team还增加了以类似方式配置资源:

1
2
3
4
5
6
7
8
9
10
android {
buildTypes {
debug {
resValue "string", "app_name", "Example DEBUG"
}
release {
resValue "string", "app_name", "Example"
}
}
}

在这里不需要转义的双引号,因为资源值在默认情况下总是用value =””形式存在的。

Project设置

如果在一个项目中有多个Android模块,则可以将settings应用于所有模块,而无需手动更改每个模块的构建文件。我们已经看到了如何在生成的顶级构建文件中使用allprojects块来定义依赖库,我们也可以使用相同的策略来应用Android特定的设置:

1
2
3
4
5
6
7
allprojects {
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
}

上面的代码中,如果项目所有的模块都是Android应用程序模块,代码才会生效,因为你需要应用Android插件来访问Android应用程序特定的设置。全局引用更好的实践模式是在项目根构建文件中定义值,然后在各个模块中引用它们。 在Gradle中可以在Project对象上添加额外的特别属性。这意味着任何build.gradle文件都可以在ext块定义额外的属性。

你可以将具有自定义属性的ext块添加到项目根构建文件中:

1
2
3
4
ext {
compileSdkVersion = 22
buildToolsVersion = "22.0.1"
}

这使得可以使用rootProject在模块级构建文件中使用属性:

1
2
3
4
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}

项目属性

前面示例中的ext块是定义额外属性的一种方法。 你也可以使用属性来自定义构建过程,我们将在第7章“创建任务和插件”中编写自定义任务时使用它们。有几种方法来定义属性,但我们现在先了解三个最常用的方法:

  • ext代码块
  • gradle.properties文件
  • 命令行的 -P参数

下面是一个build.gradle文件的示例,它包含了添加额外属性的三种方式:

1
2
3
4
5
6
7
8
9
10
11
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文件内容(在同一文件夹中):

1
propertiesFile = Hello from gradle.properties

在示例中,我们创建一个新任务。 我们将在第7章“创建任务和插件”中讨论任务和解释相关语法。

如果使用命令行运行printProperties任务,输出将如下所示:

1
2
3
4
5
$ gradlew printProperties -P cmd='Hello from the command line'
:printProperties
Hello from build.gradle
Hello from gradle.properties
Hello from the command line

自定义属性使得更改构建的配置与更改单个属性一样简单,甚至只需添加命令行参数即可(命令行-P方式)。

可以在项目根目录构建文件和模块构建文件中都定义属性。 果模块定义了顶级文件中已存在的属性,那么它将覆盖根目录构建文件中定义的。

默认任务

如果你运行Gradle而不指定任务,它会默认运行help任务,打印一些关于如何使用Gradle的信息。 这是因为帮助任务设置为默认任务。你可以用一个你的常用任务或多个任务覆盖默认任务。每次执行Gradle,不用明确指定任务就可执行(相当于是一个热键)。

要指定默认任务,请将此行添加到项目根build.gradle文件中:

1
defaultTasks 'clean', 'assembleDebug'

现在,当你运行没有任何参数的Gradlew命令时,它将运行clean和assembleDebug任务。通过运行tasks任务并过滤输出,很容易看到哪些任务被设置为默认任务。

1
2
$ gradlew tasks | grep "Default tasks"
Default tasks: clean, assembleDebug

总结

在本章中,我们详细介绍了Android Studio创建新项目时自动生成的不同Gradle文件。 你现在可以自己创建build.gradle文件,并添加所有必需的配置属性。

我们开始了基本的构建任务,并了解Android插件如何基于基本插件来构建项目,以及如何扩展新的Android任务。 我们还看到了如何从命令行界面和从Android Studio内部运行构建任务。

在过去几年中,Android开发人员生态系统已经发展得非常迅速,许多有趣的第三方库已经可供大家使用。 在下一章中,我们将介绍几种向项目添加依赖项的方法。

写得好,就打赏一下吧!