Gradle 构建概述

Android 应用通常使用 Gradle 构建系统构建。在我们深入探讨如何配置构建的细节之前,我们将探讨构建背后的概念,以便您可以全面了解该系统。

什么是构建?

构建系统将您的源代码转换为可执行应用。构建通常涉及多个工具,用于分析、编译、链接和打包您的应用或库。Gradle 使用基于任务的方法来组织和运行这些命令。

任务 封装将输入转换为输出的命令。插件 定义任务及其配置。将插件应用于您的构建会注册其任务,并使用其输入和输出来将它们连接在一起。例如,将 Android Gradle 插件 (AGP) 应用于您的构建文件将注册构建 APK 或 Android 库所需的所有任务。 java-library 插件允许您从 Java 源代码构建 jar。Kotlin 和其他语言也存在类似的插件,但其他插件旨在扩展插件。例如,protobuf 插件旨在为现有插件(如 AGP 或 java-library)添加 protobuf 支持。

Gradle 更偏向于约定优于配置,因此插件将开箱即用地提供良好的默认值,但您可以通过声明性 领域特定语言 (DSL) 来进一步配置构建。DSL 的设计使您可以指定要构建的内容,而不是如何构建。插件中的逻辑管理“如何”部分。该配置在您的 项目(和子项目)中的多个 构建文件 中指定。

任务输入可以是文件和目录,也可以是编码为 Java 类型(整数、字符串或自定义类)的其他信息。输出只能是目录或文件,因为它们必须写入磁盘。将任务输出连接到另一个任务输入,将任务链接在一起,以便一个任务必须在另一个任务之前运行。

虽然 Gradle 支持在构建文件中编写任意代码和任务声明,但这会使工具更难以理解您的构建,也更难以维护。例如,您可以为插件内的代码编写测试,但不能为构建文件编写测试。相反,您应该将构建逻辑和任务声明限制在插件中(由您或其他人定义),并在构建文件中声明您希望如何使用该逻辑。

Gradle 构建运行时会发生什么?

Gradle 构建运行分为三个阶段。每个阶段都会执行您在构建文件中定义的不同代码部分。

  • **初始化**阶段确定构建中包含哪些项目和子项目,并设置包含构建文件和应用插件的类路径。此阶段侧重于设置文件,您可以在其中声明要构建的项目以及从中获取插件和库的位置。
  • **配置**阶段为每个项目注册任务,并执行构建文件以应用用户的构建规范。重要的是要理解,您的配置代码将无法访问执行期间生成的数据或文件。
  • **执行**阶段执行应用程序的实际“构建”。配置的输出是一个有向无环图 (DAG) 任务,表示用户请求的所有必需构建步骤(命令行中提供的任务或构建文件中的默认任务)。此图表示任务之间的关系,这些关系可以是任务声明中的显式关系,也可以是基于其输入和输出的关系。如果一个任务的输入是另一个任务的输出,则它必须在另一个任务之后运行。此阶段按图中定义的顺序运行过时的任务;如果任务的输入自上次执行以来没有更改,Gradle 将跳过它。

更多信息,请参见 Gradle 的构建生命周期

配置 DSL

Gradle 使用领域特定语言 (DSL) 来配置构建。这种声明式方法侧重于指定数据,而不是编写逐步(命令式)指令。您可以使用 Kotlin 或 Groovy 编写构建文件,但我们强烈建议使用 Kotlin。

DSL 试图让每个人,包括领域专家和程序员,都能更轻松地参与项目贡献,定义一种小型语言,以更自然的方式表示数据。Gradle 插件可以扩展 DSL 来配置其任务所需的数据。

例如,配置构建的 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 来配置它,以及一个同名的属性来访问它。这使得构建文件中的代码感觉更像数据规范。

外部依赖项

Maven 构建系统引入了一个依赖项规范、存储和管理系统。库存储在存储库(服务器或目录)中,元数据包括其版本以及对其他库的依赖关系。您指定要搜索哪些存储库、要使用的依赖项的版本,构建系统将在构建期间下载它们。

Maven 工件由组名(公司、开发人员等)、工件名(库的名称)和该工件的版本标识。这通常表示为group:artifact:version

这种方法显著改进了构建管理。您经常会听到这些存储库被称为“Maven 存储库”,但这完全取决于工件的打包和发布方式。这些存储库和元数据已在多个构建系统中重复使用,包括 Gradle(Gradle 可以发布到这些存储库)。公共存储库允许所有人共享使用,而公司存储库则将内部依赖项保留在内部。

您还可以将项目模块化成子项目(在 Android Studio 中也称为“模块”)这些子项目也可以用作依赖项。每个子项目都会生成输出(例如 jar 文件),这些输出可以被子项目或顶级项目使用。这可以通过隔离需要重建的部分来缩短构建时间,并更好地分离应用程序中的职责。

我们将在添加构建依赖项中更详细地介绍如何指定依赖项。

构建变体

创建 Android 应用程序时,通常需要构建多个变体。变体包含不同的代码或使用不同的选项构建,并且由构建类型和产品风格组成。

构建类型会改变声明的构建选项。默认情况下,AGP 会设置“发布”和“调试”构建类型,但您可以调整它们并添加更多类型(例如用于暂存或内部测试)。

调试构建不会缩小或混淆您的应用程序,从而加快其构建速度并按原样保留所有符号。它还会将应用程序标记为“可调试”,并使用通用的调试密钥对其进行签名,并启用对设备上已安装应用程序文件的访问。这使得在运行应用程序时可以浏览文件中和数据库中保存的数据。

发布构建会优化应用程序,并使用您的发布密钥对其进行签名,并保护已安装的应用程序文件。

使用产品风格,您可以更改应用程序的包含源和依赖项变体。例如,您可能希望为您的应用程序创建“演示”和“完整”风格,或者可能是“免费”和“付费”风格。您可以在“main”源集目录中编写公共源代码,并在以风格命名的源集中覆盖或添加源代码。

AGP 为构建类型和产品风格的每种组合创建变体。如果您没有定义风格,则变体将以构建类型命名。如果您同时定义两者,则变体的名称为<flavor><Buildtype>。例如,对于构建类型releasedebug,以及风格demofull,AGP 将创建以下变体:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

后续步骤

现在您已经了解了构建概念,请查看项目中的Android 构建结构