构建多个 APK

注意: 自 2021 年 8 月起,所有新应用都必须发布为应用包。如果您将应用发布到 Google Play,请构建并上传 Android 应用包。这样做时,Google Play 会自动为每个用户的设备配置生成和提供优化的 APK,因此他们只需下载运行您的应用所需的代码和资源。如果您发布到不支持 AAB 格式的商店,则构建多个 APK 很有用。在这种情况下,您必须自己构建、签名和管理每个 APK。

尽管最好尽可能构建单个 APK 来支持所有目标设备,但这可能会导致 APK 非常大,因为文件支持多个 屏幕密度应用二进制接口 (ABI)。减小 APK 大小的一种方法是创建 多个 APK,其中包含针对特定屏幕密度或 ABI 的文件。

Gradle 可以创建单独的 APK,其中仅包含特定于每个密度或 ABI 的代码和资源。此页面介绍如何配置构建以生成多个 APK。如果您需要创建并非基于屏幕密度或 ABI 的不同版本的应用,请改用 构建变体

配置构建以生成多个 APK

要配置构建以生成多个 APK,请将 splits 块添加到模块级的 build.gradle 文件中。在 splits 块中,提供一个 density 块,指定您希望 Gradle 如何生成每个密度的 APK,或提供一个 abi 块,指定您希望 Gradle 如何生成每个 ABI 的 APK。您可以同时提供 density 和 ABI 块,构建系统将为每个密度和 ABI 组合创建一个 APK。

为屏幕密度配置多个 APK

要为不同的屏幕密度创建单独的 APK,请在 splits 块内添加一个 density 块。在 density 块中,提供所需的屏幕密度和兼容屏幕尺寸的列表。只有在需要每个 APK 的清单中包含特定的 <compatible-screens> 元素时,才使用兼容屏幕尺寸的列表。

以下 Gradle DSL 选项用于为屏幕密度配置多个 APK

enable(适用于 Groovy),isEnable(适用于 Kotlin 脚本)
如果将此元素设置为 true,Gradle 将根据您定义的屏幕密度生成多个 APK。默认值为 false
exclude
指定您不希望 Gradle 为其生成单独 APK 的密度的逗号分隔列表。如果您想为大多数密度生成 APK,但需要排除您的应用不支持的少数几个密度,请使用 exclude
reset()

清除屏幕密度的默认列表。仅当与 include 元素结合使用以指定要添加的密度时才使用。

以下代码片段通过调用 reset() 来清除列表,然后使用 include 将密度列表设置为仅 ldpixxhdpi

reset()                  // Clears the default list from all densities
                         // to no densities.
include "ldpi", "xxhdpi" // Specifies the two densities to generate APKs
                         // for.
include
指定您希望 Gradle 为其生成 APK 的密度的逗号分隔列表。仅当与 reset() 结合使用以指定确切的密度列表时才使用。
compatibleScreens

指定兼容屏幕尺寸的逗号分隔列表。这会在每个 APK 的清单中注入匹配的 <compatible-screens> 节点。

此设置提供了一种方便的方法,可以在同一个 build.gradle 部分管理屏幕密度和屏幕尺寸。但是,使用 <compatible-screens> 会限制应用适用的设备类型。有关支持不同屏幕尺寸的其他方法,请参阅 屏幕兼容性概述

由于每个基于屏幕密度的 APK 都包含一个 <compatible-screens> 标签,其中包含关于 APK 支持的屏幕类型的具体限制(即使您发布多个 APK),因此某些新设备与您的多个 APK 筛选器不匹配。因此,Gradle 始终会生成一个额外的通用 APK,其中包含所有屏幕密度的资源,并且不包含 <compatible-screens> 标签。将此通用 APK 与每个密度的 APK 一起发布,以便为与包含 <compatible-screens> 标签的 APK 不匹配的设备提供后备。

以下示例将为每个 屏幕密度 生成单独的 APK,但 ldpixxhdpixxxhdpi 除外。这是通过使用 exclude 从所有密度的默认列表中删除这三个密度来实现的。

Groovy

android {
  ...
  splits {

    // Configures multiple APKs based on screen density.
    density {

      // Configures multiple APKs based on screen density.
      enable true

      // Specifies a list of screen densities you don't want Gradle to create multiple APKs for.
      exclude "ldpi", "xxhdpi", "xxxhdpi"

      // Specifies a list of compatible screen size settings for the manifest.
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
}

Kotlin

android {
    ...
    splits {

        // Configures multiple APKs based on screen density.
        density {

            // Configures multiple APKs based on screen density.
            isEnable = true

            // Specifies a list of screen densities you don't want Gradle to create multiple APKs for.
            exclude("ldpi", "xxhdpi", "xxxhdpi")

            // Specifies a list of compatible screen size settings for the manifest.
            compatibleScreens("small", "normal", "large", "xlarge")
        }
    }
}

有关将应用的不同构建变体自定义到特定屏幕类型和设备的更多详细信息,请参阅 声明受限屏幕支持

为 ABI 配置多个 APK

要为不同的 ABI 创建单独的 APK,请在 splits 块内添加一个 abi 块。在 abi 块中,提供所需的 ABI 列表。

以下 Gradle DSL 选项用于为每个 ABI 配置多个 APK

enable(适用于 Groovy),或 isEnable(适用于 Kotlin 脚本)
如果将此元素设置为 true,Gradle 将根据您定义的 ABI 生成多个 APK。默认值为 false
exclude
指定您不希望 Gradle 为其生成单独 APK 的 ABI 的逗号分隔列表。如果您想为大多数 ABI 生成 APK,但需要排除您的应用不支持的少数几个 ABI,请使用 exclude
reset()

清除默认的 ABI 列表。仅当与`include` 元素结合使用以指定要添加的 ABI 时才使用。

以下代码片段通过调用`reset()` 清除列表,然后使用`include` 将 ABI 列表设置为仅`x86` 和`x86_64`。

reset()                 // Clears the default list from all ABIs to no ABIs.
include "x86", "x86_64" // Specifies the two ABIs we want to generate APKs for.
include
指定您希望 Gradle 为其生成 APK 的 ABI 的逗号分隔列表。仅当与`reset()` 结合使用以指定确切的 ABI 列表时才使用。
Groovy 使用`universalApk`,Kotlin 脚本使用`isUniversalApk`

如果为`true`,Gradle 除了每个 ABI 的 APK 之外,还会生成一个通用 APK。通用 APK 在单个 APK 中包含所有 ABI 的代码和资源。默认值为`false`。

请注意,此选项仅在`splits.abi` 块中可用。当基于屏幕密度构建多个 APK 时,Gradle 始终会生成一个包含所有屏幕密度代码和资源的通用 APK。

以下示例为每个 ABI 生成一个单独的 APK:`x86` 和`x86_64`。这是通过使用`reset()` 从空的 ABI 列表开始,然后使用`include` 和每个都获得一个 APK 的 ABI 列表来完成的。

Groovy

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      enable true

      // By default all ABIs are included, so use reset() and include to specify that you only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs for Gradle to create APKs for to none.
      reset()

      // Specifies a list of ABIs for Gradle to create APKs for.
      include "x86", "x86_64"

      // Specifies that you don't want to also generate a universal APK that includes all ABIs.
      universalApk false
    }
  }
}

Kotlin

android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      isEnable = true

      // By default all ABIs are included, so use reset() and include to specify that you only
      // want APKs for x86 and x86_64.

      // Resets the list of ABIs for Gradle to create APKs for to none.
      reset()

      // Specifies a list of ABIs for Gradle to create APKs for.
      include("x86", "x86_64")

      // Specifies that you don't want to also generate a universal APK that includes all ABIs.
      isUniversalApk = false
    }
  }
}

有关支持的 ABI 列表,请参阅支持的 ABI

没有原生/C++ 代码的项目

对于没有原生/C++ 代码的项目,“构建变体”面板包含两列:“模块”和“活动构建变体”,如图 1 所示。

The Build variants panel
图 1. 对于没有原生/C++ 代码的项目,“构建变体”面板包含两列。

模块的“活动构建变体”值确定在编辑器中部署和显示的构建变体。要在变体之间切换,请单击模块的“活动构建变体”单元格,然后从列表字段中选择所需的变体。

具有原生/C++ 代码的项目

对于具有原生/C++ 代码的项目,“构建变体”面板包含三列:“模块”、“活动构建变体”和“活动 ABI”,如图 2 所示。

图 2. 对于具有原生/C++ 代码的项目,“构建变体”面板添加了“活动 ABI”列。

模块的“活动构建变体”值确定部署并在编辑器中显示的构建变体。对于原生模块,“活动 ABI”值确定编辑器使用的 ABI,但不影响部署的内容。

更改构建类型或 ABI

  1. 单击“活动构建变体”或“活动 ABI”列的单元格。
  2. 从列表字段中选择所需的变体或 ABI。新的同步会自动运行。

更改应用或库模块的任一列都会将更改应用于所有相关行。

配置版本控制

默认情况下,当 Gradle 生成多个 APK 时,每个 APK 具有相同的版本信息,如模块级`build.gradle` 或`build.gradle.kts` 文件中指定。由于 Google Play 商店不允许针对同一应用使用多个 APK,这些 APK 都具有相同的版本信息,因此您需要确保每个 APK 都有唯一的`versionCode`,然后才能上传到 Play 商店。

您可以配置模块级的`build.gradle` 文件来覆盖每个 APK 的`versionCode`。通过创建映射来为配置多个 APK 的每个 ABI 和密度分配唯一的数值,您可以使用结合`defaultConfig` 或`productFlavors` 块中定义的版本代码和分配给密度或 ABI 的数值的值来覆盖输出版本代码。

在以下示例中,`x86` ABI 的 APK 获取`versionCode` 2004,`x86_64` ABI 获取`versionCode` 3004。

以较大的增量(例如 1000)分配版本代码,允许您在需要更新应用时稍后分配唯一的版本代码。例如,如果`defaultConfig.versionCode` 在后续更新中迭代到 5,Gradle 将`versionCode` 2005 分配给`x86` APK,并将 3005 分配给`x86_64` APK。

提示:如果您的构建包含通用 APK,请为其分配一个`versionCode`,该代码低于任何其他 APK 的`versionCode`。由于 Google Play 商店会安装与目标设备兼容且`versionCode` 最高的应用版本,因此为通用 APK 分配较低的`versionCode` 可确保 Google Play 商店尝试安装您的 APK 之一,然后再回退到通用 APK。以下示例代码通过不覆盖通用 APK 的默认`versionCode` 来处理此问题。

Groovy

android {
  ...
  defaultConfig {
    ...
    versionCode 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
ext.abiCodes = ['armeabi-v7a':1, x86:2, x86_64:3]

// For per-density APKs, create a similar map:
// ext.densityCodes = ['mdpi': 1, 'hdpi': 2, 'xhdpi': 3]

import com.android.build.OutputFile

// For each APK output variant, override versionCode with a combination of
// ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
android.applicationVariants.all { variant ->

  // Assigns a different version code for each output APK
  // other than the universal APK.
  variant.outputs.each { output ->

    // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
    def baseAbiVersionCode =
            // Determines the ABI for this variant and returns the mapped value.
            project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

    // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
    // the following code doesn't override the version code for universal APKs.
    // However, because you want universal APKs to have the lowest version code,
    // this outcome is desirable.
    if (baseAbiVersionCode != null) {

      // Assigns the new version code to versionCodeOverride, which changes the
      // version code for only the output APK, not for the variant itself. Skipping
      // this step causes Gradle to use the value of variant.versionCode for the APK.
      output.versionCodeOverride =
              baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    versionCode = 4
  }
  splits {
    ...
  }
}

// Map for the version code that gives each ABI a value.
val abiCodes = mapOf("armeabi-v7a" to 1, "x86" to 2, "x86_64" to 3)

// For per-density APKs, create a similar map:
// val densityCodes = mapOf("mdpi" to 1, "hdpi" to 2, "xhdpi" to 3)

import com.android.build.api.variant.FilterConfiguration.FilterType.*

// For each APK output variant, override versionCode with a combination of
// abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
androidComponents {
    onVariants { variant ->

        // Assigns a different version code for each output APK
        // other than the universal APK.
        variant.outputs.forEach { output ->
            val name = output.filters.find { it.filterType == ABI }?.identifier

            // Stores the value of abiCodes that is associated with the ABI for this variant.
            val baseAbiCode = abiCodes[name]
            // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
            // the following code doesn't override the version code for universal APKs.
            // However, because you want universal APKs to have the lowest version code,
            // this outcome is desirable.
            if (baseAbiCode != null) {
                // Assigns the new version code to output.versionCode, which changes the version code
                // for only the output APK, not for the variant itself.
                output.versionCode.set(baseAbiCode * 1000 + (output.versionCode.get() ?: 0))
            }
        }
    }
}

有关其他版本代码方案的更多示例,请参阅分配版本代码

构建多个 APK

配置模块级的`build.gradle` 或`build.gradle.kts` 文件以构建多个 APK 后,单击构建 > 构建 APK 以构建当前在项目窗格中选择的模块的所有 APK。Gradle 会在项目的`build/outputs/apk/` 目录中创建每个密度或 ABI 的 APK。

Gradle 会为每个配置多个 APK 的密度或 ABI 构建一个 APK。如果您为密度和 ABI 都启用了多个 APK,Gradle 将为每个密度和 ABI 组合创建一个 APK。

例如,以下`build.gradle` 代码片段启用了为`mdpi` 和`hdpi` 密度以及`x86` 和`x86_64` ABI 构建多个 APK。

Groovy

...
  splits {
    density {
      enable true
      reset()
      include "mdpi", "hdpi"
    }
    abi {
      enable true
      reset()
      include "x86", "x86_64"
    }
  }

Kotlin

...
  splits {
    density {
      isEnable = true
      reset()
      include("mdpi", "hdpi")
    }
    abi {
      isEnable = true
      reset()
      include("x86", "x86_64")
    }
  }

示例配置的输出包含以下 4 个 APK

  • app-hdpiX86-release.apk:包含`hdpi` 密度和`x86` ABI 的代码和资源。
  • app-hdpiX86_64-release.apk:包含`hdpi` 密度和`x86_64` ABI 的代码和资源。
  • app-mdpiX86-release.apk:包含`mdpi` 密度和`x86` ABI 的代码和资源。
  • app-mdpiX86_64-release.apk:包含`mdpi` 密度和`x86_64` ABI 的代码和资源。

当基于屏幕密度构建多个 APK 时,Gradle 除了每个密度的 APK 之外,还会始终生成一个包含所有密度的代码和资源的通用 APK。

当基于 ABI 构建多个 APK 时,只有在`build.gradle` 文件(对于 Groovy)的`splits.abi` 块中指定`universalApk true` 或`build.gradle.kts` 文件(对于 Kotlin 脚本)的`splits.abi` 块中指定`isUniversalApk = true` 时,Gradle 才会生成一个包含所有 ABI 代码和资源的 APK。

APK 文件名格式

构建多个 APK 时,Gradle 使用以下方案生成 APK 文件名

modulename-screendensityABI-buildvariant.apk

方案组件包括

modulename
指定正在构建的模块名称。
screendensity
如果启用了屏幕密度的多个 APK,则指定 APK 的屏幕密度,例如`mdpi`。
ABI

如果启用了 ABI 的多个 APK,则指定 APK 的 ABI,例如`x86`。

如果同时启用了屏幕密度和 ABI 的多个 APK,Gradle 会将密度名称与 ABI 名称连接起来,例如`mdpiX86`。如果为每个 ABI 的 APK 启用了`universalApk`,Gradle 会使用`universal` 作为通用 APK 文件名的 ABI 部分。

buildvariant
指定正在构建的构建变体,例如`debug`。

例如,当为 myApp 的调试版本构建`mdpi` 屏幕密度 APK 时,APK 文件名为`myApp-mdpi-debug.apk`。配置为同时为`mdpi` 屏幕密度和`x86` ABI 构建多个 APK 的 myApp 的发行版本具有`myApp-mdpiX86-release.apk` 的 APK 文件名。