Android 应用通常使用 Gradle 构建系统进行构建。在深入了解如何配置构建细节之前,我们将先探讨构建背后的概念,以便您能够从整体上理解该系统。
什么是构建?
构建系统将您的源代码转换为可执行的应用。构建通常涉及多种工具,用于分析、编译、链接和打包您的应用或库。Gradle 使用基于任务的方法来组织和运行这些命令。
任务 (Tasks) 封装了将输入转换为输出的命令。插件 (Plugins) 用于定义任务及其配置。将插件应用到构建中会注册其任务,并利用它们的输入和输出将它们连接在一起。例如,将 Android Gradle 插件 (AGP) 应用到您的构建文件,就会注册构建 APK 或 Android 库所需的所有任务。java-library 插件允许您根据 Java 源代码构建 jar 包。Kotlin 等其他语言也有类似的插件,不过有些插件旨在扩展现有插件。例如,protobuf 插件旨在为 AGP 或 java-library 等现有插件添加 protobuf 支持。
Gradle 推崇“约定优于配置”,因此插件会预先提供良好的默认值,但您也可以通过声明式领域特定语言 (DSL) 进一步配置构建。该 DSL 的设计旨在让您可以指定“构建什么”,而不是“如何构建”。插件中的逻辑负责管理“如何构建”。这些配置是在您项目(及子项目)中的多个构建文件里指定的。
任务输入可以是文件、目录以及编码为 Java 类型(整数、字符串或自定义类)的其他信息。输出只能是目录或文件,因为它们必须写入磁盘。将一个任务的输出连接到另一个任务的输入,可以将这些任务关联起来,从而确保一个任务在另一个任务之前运行。
虽然 Gradle 支持在构建文件中编写任意代码和任务声明,但这会增加工具理解构建的难度,也不利于维护。例如,您可以为插件内的代码编写测试,但无法为构建文件内的代码编写测试。因此,您应该将构建逻辑和任务声明限制在插件(由您或他人定义)中,并在构建文件中声明您想要如何使用该逻辑。
当 Gradle 构建运行时会发生什么?
Gradle 构建分为三个阶段运行。每个阶段都会执行您在构建文件中定义的不同代码部分。
- 初始化 (Initialization) 确定哪些项目和子项目包含在构建中,并设置包含构建文件和已应用插件的类路径。此阶段侧重于 settings 文件,您可以在其中声明要构建的项目以及获取插件和库的位置。
- 配置 (Configuration) 为每个项目注册任务,并执行构建文件以应用用户的构建规范。需要注意的是,您的配置代码无法访问执行期间产生的数据或文件。
- 执行 (Execution) 执行应用的实际“构建”。配置的输出是一个任务有向无环图 (DAG),它代表了用户请求的所有必需构建步骤(在命令行上提供或作为构建文件中的默认任务)。该图表示任务之间的关系,这种关系要么在任务声明中显式定义,要么基于其输入和输出。如果一个任务的输入是另一个任务的输出,那么它必须在另一个任务之后运行。此阶段按图中定义的顺序运行过期的任务;如果自上次执行以来任务的输入没有发生变化,Gradle 将跳过该任务。
有关更多信息,请参阅 Gradle 构建生命周期。
配置 DSL
Gradle 使用领域特定语言 (DSL) 来配置构建。这种声明式方法侧重于指定数据,而不是编写分步(命令式)指令。您可以使用 Kotlin 或 Groovy 编写构建文件,但我们强烈建议使用 Kotlin。
DSL 旨在让领域专家和程序员都能更轻松地为项目做出贡献,它定义了一种以更自然的方式表示数据的小型语言。Gradle 插件可以扩展 DSL,以配置其任务所需的数据。
例如,配置构建的 Android 部分可能如下所示:
Kotlin
android { namespace = "com.example.app" compileSdk { version = release(36) { minorApiLevel = 1 } } // ... defaultConfig { applicationId = "com.example.app" minSdk { version = release(23) } targetSdk { version = release(36) } // ... } }
Groovy
android { namespace = 'com.example.app' compileSdk { version = release(36) { minorApiLevel = 1 } } // ... defaultConfig { applicationId = 'com.example.app' minSdk { version = release(23) } targetSdk { version = release(36) } // ... } }
在后台,DSL 代码类似于:
fun Project.android(configure: ApplicationExtension.() -> Unit) {
...
}
interface ApplicationExtension {
var namespace: String?
fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
...
}
val defaultConfig: DefaultConfig
fun defaultConfig(configure: DefaultConfig.() -> Unit) {
...
}
}
DSL 中的每个块都由一个函数表示,该函数接受一个 lambda 表达式进行配置,并提供一个同名属性来访问它。这使得构建文件中的代码读起来更像是一种数据规范。
外部依赖项
Maven 构建系统引入了依赖项规范、存储和管理系统。库存储在仓库(服务器或目录)中,并附带包含版本和其他库依赖关系的元数据。您指定要搜索的仓库以及要使用的依赖项版本,构建系统会在构建期间下载它们。
Maven 工件由组名(公司、开发者等)、工件名(库的名称)和工件版本标识。这通常表示为 group:artifact:version。
这种方法显著改进了构建管理。您经常会听到这些仓库被称为“Maven 仓库”,但这本质上与工件打包和发布的方式有关。这些仓库和元数据已被多个构建系统复用,包括 Gradle(Gradle 也可以发布到这些仓库)。公共仓库允许所有人共享,而公司仓库则用于保存内部依赖项。
您还可以将项目模块化为子项目(在 Android Studio 中也称为“模块”),这些子项目也可以用作依赖项。每个子项目都会产生输出(例如 jar 包),供子项目或顶级项目使用。这可以通过隔离需要重新构建的部分来缩短构建时间,并更好地分离应用中的职责。
我们将在添加构建依赖项中详细介绍如何指定依赖项。
构建变体
创建 Android 应用时,通常需要构建多个变体 (variants)。变体包含不同的代码或使用不同的选项进行构建,并由构建类型和产品风味 (product flavors) 组成。
构建类型会改变已声明的构建选项。默认情况下,AGP 会设置“release”和“debug”构建类型,但您可以对其进行调整并添加更多类型(例如用于阶段测试或内部测试)。
调试构建不会对您的应用进行缩减或混淆,从而加快了构建速度并保留了所有符号。它还将应用标记为“可调试”,使用通用的调试密钥进行签名,并允许访问设备上已安装的应用文件。这使得在运行应用时可以浏览文件和数据库中保存的数据。
发布构建会对应用进行优化,使用您的发布密钥进行签名,并保护已安装的应用文件。
使用产品风味,您可以更改应用中包含的源代码和依赖项变体。例如,您可能希望为应用创建“demo”和“full”风味,或者“free”和“paid”风味。您可以将公共源代码写入“main”源码集 (source set) 目录中,并在以风味命名的源码集中覆盖或添加源代码。
AGP 会为构建类型和产品风味的每种组合创建变体。如果您没有定义风味,变体将以构建类型命名。如果您两者都定义了,变体将命名为 <flavor><Buildtype>。例如,构建类型为 release 和 debug,且风味为 demo 和 full 时,AGP 将创建以下变体:
demoReleasedemoDebugfullReleasefullDebug
后续步骤
现在您已经了解了构建概念,请查看项目中关于 Android 构建结构的内容。