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