配置 build 变体

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

每种 build 变体都代表您可以构建的一个不同的应用版本。例如,您可能希望为应用构建两个版本,一个是内容有限的免费版本,另一个是包含更多内容的付费版本。您还可以根据 API 级别或其他设备变化因素,为应用构建以不同设备为目标的不同版本。

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

例如,“demo”产品变种可以指定某些功能和设备要求(如自定义源代码、资源和最低 API 级别),而“debug”build 类型则会采用不同的构建和打包设置(如调试选项和签名密钥)。结合上述两者的 build 变体是您的应用的“demoDebug”版本,它由“demo”产品变种、“debug”build 类型和 main/ 源代码集中包含的配置和资源组合而成。

配置 build 类型

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

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

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 会要求您将项目与新配置同步。如要同步您的项目,请点击做出更改后显示的通知栏中的 Sync Now,或者点击菜单栏中的 Sync Project 图标 。如果 Android Studio 发现配置有任何错误,就会显示 Messages 窗口,以说明相应的问题。

如需详细了解您可以通过 build 类型配置的所有属性,请参阅 BuildType 参考文档。

配置产品变种

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

注意:您仍然需要在 main/ 清单文件中使用 package 属性指定软件包名称。此外,您还必须在源代码中使用该软件包名称引用 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"
        }
    }
}

注意:如果您有使用 APK 在 Google Play 上进行分发的旧版应用(创建于 2021 年 8 月之前),若需在 Google Play 中使用多 APK 支持功能分发应用,请将相同的 applicationId 值赋予所有变体,并为每个变体分别指定一个不同的 versionCode。如需在 Google Play 中以独立应用的形式分发应用的不同变体,您需要为每个变体分配一个不同的 applicationId

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

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

若要选择要构建并运行的 build 变体,请依次前往 Build > Select Build Variant,然后从菜单中选择一个 build 变体。不过,如需开始使用每个 build 变体自己的功能和资源对其进行自定义,您需要创建和管理源代码集(具体说明见本页)。

更改 build 变体的应用 ID

当您为应用构建 APK 或 AAB 时,构建工具会使用 build.gradle.kts 文件的 defaultConfig 代码块中定义的应用 ID 来标记应用(如下方示例中所示)。不过,如果您想创建不同版本的应用,使它们在 Google Play 商店中以单独的详情进行显示(如“free”和“pro”版本),就需要创建具有不同应用 ID 的多个单独的 build 变体

在这种情况下,将每个 build 变体定义为单独的产品变种。对于 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 会在产品变种后面应用 build 类型配置,因此“free debug”build 变体的应用 ID 是“com.example.myapp.free.debug”。如果您希望同一设备上同时具有调试 build 和发布 build,这会很有用,因为两个应用不能具有相同的应用 ID。

如果您有使用 APK 在 Google Play 上进行分发的旧版应用(创建于 2021 年 8 月之前),想使用相同的应用详情分发多个 APK,并让每个 APK 都以不同的设备配置(例如 API 级别)作为目标,则您必须对所有 build 变体使用相同的应用 ID,但为每个 APK 分别指定一个不同的 versionCode。如需了解详情,请参阅多 APK 支持。如果您使用 AAB 进行发布,则不会受到任何影响,因为 AAB 使用单个工件,而该工件默认使用单个版本代码和应用 ID。

提示:如果需要在清单文件中引用应用 ID,您可以在任何清单属性中使用 ${applicationId} 占位符。在构建期间,Gradle 会将此标记替换为实际的应用 ID。如需了解详情,请参阅将 build 变量注入清单

将多个产品变种与变种维度组合在一起

在某些情况下,您可能要将多个产品变种的配置组合在一起。例如,您可能要为基于 API 级别的“full”和“demo”产品变种创建不同的配置。为此,您可以使用 Android Gradle 插件创建多组产品变种作为变种维度。

在构建应用时,Gradle 会将您定义的每个变种维度中的产品变种配置以及 build 类型配置组合在一起,以创建最终的 build 变体。Gradle 不会将属于同一变种维度的产品变种组合在一起。

以下代码示例使用 flavorDimensions 属性创建“mode”变种维度和“api”变种维度,前者用于对“full”和“demo”产品变种进行分组,后者用于根据 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 创建的 build 变体数量等于每个变种维度中的变种数量与您配置的 build 类型数量的乘积。当 Gradle 为每个 build 变体或对应的工件命名时,先显示属于较高优先级变种维度的产品变种,接着是较低优先级维度中的产品变种,再接着是 build 类型。

以上面的 build 配置为例,Gradle 使用以下命名方案创建了总共 12 个 build 变体:

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

除了可以为各个产品变种和 build 变体创建源代码集目录之外,您还可以为产品变种的每个组合创建源代码集目录。例如,您可以创建 Java 源代码并将其添加到 src/demoMinApi24/java/ 目录中,只有在构建将这两个产品变种组合在一起的变体时,Gradle 才会使用这些源代码。

您为产品变种组合创建的源代码集的优先级高于属于各个产品变种的源代码集。如需详细了解源代码集以及 Gradle 如何合并资源,请参阅关于如何创建源代码集的部分。

过滤变体

Gradle 会为您配置的产品变种和 build 类型的每种可能组合创建一个 build 变体。不过,某些 build 变体可能并不是您需要的,或者在您项目的背景下没有意义。您可以在模块级 build.gradle.kts 文件中创建变体过滤器,以移除某些 build 变体配置。

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

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

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

创建源代码集

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

例如,您可以在 main/ 源代码集中定义基本功能,并使用产品变种源代码集为不同客户端更改应用的品牌信息,或仅为使用“debug”build 类型的 build 变体添加特殊权限和日志记录功能。

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

Android Gradle 插件提供了一项有用的 Gradle 任务,可向您展示如何整理每个 build 类型、产品变种和 build 变体的文件。例如,任务输出中的以下示例描述了 Gradle 希望在何处能够找到“debug”build 类型的某些文件:

------------------------------------------------------------
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/ 测试源代码集

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

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

Android Studio 会为“debug”build 类型创建源代码集目录,然后在其中创建 java/ 目录。或者,Android Studio 可在您为特定 build 变体向项目中添加新文件时为您创建这些目录。

例如,如需为“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 文件时自动创建必要的目录。生成的目录结构如图 1 所示。

图 1. “debug”build 类型的新源代码集目录。

活跃的源代码集的图标上会显示绿色指示标志,表明其处于活跃状态。debug 源代码集以 [main] 为后缀,表示它将合并到 main 源代码集中。

遵循相同的过程,您也可以创建产品变种的源代码集目录(如 src/demo/)和 build 变体的源代码集目录(如 src/demoDebug/)。此外,您还可以创建针对特定 build 变体的测试源代码集,如 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”build 变体(“demo”产品变种和“debug”build 类型的混合产物),Gradle 会查看这些目录,并为它们指定以下优先级:

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

为产品变种组合创建的源代码集必须包含所有变种维度。 例如,build 变体源代码集必须是 build 类型及所有变种维度的组合。不支持合并涉及的文件夹涵盖多个(但并非全部)变种维度的代码和资源。

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

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

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

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

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

声明依赖项

若要为特定 build 变体或测试源代码集配置依赖项,请在 Implementation 关键字前面加上 build 变体或测试源代码集的名称作为前缀,如以下示例所示:

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

如需详细了解如何配置依赖项,请参阅添加 build 依赖项

使用变体感知型依赖项管理机制

Android Gradle 插件 3.0.0 及更高版本包含一种新的依赖项机制,该机制可在使用库时自动匹配变体。这意味着,应用的 debug 变体会自动使用库的 debug 变体,依此类推。这种机制在使用变种时也同样适用:应用的 freeDebug 变体将使用库的 freeDebug 变体。

为了让插件准确匹配变体,您需要在无法进行直接匹配的情况下,按照以下部分中所述提供匹配回退机制

例如,假设您的应用配置了一个名为“staging”的 build 类型,但该应用的一个库依赖项没有进行相应配置。当插件尝试构建“staging”版本的应用时,它不知道要使用哪个版本的库,因此您将看到一条与以下内容类似的错误消息:

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

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

插件包含一些 DSL 元素,这些元素有助于控制 Gradle 如何解决应用与依赖项之间无法进行直接变体匹配的问题。

下表列出了一些与变体感知依赖项匹配相关的问题,以及如何使用 DSL 属性来解决特定问题:

  • 您的应用包含库依赖项不包含的 build 类型

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

    请注意,如果库依赖项包含您的应用不包含的 build 类型,这不会引发问题。这是因为,插件在任何时候都不会从依赖项请求该 build 类型。

    使用 matchingFallbacks 为给定的 build 类型指定替代匹配项,如下所示:

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

如需了解详情,请参阅 Android Gradle 插件 DSL 参考文档中的 matchingFallbacksmissingDimensionStrategy

配置签名设置

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

如需使用 Gradle build 配置为“release”build 类型手动进行签名配置,请执行以下操作:

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

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

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

Kotlin

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

Groovy

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

或者,您也可以从一个本地属性文件中加载密钥库。出于安全原因,请勿将此文件添加到源代码控制系统中,而应针对每个开发者在本地设置此文件。如需了解详情,请参阅从 build 文件中移除签名信息

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

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

为 Wear OS 应用签名

发布 Wear OS 应用时,必须使用同一个密钥为手表 APK 和可选的手机 APK 签名。如需详细了解如何打包 Wear OS 应用并为其签名,请参阅打包和分发 Wear 应用