配置构建变体

本页介绍如何配置构建变体以从单个项目创建应用的不同版本,以及如何正确管理依赖项和签名配置。

每个构建变体代表您要构建的应用的不同版本。例如,您可能希望构建一个应用版本,该版本是免费的,包含有限的内容集,以及另一个包含更多内容的付费版本。您还可以构建应用的不同版本,这些版本针对不同的设备,根据 API 级别或其他设备差异。

构建变体是 Gradle 使用一组特定规则来组合构建类型和产品风格中配置的设置、代码和资源的结果。尽管您不会直接配置构建变体,但您确实会配置构成它们的构建类型和产品风格。

例如,一个“demo”产品风格可能会指定某些功能和设备要求,例如自定义源代码、资源和最低 API 级别,而“debug”构建类型则应用不同的构建和打包设置,例如调试选项和签名密钥。将这两个组合在一起的构建变体是应用的“demoDebug”版本,它包含“demo”产品风格、“debug”构建类型和main/源集中包含的配置和资源的组合。

配置构建类型

您可以在模块级build.gradle.kts文件的android块中创建和配置构建类型。当您创建新模块时,Android Studio 会自动创建 debug 和 release 构建类型。虽然 debug 构建类型不会出现在构建配置文件中,但 Android Studio 会使用 debuggable true对其进行配置。这使您能够在安全的 Android 设备上调试应用,并使用通用的调试密钥库配置应用签名。

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

Kotlin

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

        getByName("debug") {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }

        /**
         * The `initWith` property lets you 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.
         */
        create("staging") {
            initWith(getByName("debug"))
            manifestPlaceholders["hostName"] = "internal.example.com"
            applicationIdSuffix = ".debugStaging"
        }
    }
}

Groovy

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 lets you 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 需要您将项目与新配置同步。要同步项目,请在您进行更改时单击出现的通知栏中的“立即同步”,或从工具栏中单击“同步项目”。如果 Android Studio 发现您的配置有任何错误,“消息”窗口将显示以描述问题。

要详细了解您可以使用构建类型配置的所有属性,请阅读 BuildType 参考。

配置产品风格

创建产品风格类似于创建构建类型。将产品风格添加到构建配置中的productFlavors块中,并包含您想要的设置。产品风格支持与defaultConfig相同的属性,因为defaultConfig实际上属于 ProductFlavor 类。这意味着您可以在defaultConfig块中为所有风格提供基本配置,每个风格都可以更改这些默认值中的任何一个,例如applicationId。要详细了解应用 ID,请阅读设置应用 ID

注意:您仍然需要在 package 属性中指定包名,该属性位于 main/ 清单文件。您还必须在源代码中使用该包名来引用 R 类或解析任何相关的 Activity 或服务注册。这使您可以使用 applicationId 为每个产品风味提供一个用于打包和分发的唯一 ID,而无需更改您的源代码。

所有风味都必须属于一个命名的风味维度,它是一组产品风味。您必须将所有风味分配给一个风味维度;否则,您将收到以下构建错误。

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

如果给定模块仅指定一个风味维度,则 Android Gradle 插件会自动将该模块的所有风味分配到该维度。

以下代码示例创建了一个名为“version”的风味维度,并添加了“demo”和“full”产品风味。这些风味提供了它们自己的 applicationIdSuffixversionNameSuffix

Kotlin

android {
    ...
    defaultConfig {...}
    buildTypes {
        getByName("debug"){...}
        getByName("release"){...}
    }
    // Specifies one flavor dimension.
    flavorDimensions += "version"
    productFlavors {
        create("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"
        }
        create("full") {
            dimension = "version"
            applicationIdSuffix = ".full"
            versionNameSuffix = "-full"
        }
    }
}

Groovy

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"
        }
    }
}

注意:如果您有一个旧版应用(创建于 2021 年 8 月之前),您使用 Google Play 上的 APK 分发该应用,要使用 Google Play 上的 多个 APK 支持 分发您的应用,请将相同的 applicationId 值分配给所有变体,并为每个变体提供不同的 versionCode。要将您的应用的不同变体作为单独的应用分发到 Google Play,您需要为每个变体分配不同的 applicationId

创建和配置好产品风味后,点击通知栏中的立即同步。同步完成后,Gradle 会根据您的构建类型和产品风味自动创建构建变体,并根据 <product-flavor><Build-Type> 为它们命名。例如,如果您创建了“demo”和“full”产品风味,并保留了默认的“debug”和“release”构建类型,Gradle 会创建以下构建变体

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

要选择要构建和运行的构建变体,请转到构建 > 选择构建变体,并从菜单中选择一个构建变体。要开始使用其自己的功能和资源自定义每个构建变体,您需要 创建和管理源集,如本页所述。

更改构建变体的应用 ID

构建应用的 APK 或 AAB 时,构建工具会使用 build.gradle.kts 文件中 defaultConfig 块中定义的应用 ID 对应用进行标记,如以下示例所示。但是,如果要创建应用的不同版本,使其在 Google Play 商店中显示为单独的列表(例如,“免费版”和“专业版”),则需要创建单独的 构建变体,每个变体都有不同的应用 ID。

在这种情况下,将每个构建变体定义为单独的 产品风味。对于 productFlavors 块中的每个风味,您可以重新定义 applicationId 属性,也可以使用 applicationIdSuffix 向默认应用 ID 追加一个段,如下所示

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
        }
    }
}

Groovy

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    productFlavors {
        free {
            applicationIdSuffix ".free"
        }
        pro {
            applicationIdSuffix ".pro"
        }
    }
}

这样,"free" 产品风味的应用 ID 就是 "com.example.myapp.free"。

您还可以使用 applicationIdSuffix 根据您的 构建类型 追加一个段,如下所示

Kotlin

android {
    ...
    buildTypes {
        getByName("debug") {
            applicationIdSuffix = ".debug"
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }
    }
}

由于 Gradle 在应用产品风味配置之后应用构建类型配置,因此 "free debug" 构建变体的应用 ID 为 "com.example.myapp.free.debug"。当您想在同一设备上同时拥有调试版本和发布版本时,这很有用,因为两个应用不能拥有相同的应用 ID。

如果您有一个旧版应用(创建于 2021 年 8 月之前),您使用 Google Play 上的 APK 分发该应用,并且您想使用相同的应用列表分发多个 APK,每个 APK 都针对不同的设备配置(例如 API 级别),那么您必须使用相同的应用 ID 为每个构建变体,但为每个 APK 提供不同的 versionCode。有关更多信息,请阅读有关 多个 APK 支持 的内容。使用 AAB 发布不受影响,因为它使用单个工件,默认情况下该工件使用单个版本代码和应用 ID。

提示:如果需要在清单文件中引用应用 ID,可以在任何清单属性中使用 ${applicationId} 占位符。在构建过程中,Gradle 会将此标记替换为实际的应用 ID。有关更多信息,请参阅 将构建变量注入清单

将多个产品风味与风味维度组合

在某些情况下,您可能希望将来自多个产品风味的配置组合在一起。例如,您可能希望为“full”和“demo”产品风味创建不同的配置,这些配置基于 API 级别。为此,Android Gradle 插件允许您创建多个产品风味组作为风味维度。

构建应用时,Gradle 会将您定义的每个风味维度的产品风味配置与构建类型配置相结合,以创建最终的构建变体。Gradle 不会组合属于同一风味维度的产品风味。

以下代码示例使用 flavorDimensions 属性创建一个名为“mode”的风味维度来对“full”和“demo”产品风味进行分组,并创建一个名为“api”的风味维度来对基于 API 级别进行分组的产品风味配置。

Kotlin

android {
  ...
  buildTypes {
    getByName("debug") {...}
    getByName("release") {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their 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 += listOf("api", "mode")

  productFlavors {
    create("demo") {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension = "mode"
      ...
    }

    create("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, with the first dimension having a higher
    // priority than the second, and so on.
    create("minApi24") {
      dimension = "api"
      minSdk = 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.
      versionCode = 30000 + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi24"
      ...
    }

    create("minApi23") {
      dimension = "api"
      minSdk = 23
      versionCode = 20000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi23"
      ...
    }

    create("minApi21") {
      dimension = "api"
      minSdk = 21
      versionCode = 10000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi21"
      ...
    }
  }
}
...

Groovy

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

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their 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, with the first dimension having 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.

      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 创建的构建变体数量等于每个风味维度中风味数量与您配置的构建类型数量的乘积。当 Gradle 为每个构建变体或相应的工件命名时,属于优先级较高的风味维度的产品风味将首先出现,然后是来自优先级较低维度的产品风味,最后是构建类型。

以之前的构建配置为例,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.gradle.kts 文件中创建变体筛选器。

以上一节的构建配置为例,假设您计划仅支持 API 级别 23 及更高的应用演示版本。您可以使用 variantFilter 块筛选出所有组合了 "minApi21" 和 "demo" 产品风味的构建变体配置

Kotlin

android {
  ...
  buildTypes {...}

  flavorDimensions += listOf("api", "mode")
  productFlavors {
    create("demo") {...}
    create("full") {...}
    create("minApi24") {...}
    create("minApi23") {...}
    create("minApi21") {...}
  }
}

androidComponents {
    beforeVariants { variantBuilder ->
        // To check for a certain build type, use variantBuilder.buildType == "<buildType>"
        if (variantBuilder.productFlavors.containsAll(listOf("api" to "minApi21", "mode" to "demo"))) {
            // Gradle ignores any variants that satisfy the conditions above.
            variantBuilder.enable = false
        }
    }
}
...

Groovy

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)
      }
  }
}
...

将变体筛选器添加到构建配置并点击通知栏中的立即同步后,Gradle 会忽略满足您指定条件的任何构建变体。当您从菜单栏点击构建 > 选择构建变体或在工具窗口栏点击构建变体 时,这些构建变体将不再出现在菜单中。

创建源集

默认情况下,Android Studio 会创建 main/ 源集 以及您希望在所有构建变体之间共享的所有内容的目录。但是,您可以创建新的源集来精确控制 Gradle 为特定构建类型、产品风味、产品风味组合(使用 风味维度)和构建变体编译和打包哪些文件。

例如,您可以在 main/ 源集中定义基本功能,并使用产品风味源集更改应用的品牌以适合不同的客户,或者仅针对使用调试构建类型的构建变体包含特殊权限和日志记录功能。

Gradle 预计源集文件和目录以某种特定方式组织,类似于 main/ 源集。例如,Gradle 预计特定于 "debug" 构建类型的 Kotlin 或 Java 类文件位于 src/debug/kotlin/src/debug/java/ 目录中。

Android Gradle 插件提供了一个有用的 Gradle 任务,它可以显示如何为每个构建类型、产品风味和构建变体组织文件。例如,任务输出中的以下示例描述了 Gradle 预计在哪里找到 "debug" 构建类型的某些文件

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

...

debug
----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Kotlin sources: [app/src/debug/kotlin, 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. 在工具窗口栏中点击Gradle
  2. 导航到MyApplication > 任务 > android,然后双击sourceSets

    要查看任务文件夹,您必须让 Gradle 在同步期间构建任务列表。为此,请执行以下步骤

    1. 点击文件 > 设置 > 实验性(在 macOS 上为Android Studio > 设置 > 实验性)。
    2. 取消选中在 Gradle 同步期间不构建 Gradle 任务列表
  3. Gradle 执行完任务后,将打开运行窗口显示输出。

注意:任务输出还显示了如何组织用于运行应用测试的文件的源集,例如 test/androidTest/ 测试源集

创建新的构建变体时,Android Studio 不会为您创建源集目录,但它会提供一些帮助选项。例如,要仅为 "debug" 构建类型创建 java/ 目录

  1. 打开项目窗格,然后从窗格顶部的菜单中选择项目视图。
  2. 导航到 MyProject/app/src/
  3. 右键点击 src 目录,然后选择新建 > 目录
  4. Gradle 源集下的菜单中选择full/java
  5. Enter

Android Studio 会为您的调试构建类型创建一个源集目录,然后在其内部创建 java/ 目录。或者,Android Studio 可以为您创建目录,当您将新文件添加到项目的特定构建变体时。

例如,要为 "debug" 构建类型创建 values XML 文件

  1. 在**项目**窗格中,右键单击src目录,然后选择**新建** > **XML** > **值 XML 文件**。
  2. 输入 XML 文件的名称,或保留默认名称。
  3. 从**目标源集**旁边的菜单中,选择**debug**。
  4. 单击**完成**。

由于将“debug”构建类型指定为目标源集,因此 Android Studio 在创建 XML 文件时会自动创建必要的目录。生成的目录结构类似于图 1。

图 1.“debug”构建类型的新的源集目录。

活动源集的图标中会显示一个绿色指示器,表明它们处于活动状态。debug源集后缀为[main],表示它将合并到main源集中。

使用相同的方法,您还可以为产品风味(例如src/demo/)和构建变体(例如src/demoDebug/)创建源集目录。此外,您还可以创建针对特定构建变体的测试源集,例如src/androidTestDemoDebug/。要了解更多信息,请阅读有关测试源集的内容。

更改默认源集配置

如果您拥有的源代码没有按照 Gradle 预期的那样组织到默认的源集文件结构中(如上一节关于创建源集的说明),则可以使用 sourceSets块来更改 Gradle 查找每个源集组件文件的路径。

sourceSets块必须位于android块中。您不需要重新定位源文件;您只需要向 Gradle 提供相对于模块级build.gradle.kts文件的路径,Gradle 可以在这些路径中找到每个源集组件的文件。要了解可以配置哪些组件以及是否可以将它们映射到多个文件或目录,请参阅Android Gradle 插件 API 参考

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

Kotlin

android {
  ...
  // Encapsulates configurations for the main source set.
  sourceSets.getByName("main") {
    // Changes the directory for Java sources. The default directory is
    // 'src/main/java'.
    java.setSrcDirs(listOf("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 receive an
    // error when merging resources. The default directory is 'src/main/res'.
    res.setSrcDirs(listOf("other/res1", "other/res2"))

    // Note: Avoid specifying a directory that 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']
    // 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.
  sourceSets.getByName("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")
      ...
  }
}
...

Groovy

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 receive an
      // error when merging resources. The default directory is 'src/main/res'.
      res.srcDirs = ['other/res1', 'other/res2']

      // Note: Avoid specifying a directory that 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']
      // 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'
      ...
    }
  }
}
...

请注意,源代码目录只能属于一个源集。例如,您不能与testandroidTest源集共享相同的测试源代码。这是因为 Android Studio 为每个源集创建单独的 IntelliJ 模块,并且不支持跨源集的重复内容根目录。

使用源集构建

您可以使用源集目录来包含只想与某些配置一起打包的代码和资源。例如,如果您正在构建“demoDebug”构建变体(这是“demo”产品风味和“debug”构建类型的交叉组合),Gradle 会查看这些目录并按以下优先级对它们进行处理

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

为产品风味组合创建的源集必须包含所有风味维度。例如,构建变体源集必须是构建类型和所有风味维度的组合。不支持合并涵盖多个但不包含所有风味维度的文件夹的代码和资源。

如果您组合多个产品风味,则产品风味之间的优先级由它们所属的风味维度决定。在使用 android.flavorDimensions属性列出风味维度时,您列出的第一个风味维度所属的产品风味比第二个风味维度所属的产品风味具有更高的优先级,依此类推。此外,您为产品风味组合创建的源集比属于单个产品风味的源集具有更高的优先级。

优先级顺序决定了 Gradle 组合代码和资源时哪个源集具有更高的优先级。由于demoDebug/源代码目录可能包含特定于该构建变体的文件,因此如果demoDebug/包含在debug/中也定义的文件,Gradle 会使用demoDebug/源代码目录中的文件。同样,Gradle 会将构建类型和产品风味源代码目录中的文件赋予比main/中的相同文件更高的优先级。在应用以下构建规则时,Gradle 会考虑此优先级顺序

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

    注意:对于给定的构建变体,如果 Gradle 遇到两个或多个源代码目录已定义了相同的 Kotlin 或 Java 类,则会抛出构建错误。例如,在构建调试应用程序时,您不能同时定义src/debug/Utility.ktsrc/main/Utility.kt,因为 Gradle 在构建过程中会查看这两个目录,并抛出“重复类”错误。如果您希望不同构建类型具有Utility.kt的不同版本,则每个构建类型都必须定义自己的文件版本,并且不能将其包含在main/源集中。

  • 清单将合并到单个清单中。优先级与之前示例中列表的顺序相同。也就是说,构建类型的清单设置会覆盖产品风味的清单设置,依此类推。要了解更多信息,请阅读有关清单合并的内容。
  • values/目录中的文件将合并到一起。如果两个文件具有相同的名称(例如,两个strings.xml文件),则优先级与之前示例中列表的顺序相同。也就是说,构建类型源代码目录中文件中定义的值会覆盖产品风味中相同文件中定义的值,依此类推。
  • res/asset/目录中的资源将一起打包。如果在两个或多个源集中定义了具有相同名称的资源,则优先级与之前示例中列表的顺序相同。
  • 在构建应用程序时,Gradle 会将库模块依赖项中包含的资源和清单赋予最低优先级。

声明依赖项

要为特定构建变体或测试源集配置依赖项,请在Implementation关键字之前添加构建变体或测试源集的名称,如以下示例所示

Kotlin

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.6.1")
}

Groovy

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.6.1'
}

有关配置依赖项的更多信息,请参阅添加构建依赖项

使用变体感知依赖项管理

Android Gradle 插件 3.0.0 及更高版本包含一种新的依赖项机制,该机制在使用库时会自动匹配变体。这意味着应用程序的debug变体会自动使用库的debug变体,依此类推。它在使用风味时也能正常工作:应用程序的freeDebug变体会使用库的freeDebug变体。

为了使插件能够准确匹配变体,您需要提供匹配的回退,如以下部分所述,用于无法直接匹配的情况。

例如,假设您的应用程序配置了一个名为“staging”的构建类型,但其中一个库依赖项没有此构建类型。当插件尝试构建您的应用程序的“staging”版本时,它将不知道使用库的哪个版本,您将看到类似于以下内容的错误消息

Error:Failed to resolve: Could not resolve project :mylibrary.
Required by:
    project :app

解决与变体匹配相关的构建错误

插件包含 DSL 元素,可帮助您控制 Gradle 如何解决应用程序和依赖项之间无法直接匹配变体的情况。

以下是与变体感知依赖项匹配相关的错误列表,以及如何使用 DSL 属性解决这些错误

  • 您的应用程序包含库依赖项中不存在的构建类型。

    例如,您的应用程序包含“staging”构建类型,但依赖项仅包含“debug”和“release”构建类型。

    请注意,当库依赖项包含您的应用程序中不存在的构建类型时,不会出现问题。这是因为插件永远不会从依赖项中请求该构建类型。

    使用matchingFallbacks为给定的构建类型指定备用匹配项,如下所示

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        buildTypes {
            getByName("debug") {}
            getByName("release") {}
            create("staging") {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks += listOf("debug", "qa", "release")
            }
        }
    }

    Groovy

    // In the app's build.gradle file.
    android {
        buildTypes {
            debug {}
            release {}
            staging {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks = ['debug', 'qa', 'release']
            }
        }
    }
    
  • 对于应用程序及其库依赖项中都存在的给定风味维度,您的应用程序包含库中不存在的风味。

    例如,您的应用程序及其库依赖项都包含“tier”风味维度。但是,应用程序中的“tier”维度包含“free”和“paid”风味,但依赖项仅包含相同维度上的“demo”和“paid”风味。

    请注意,对于应用程序及其库依赖项中都存在的给定风味维度,当库包含应用程序中不存在的产品风味时,不会出现问题。这是因为插件永远不会从依赖项中请求该风味。

    使用matchingFallbacks为应用程序的“free”产品风味指定备用匹配项,如下所示

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions += "tier"
        productFlavors {
            create("paid") {
                dimension = "tier"
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            create("free") {
                dimension = "tier"
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks += listOf("demo", "trial")
            }
        }
    }
    

    Groovy

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions 'tier'
        productFlavors {
            paid {
                dimension 'tier'
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            free {
                dimension 'tier'
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks = ['demo', 'trial']
            }
        }
    }
    
  • 库依赖项包含您的应用程序中不存在的风味维度。

    例如,库依赖项包含“minApi”维度的风味,但您的应用程序仅包含“tier”维度的风味。当您想要构建您的应用程序的“freeDebug”版本时,插件将不知道使用依赖项的“minApi23Debug”版本还是“minApi18Debug”版本。

    请注意,当您的应用程序包含库依赖项中不存在的风味维度时,不会出现问题。这是因为插件仅匹配依赖项中存在的维度的风味。例如,如果依赖项不包含用于 ABI 的维度,则您的应用程序的“freeX86Debug”版本将使用依赖项的“freeDebug”版本。

    defaultConfig块中使用missingDimensionStrategy来指定插件从每个缺少的维度中选择的默认风味,如以下示例所示。您也可以在productFlavors块中覆盖您的选择,因此每个风味都可以为缺少的维度指定不同的匹配策略。

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy("minApi", "minApi18", "minApi23")
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy("abi", "x86", "arm64")
        }
        flavorDimensions += "tier"
        productFlavors {
            create("free") {
                dimension = "tier"
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the "minApi" dimension.
                missingDimensionStrategy("minApi", "minApi23", "minApi18")
            }
            create("paid") {}
        }
    }
    

    Groovy

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy 'abi', 'x86', 'arm64'
        }
        flavorDimensions 'tier'
        productFlavors {
            free {
                dimension 'tier'
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the 'minApi' dimension.
                missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
            }
            paid {}
        }
    }
    

有关更多信息,请参阅matchingFallbacksmissingDimensionStrategy,它们位于 Android Gradle 插件 DSL 参考中。

配置签名设置

除非您明确为该构建定义签名配置,否则 Gradle 不会对您的发布版构建的 APK 或 AAB 进行签名。如果您还没有签名密钥,请使用 Android Studio生成上传密钥和密钥库

要使用 Gradle 构建配置手动配置您的发布版构建类型的签名配置

  1. 创建密钥库。密钥库是一个包含一组私钥的二进制文件。您必须将密钥库保存在安全可靠的地方。
  2. 创建私钥。私钥用于为您的应用程序签名以进行分发,并且不会包含在应用程序中,也不会泄露给未经授权的第三方。
  3. 将签名配置添加到模块级build.gradle.kts文件中

    Kotlin

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

    Groovy

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

注意:在构建文件中包含发布密钥和密钥库的密码不是良好的安全实践。相反,请配置构建文件以从环境变量获取这些密码,或让构建过程提示您输入这些密码。

要从环境变量中获取这些密码

Kotlin

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

Groovy

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

或者,您可以从本地属性文件中加载密钥库。出于安全原因,请勿将此文件添加到源代码管理中。相反,请为每个开发人员在本地设置它。要了解更多信息,请阅读 从构建文件中删除签名信息

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

警告:请将您的密钥库和私钥保存在安全的地方,并确保您对其进行了安全的备份。如果您使用 Play 应用签名,并且丢失了上传密钥,您可以使用 Play 管理中心请求重置。如果您在没有使用 Play 应用签名的情况下发布应用(适用于 2021 年 8 月之前创建的应用),并且您丢失了应用签名密钥,您将无法发布任何更新到您的应用,因为您必须始终使用相同的密钥签名所有版本的应用。

签名 Wear OS 应用

发布 Wear OS 应用时,手表 APK 和可选的手机 APK 必须使用相同的密钥签名。有关打包和签名 Wear OS 应用的更多信息,请参阅打包和分发 Wear 应用