配置构建变体

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

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

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

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

配置构建类型

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

如果要添加或更改某些设置,可以将 debug 构建类型添加到您的配置中。以下示例为 debug 构建类型指定了 applicationIdSuffix,并配置了一个使用 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

注意:您仍然需要使用清单文件 main/ 中的 package 属性指定包名。您还必须在源代码中使用该包名来引用 R 类或解析任何相对活动或服务注册。这样,您可以使用 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 月之前创建),您使用 APK 在 Google Play 上分发该应用,要使用 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 月之前创建),您使用 APK 在 Google Play 上分发该应用,并且您想使用相同的应用列表来分发多个 APK,每个 APK 都针对不同的设备配置(例如 API 级别),那么您必须为每个构建变体使用相同的应用程序 ID,但为每个 APK 提供不同的 versionCode。有关更多信息,请阅读有关 多 APK 支持 的内容。使用 AAB 发布不受影响,因为它使用一个工件,该工件默认使用一个版本代码和应用程序 ID。

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

将多个产品风格与风格维度结合使用

在某些情况下,您可能希望组合来自多个产品风格的配置。例如,您可能希望为基于 API 级别的“full”和“demo”产品风格创建不同的配置。为此,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 > Tasks > android 并双击sourceSets

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

    1. 单击File > Settings > Experimental(在 macOS 上为Android Studio > Settings > Experimental)。
    2. 取消选中Do not build Gradle task list during Gradle sync
  3. Gradle 执行任务后,将打开Run 窗口以显示输出。

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

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

  1. 打开Project 窗格,然后从窗格顶部的菜单中选择Project 视图。
  2. 导航到MyProject/app/src/
  3. 右键单击src 目录,然后选择New > Directory
  4. Gradle Source Sets下的菜单中,选择full/java
  5. Enter

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

例如,要为您的“debug”构建类型创建 values XML 文件

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

由于“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”版本。

    请注意,如果您的应用包含库依赖项未包含的风味维度,则不会出现问题。这是因为插件仅匹配依赖项中存在的维度的风味。例如,如果依赖项不包含ABIs的维度,则应用的“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 {}
        }
    }

有关更多信息,请参阅Android Gradle插件DSL参考中的matchingFallbacksmissingDimensionStrategy

配置签名设置

除非您明确为此构建定义签名配置,否则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应用