配置构建变体

本页以配置 Build 概览为基础,向您介绍如何配置构建变体以根据单个项目构建出不同 Build 的应用,以及如何正确管理依赖项和签名配置。

每种构建变体都代表您可以构建的一个不同的应用版本。例如,您可能希望为应用构建两个版本,一个是内容有限的免费版本,另一个是包含更多内容的付费版本。您还可以根据 API 级别或其他设备变量,为应用构建以不同设备为目标的不同版本。但是,如果您想根据设备 ABI 或屏幕密度构建不同的版本,则应构建多个 APK

构建变体是 Gradle 使用一组特定规则将在版本类型和产品类型中配置的设置、代码和资源结合在一起的结果。虽然您无法直接配置构建变体,但可以配置组成它们的版本类型和产品类型。

例如,“demo”产品类型可以指定不同的功能和设备要求(如自定义源代码、资源和最低 API 级别),而“debug”版本类型则会应用不同的构建和打包设置(如调试选项和签名密钥)。生成的构建变体是应用的“demoDebug”版本,并且包括“demo”产品类型、“debug”版本类型和 main/ 源代码文件集中包含的配置和资源组合。

配置 Build 类型

您可以在 android 块内的模块级 build.gradle 文件中创建和配置 Build 类型。当您创建新模块时,Android Studio 会自动为您创建“debug”Build 类型和“release”Build 类型。虽然“debug”Build 类型没有显示在构建配置文件中,但 Android Studio 会使用 debuggable true 配置它。这样,您就可以在安全的 Android 设备上调试应用,并使用常规调试密钥库配置 APK 签名。

如果要添加或更改某些设置,可以将“debug”Build 类型添加到您的配置中。以下示例为调试 Build 类型指定了 applicationIdSuffix,并配置了一个使用“debug”Build 类型的设置进行初始化的“staging”Build 类型。

    android {
        defaultConfig {
            manifestPlaceholders = [hostName:"www.example.com"]
            ...
        }
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }

            debug {
                applicationIdSuffix ".debug"
                debuggable true
            }

            /**
             * The `initWith` property allows you to copy configurations from other build types,
             * then configure only the settings you want to change. This one copies the debug build
             * type, and then changes the manifest placeholder and application ID.
             */
            staging {
                initWith debug
                manifestPlaceholders = [hostName:"internal.example.com"]
                applicationIdSuffix ".debugStaging"
            }
        }
    }
    

注意:当您更改构建配置文件时,Android Studio 会要求您将项目与新配置同步。要同步项目,您可以点击通知栏(在您做出更改后,系统会立即显示此栏)中的 Sync Now,或点击工具栏中的 Sync Project 图标 。如果 Android Studio 发现您的配置有任何错误,则会显示 Messages 窗口,以说明该问题。

要详细了解您可以通过 Build 类型配置的所有属性,请参阅Build 类型 DSL 参考文档

配置产品类型

创建产品类型与创建 Build 类型类似:将其添加到构建配置中的 productFlavors 代码块并添加所需的设置。产品类型支持与 defaultConfig 相同的属性,这是因为 defaultConfig 实际上属于 ProductFlavor 类。这意味着,您可以在 defaultConfig 代码块中提供所有类型的基本配置,每种类型均可更改任何这些默认值,例如 applicationId。要详细了解应用 ID,请参阅设置应用 ID

注意:您仍然需要在 main/ 清单文件中使用 package 属性指定软件包名称。此外,您还必须在源代码中使用该软件包名称引用 R 类,或解析任何相关 Activity 或 Service 注册。这样,您可以使用 applicationId 为每个产品类型分配一个唯一的 ID,以用于打包和分发,而不必更改您的源代码。

所有类型都必须属于一个指定的类型维度,即一个产品类型组。您必须将所有类型分配给某个类型维度;否则,您将收到如下所示的构建错误。如果给定的模块仅指定一个类型维度,则 Android Gradle 插件会自动将该模块的所有类型分配给该维度。

      Error:All flavors must now belong to a named flavor dimension.
      The flavor 'flavor_name' is not assigned to a flavor dimension.
    

以下代码示例创建了一个名为“version”的类型维度,并添加了“demo”和“full”产品类型。这些类型可自行提供其 applicationIdSuffixversionNameSuffix

    android {
        ...
        defaultConfig {...}
        buildTypes {
            debug{...}
            release{...}
        }
        // Specifies one flavor dimension.
        flavorDimensions "version"
        productFlavors {
            demo {
                // Assigns this product flavor to the "version" flavor dimension.
                // If you are using only one dimension, this property is optional,
                // and the plugin automatically assigns all the module's flavors to
                // that dimension.
                dimension "version"
                applicationIdSuffix ".demo"
                versionNameSuffix "-demo"
            }
            full {
                dimension "version"
                applicationIdSuffix ".full"
                versionNameSuffix "-full"
            }
        }
    }
    

注意:要使用 Google Play 中的多 APK 支持分发您的应用,请将同一 applicationId 值分配给所有变体,并为每个变体分配不同的 versionCode。要在 Google Play 中以独立应用的形式分发应用的不同变体,您需要为每个变体分配一个不同的 applicationId

创建并配置产品类型后,点击通知栏中的 Sync Now。同步完成后,Gradle 会根据您的 Build 类型和产品类型自动创建构建变体,并根据 <product-flavor><Build-Type> 对其进行命名。例如,如果您创建了“demo”和“full”产品类型,并保留默认的“debug”和“release”Build 类型,则 Gradle 会创建以下构建变体:

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

您可以将构建变体更改为您要构建并运行的任意变体,只需依次转到 Build > Select Build Variant,然后从下拉菜单中选择一种变体即可。不过,要开始使用自己的功能和资源自定义每个构建变体,您需要知道如何创建和管理源代码文件集

将多个产品类型与类型维度结合起来

在某些情况下,您可能需要合并多个产品类型的配置。例如,您可能需要为基于 API 级别的“full”和“demo”产品类型创建不同的配置。为此,您可以使用 Android Plugin for Gradle 创建多组产品特性作为类型维度。在构建应用时,Gradle 会结合使用您定义的每个类型维度的产品类型配置以及 Build 类型配置,以创建最终的构建变体。Gradle 不会将属于同一类型维度的产品类型组合在一起。

提示:要根据 ABI 和屏幕密度创建不同版本的应用,您应该构建多个 APK,而不要使用产品类型。

以下代码示例使用 flavorDimensions 属性来创建“mode”类型维度和“api”类型维度,前者用于对“full”和“demo”产品类型进行分组,后者用于根据 API 级别对产品类型配置进行分组。

    android {
      ...
      buildTypes {
        debug {...}
        release {...}
      }

      // Specifies the flavor dimensions you want to use. The order in which you
      // list each dimension determines its priority, from highest to lowest,
      // when Gradle merges variant sources and configurations. You must assign
      // each product flavor you configure to one of the flavor dimensions.
      flavorDimensions "api", "mode"

      productFlavors {
        demo {
          // Assigns this product flavor to the "mode" flavor dimension.
          dimension "mode"
          ...
        }

        full {
          dimension "mode"
          ...
        }

        // Configurations in the "api" product flavors override those in "mode"
        // flavors and the defaultConfig block. Gradle determines the priority
        // between flavor dimensions based on the order in which they appear next
        // to the flavorDimensions property above--the first dimension has a higher
        // priority than the second, and so on.
        minApi24 {
          dimension "api"
          minSdkVersion 24
          // To ensure the target device receives the version of the app with
          // the highest compatible API level, assign version codes in increasing
          // value with API level. To learn more about assigning version codes to
          // support app updates and uploading to Google Play, read Multiple APK Support
          versionCode 30000 + android.defaultConfig.versionCode
          versionNameSuffix "-minApi24"
          ...
        }

        minApi23 {
          dimension "api"
          minSdkVersion 23
          versionCode 20000  + android.defaultConfig.versionCode
          versionNameSuffix "-minApi23"
          ...
        }

        minApi21 {
          dimension "api"
          minSdkVersion 21
          versionCode 10000  + android.defaultConfig.versionCode
          versionNameSuffix "-minApi21"
          ...
        }
      }
    }
    ...
    

Gradle 创建的构建变体数量等于每个类型维度中的类型数与您配置的 Build 类型数量的乘积。当 Gradle 为每个构建变体或相应的 APK 命名时,属于较高优先级类型维度的产品类型会先显示,然后是属于较低优先级维度的产品类型,然后是 Build 类型。以上面的构建配置为例,Gradle 使用以下命名方案创建了总共 12 个构建变体:

构建变体:[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
对应的 APK:app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
例如,
构建变体:minApi24DemoDebug
对应的 APK:app-minApi24-demo-debug.apk

除了可以为各个产品类型和构建变体创建源代码文件集目录外,您还可以为每个产品类型组合创建源代码文件集目录。例如,您可以创建 Java 源文件并将其添加到 src/demoMinApi24/java/ 目录中,这样 Gradle 就只会在构建同时对应这两种产品类型的变体时才使用这些源文件。您为产品类型组合创建的源代码文件集的优先级高于属于各个产品类型的源代码文件集。要详细了解源代码文件集以及 Gradle 如何合并资源,请参阅关于如何创建源代码文件集的部分。

过滤变体

Gradle 会为您配置的产品类型和 Build 类型的每种可能组合创建构建变体。但是,某些构建变体可能并不是您需要的,或者对于您的项目来说没有意义。您可以通过在模块级 build.gradle 文件中创建变体过滤器来移除某些构建变体配置。

以上一部分中的构建配置为例,假设您打算让“demo”版应用仅支持 API 级别 23 和更高版本。您可以使用 variantFilter 代码块过滤掉所有将“minApi21”和“demo”产品类型结合在一起的构建变体配置:

    android {
      ...
      buildTypes {...}

      flavorDimensions "api", "mode"
      productFlavors {
        demo {...}
        full {...}
        minApi24 {...}
        minApi23 {...}
        minApi21 {...}
      }

      variantFilter { variant ->
          def names = variant.flavors*.name
          // To check for a certain build type, use variant.buildType.name == "<buildType>"
          if (names.contains("minApi21") && names.contains("demo")) {
              // Gradle ignores any variants that satisfy the conditions above.
              setIgnore(true)
          }
      }
    }
    ...
    

将变体过滤器添加到构建配置,并点击通知栏中的 Sync Now 后,Gradle 会忽略符合您指定的条件的所有构建变体,并且当您从菜单栏中依次点击 Build > Select Build Variant(或点击工具窗口栏中的 Build Variants 图标 )时,它们不会再显示在下拉菜单中。

创建源代码文件集

默认情况下,Android Studio 会为您希望在所有构建变体之间共享的所有内容创建 main/ 源代码文件集和目录。但是,您可以创建新的源代码文件集来精确控制 Gradle 为特定 Build 类型、产品类型(使用类型维度时的产品类型组合)和编译变体构建和打包的文件。例如,您可以在 main/ 源代码文件集中定义基本功能,使用产品类型源代码文件集为不同客户端更改应用的品牌,或者仅为使用调试 Build 类型的构建变体添加特殊权限和日志记录功能。

Gradle 要求您以某种类似于 main/ 源代码文件集的方式组织源代码文件集文件和目录。例如,Gradle 要求将“debug”Build 类型特有的 Java 类文件放在 src/debug/java/ 目录中。

Android Plugin for Gradle 提供了一个有用的 Gradle 任务,向您展示了如何整理每个 Build 类型、产品类型和构建变体的文件。例如,以下任务输出的示例描述了 Gradle 希望“debug”Build 类型的部分文件所在的位置:

    ------------------------------------------------------------
    Project :app
    ------------------------------------------------------------

    ...

    debug
    ----
    Compile configuration: compile
    build.gradle name: android.sourceSets.debug
    Java sources: [app/src/debug/java]
    Manifest file: app/src/debug/AndroidManifest.xml
    Android resources: [app/src/debug/res]
    Assets: [app/src/debug/assets]
    AIDL sources: [app/src/debug/aidl]
    RenderScript sources: [app/src/debug/rs]
    JNI sources: [app/src/debug/jni]
    JNI libraries: [app/src/debug/jniLibs]
    Java-style resources: [app/src/debug/resources]
    

要查看此输出,请按以下步骤操作:

  1. 点击 IDE 窗口右侧的 Gradle 图标
  2. 依次转到 MyApplication > Tasks > android,然后双击 sourceSets。Gradle 执行该任务后,系统应该会打开 Run 窗口以显示输出。
  3. 如果显示内容不是处于如上所示的文本模式,请点击 Run 窗口左侧的 Toggle view 图标

注意:任务输出还为您展示了如何整理运行应用测试所需的文件的源代码文件集,例如 test/androidTest/ 测试源代码文件集

当您创建新的构建变体时,Android Studio 不会为您创建源代码文件集目录,而是为您提供一些有用的选项。例如,要仅为您的“debug”Build 类型创建 java/ 目录,请执行以下操作:

  1. 打开 Project 窗格,然后从窗格顶部的下拉菜单中选择 Project 视图。
  2. 转到 MyProject/app/src/
  3. 右键点击 src 目录,然后依次选择 New > Folder > Java Folder
  4. Target Source Set 旁边的下拉菜单中,选择 debug
  5. 点击 Finish

Android Studio 会为您的“debug”Build 类型创建一个源代码文件集目录,然后在其中创建 java/ 目录。或者,您也可以让 Android Studio 在您为项目添加特定构建变体的新文件时为您创建这些目录。例如,要为您的“debug”Build 类型创建值 XML 文件,请执行以下操作:

  1. 在同一 Project 窗格中,右键点击 src 目录,然后依次选择 New > XML > Values XML File
  2. 输入 XML 文件的名称或保留默认名称。
  3. Target Source Set 旁边的下拉菜单中,选择 debug
  4. 点击 Finish

由于“debug”Build 类型被指定为目标源代码文件集,因此 Android Studio 在创建 XML 文件时会自动创建必要的目录。生成的目录结构应如图 2 所示。

图 2. “debug”Build 类型的新源代码文件集目录。

遵循相同的过程,您也可以创建产品类型的源代码文件集目录(如 src/demo/)和构建变体的源代码文件集目录(如 src/demoDebug/)。此外,您可以创建定位到特定构建变体的测试源代码文件集(例如 src/androidTestDemoDebug/)。要了解详情,请转到测试源代码文件集

更改默认源代码文件集配置

如果您未按照 Gradle 预期的默认源代码文件集文件结构整理源文件(如上文创建源代码文件集的相关章节中所述),则可以使用 sourceSets 代码块更改 Gradle 为源代码文件集的每个组件收集文件的位置。您无需改变文件的位置,而只需向 Gradle 提供相对于模块级 build.gradle 文件的路径,Gradle 应该会在该路径下找到每个源代码文件集组件的文件。要了解您可以配置哪些组件,以及是否可以将它们映射到多个文件或目录,请参阅 Android Plugin for Gradle DSL 参考文档

以下代码示例将 app/other/ 目录中的源文件映射到 main 源代码文件集的某些组件,并更改 androidTest 源代码文件集的根目录。

    android {
      ...
      sourceSets {
        // Encapsulates configurations for the main source set.
        main {
          // Changes the directory for Java sources. The default directory is
          // 'src/main/java'.
          java.srcDirs = ['other/java']

          // If you list multiple directories, Gradle uses all of them to collect
          // sources. Because Gradle gives these directories equal priority, if
          // you define the same resource in more than one directory, you get an
          // error when merging resources. The default directory is 'src/main/res'.
          res.srcDirs = ['other/res1', 'other/res2']

          // Note: You should avoid specifying a directory which is a parent to one
          // or more other directories you specify. For example, avoid the following:
          // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
          // You should specify either only the root 'other/res1' directory, or only the
          // nested 'other/res1/layouts' and 'other/res1/strings' directories.

          // For each source set, you can specify only one Android manifest.
          // By default, Android Studio creates a manifest for your main source
          // set in the src/main/ directory.
          manifest.srcFile 'other/AndroidManifest.xml'
          ...
        }

        // Create additional blocks to configure other source sets.
        androidTest {

          // If all the files for a source set are located under a single root
          // directory, you can specify that directory using the setRoot property.
          // When gathering sources for the source set, Gradle looks only in locations
          // relative to the root directory you specify. For example, after applying the
          // configuration below for the androidTest source set, Gradle looks for Java
          // sources only in the src/tests/java/ directory.
          setRoot 'src/tests'
          ...
        }
      }
    }
    ...
    

使用源代码文件集构建

您可以在源代码文件集目录中添加只希望针对某些配置打包在一起的代码和资源。例如,如果您要构建“demoDebug”这个变体(“demo”产品类型和“debug”Build 类型的混合产物),则 Gradle 会查看这些目录,并为它们指定以下优先级:

  1. src/demoDebug/(构建变体源代码文件集)
  2. src/debug/(构建类型源代码文件集)
  3. src/demo/(产品类型源代码文件集)
  4. src/main/(主源代码文件集)

注意:如果您结合使用多个产品类型,那么这些产品类型的优先级由它们所属的类型维度决定。使用 android.flavorDimensions 属性列出类型维度时,属于您列出的第一个类型维度的产品类型的优先级高于属于第二个类型维度的产品类型,依此类推。此外,您为产品类型组合创建的源代码文件集的优先级高于属于各个产品类型的源代码文件集。

上面列出的顺序决定了 Gradle 组合代码和资源时哪个源代码文件集的优先级更高。由于 demoDebug/ 源代码文件集目录可能包含该构建变体特有的文件,因此,如果 demoDebug/ 包含在 debug/ 中也定义了的文件,则 Gradle 会使用 demoDebug/ 源代码文件集中的文件。类似地,Gradle 会为 Build 类型和产品类型源代码文件集中的文件提供比 main/ 中的相同文件更高的优先级。在应用以下构建规则时,Gradle 会考虑这种优先级顺序:

  • java/ 目录中的所有源代码将一起编译以生成单个输出。

    注意:对于给定的构建变体,如果 Gradle 遇到两个或更多个源代码文件集目录定义了同一个 Java 类的情况,则会抛出构建错误。例如,在构建调试 APK 时,您不能同时定义 src/debug/Utility.javasrc/main/Utility.java。这是因为 Gradle 在构建过程中会查看这两个目录并抛出“重复类”错误。如果您要为不同的 Build 类型使用不同版本的 Utility.java,则可以让每个 Build 类型定义各自的文件版本,而不是将其包含在 main/ 源代码文件集中。

  • 所有清单都将合并为一个清单。优先级将按照上面列出的顺序分配。也就是说,Build 类型的清单设置会替换产品类型的清单设置,依此类推。要了解详情,请参阅清单合并
  • 同样,values/ 目录中的文件也会合并在一起。如果两个文件同名,例如存在两个 strings.xml 文件,将按照上述列表中的相同顺序指定优先级。也就是说,在 Build 类型源代码文件集的文件中定义的值会重写在产品类型的同一文件中定义的值,依此类推。
  • res/asset/ 目录中的资源会打包到一起。如果在两个或更多个源代码文件集中定义了同名的资源,将按照上面列表中的顺序指定优先级。
  • 最后,在构建 APK 时,Gradle 会为库模块依赖项随附的资源和清单指定最低优先级。

声明依赖项

您可以为特定构建变体或测试源代码文件集配置依赖项,方法是在 Implementation 关键字之前加上构建变体或测试源代码文件集的名称作为前缀,如以下示例所示。

    dependencies {
        // Adds the local "mylibrary" module as a dependency to the "free" flavor.
        freeImplementation project(":mylibrary")

        // Adds a remote binary dependency only for local tests.
        testImplementation 'junit:junit:4.12'

        // Adds a remote binary dependency only for the instrumented test APK.
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
    

有关详情,请参阅添加构建依赖项

配置签名设置

除非您明确定义发布 Build 的签名配置,否则 Gradle 不会为该 Build 的 APK 签名。您可以使用 Android Studio 轻松创建发布密钥并为发布 Build 类型签名

要使用 Gradle 构建配置为您的发布 Build 类型手动配置签名,请执行以下操作:

  1. 创建一个密钥库。密钥库是一个包含一组私钥的二进制文件。您必须将密钥库保存在安全可靠的地方。
  2. 创建一个私钥。私钥代表将通过应用识别的实体,如个人或公司。
  3. 将签名配置添加到模块级 build.gradle 文件中:

        ...
        android {
            ...
            defaultConfig {...}
            signingConfigs {
                release {
                    storeFile file("myreleasekey.keystore")
                    storePassword "password"
                    keyAlias "MyReleaseKey"
                    keyPassword "password"
                }
            }
            buildTypes {
                release {
                    ...
                    signingConfig signingConfigs.release
                }
            }
        }
        

要生成已签名的 APK,请从菜单栏中依次选择 Build > Generate Signed APKapp/build/apk/app-release.apk 中的软件包现已使用您的发布密钥进行签名。

注意:在构建文件中添加发布密钥和密钥库的密码并不是一种好的安全做法。作为替代方案,您可以将构建文件配置为从环境变量获取这些密码,或让构建流程提示您输入这些密码。

要从环境变量获取这些密码,请添加以下代码:

    storePassword System.getenv("KSTOREPWD")
    keyPassword System.getenv("KEYPWD")
    

要让构建流程在您要从命令行调用此版本时提示您输入这些密码,请添加以下代码:

    storePassword System.console().readLine("\nKeystore password: ")
    keyPassword System.console().readLine("\nKey password: ")
    

完成此流程后,您可以分发您的应用并在 Google Play 上发布它。

警告:请将密钥库和私钥保存在安全可靠的地方,并确保您为其创建了安全的备份。如果您将应用发布到 Google Play,随后丢失了为应用签名的密钥,那么您将无法发布任何应用更新,因为您必须始终使用相同的密钥为应用的所有版本签名。

为 Wear OS 应用签名

发布 Wear OS 应用时,手表 APK 和可选的手机 APK 都需要进行签名。要详细了解如何打包 Wear OS 应用并为其签名,请参阅打包穿戴式设备应用