构建多个 APK

注意:自 2021 年 8 月起,所有新增的 应用必须以 App Bundle 格式发布。如果您将应用发布到 Google Play 构建并上传 Android App Bundle。时间 之后,Google Play 会自动针对 每位用户的设备配置,因此他们只需下载 运行应用所需的资源 发布到不支持 AAB 格式的商店。在这种情况下 必须自行构建、签署和管理每个 APK。

不过,构建单个 APK 来支持所有目标设备是比较好的做法 都可能导致系统因包含文件而生成非常大的 APK 支持多个 屏幕密度应用二进制文件 接口 (ABI)。减小 APK 大小的一种方法是 多个 APK 包含适用于特定屏幕密度或 ABI 的文件。

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

针对多个 APK 配置 build

如需针对多个 APK 配置 build,请将 splits 代码块添加到模块级 build.gradle 文件。在 splits 代码块,提供 density 代码块,用于指定希望 Gradle 如何生成 按密度生成的 APK 或用于指定 Gradle 的 abi 代码块 按 ABI 生成 APK。您可以同时提供密度代码块和 ABI 代码块,以及 构建系统会针对密度和 ABI 的每种组合创建一个 APK。

针对屏幕密度配置多个 APK

要针对不同的屏幕密度创建单独的 APK,请将 density块内的 splits 代码块。在 density 代码块中,提供一个 所需屏幕密度和兼容屏幕尺寸的列表。仅使用 兼容的屏幕尺寸 <ph type="x-smartling-placeholder"></ph> <compatible-screens> 元素。

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

enable 用于 Groovy,isEnable 用于 Kotlin 脚本
如果将此元素设为 true,Gradle 会生成多个 APK 调整屏幕大小默认值为 false
exclude
以逗号分隔列表的形式指定你不需要 Gradle 的密度 来生成单独的 APK如果您符合以下情况,请使用 exclude 想针对大多数密度生成 APK,但需要排除一些 应用不支持的屏幕密度
reset()

清除默认的屏幕密度列表。只与 include 元素用于指定所需的密度 添加。

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

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

以逗号分隔列表的形式指定兼容的屏幕尺寸。这会注入 匹配的<ph type="x-smartling-placeholder"></ph> <compatible-screens> 节点 每个 APK

此设置提供了一种便捷的方式来管理这两个屏幕 密度和屏幕尺寸。build.gradle 不过,如果使用 <compatible-screens>可以限制设备类型 应用的支持范围如需通过其他方式支持不同的 屏幕尺寸,请参阅 屏幕兼容性概览

因为每个基于屏幕密度的 APK 都包含 具有特定限制的 <compatible-screens> 标记 APK 支持的屏幕类型 - 即使您发布了多个 APK - 某些新设备与您的多个 APK 过滤条件不匹配。因此 Gradle 始终会生成包含资源的额外通用 APK 并且不包含 <compatible-screens> 标记之间。发布此内容 通用 APK 与您的按密度生成的 APK 结合使用, 与 <compatible-screens> 标记之间。

以下示例针对每个 屏幕 密度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")
        }
    }
}

如需详细了解如何自定义不同的 build 针对特定的屏幕类型和设备提供不同的应用变体,请参阅 声明受限 屏幕支持

针对 ABI 配置多个 APK

如需针对不同的 ABI 创建单独的 APK,请在 splits 代码块内添加一个 abi 代码块。在 abi 代码块中,提供 所需的 ABI。

以下 Gradle DSL 选项用于根据 ABI:

enable(对于 Groovy)或 isEnable(对于 Kotlin 脚本)
如果将此元素设为 true,Gradle 会生成多个 APK。默认值为 false
exclude
以逗号分隔列表的形式指定您不希望 Gradle 运行的 ABI 生成单独的 APK如果要生成exclude 适用于大多数 ABI 的 APK,但需要排除您的应用没有的一些 ABI 联系。
reset()

清除默认的 ABI 列表。只与 include 元素来指定要添加的 ABI。

以下代码段将 ABI 列表设置为仅包含 x86x86_64:通过调用 reset() 清除列表;以及 然后使用 include

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 列表。
universalApk(对于 Groovy)或 isUniversalApk(对于 Groovy) Kotlin 脚本

如果设为 true,则除了生成以下 APK 外,Gradle 还会生成通用 APK 按 ABI 生成的 APK。通用 APK 包含适用于 Google Cloud 中适用于所有 ABI 的代码和资源, 单个 APK默认值为 false

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

以下示例针对每个 ABI(即 x86x86_64)生成了单独的 APK,具体方法是使用 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++ 代码的项目,Build Variants 面板有两个 列:模块活动版本 Variant [款式/规格],如图 1 所示。

“build 变体”面板
图 1. Build Variants 面板有两列,显示没有 原生/C++ 代码。

Active Build Variant 的 模块决定了已部署并在编辑器中可见的 build 变体。 如需在变体之间切换,请点击模块的 Active Build Variant 单元格 然后从列表字段中选择所需的变体。

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

对于含有原生/C++ 代码的项目,Build Variants 面板有三个 列:模块, 活动版本 VariantActive ABI,如图 2 所示。

图 2. Build Variants 面板为以下项目添加了 Active ABI 列: 使用原生/C++ 代码构建项目。

模块的 Active Build Variant 值 确定已部署并显示在编辑器中的 build 变体。 对于原生模块,Active ABI 值决定了编辑器的 ABI 但不会影响部署的内容

如需更改 build 类型或 ABI,请执行以下操作:

  1. 点击 Active Build Variant 对应的单元格 Active ABI 列。
  2. 从列表中选择所需的变体或 ABI 字段。系统会自动运行新的同步。

更改应用或库模块的任一列都会将更改应用于 依赖行。

配置版本控制

默认情况下,当 Gradle 生成多个 APK 时,每个 APK 都有相同的 版本信息,如模块级 build.gradlebuild.gradle.kts 文件。由于 Google Play 商店不允许同一个应用的多个 APK 全都具有 相同的版本信息,您需要确保每个 APK 都有唯一的 <ph type="x-smartling-placeholder"></ph> versionCode,然后再上传到 Play 商店。

您可以将模块级 build.gradle 文件配置为 为每个 APK 替换 versionCode。创建映射 为您配置的每个 ABI 和密度分配一个唯一的数值 您可以替换输出版本代码 它会合并 defaultConfig 中定义的版本代码,或 productFlavors块,并将数值分配给 密度或 ABI。

在以下示例中,x86 ABI 的 APK 会获得 2004 的 versionCodex86_64 ABI 获得的 versionCode 为 3004。

如果以较大的增量(例如 1000)分配版本代码, 以便您以后在需要更新应用时分配唯一的版本代码。对于 例如,如果 defaultConfig.versionCode 在 后续更新中,Gradle 会将 2005 的 versionCode 分配给 将 x86 APK 和 3005 复制到 x86_64 APK。

提示:如果您的 build 包含通用 APK,请为它分配一个 versionCode,该值低于您的任何其他 APK 的相应值。 由于 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.gradlebuild.gradle.kts 文件以构建多个 APK,请点击 构建 >构建 APK,以便针对当前 Project 窗格中的选定模块。Gradle 会创建 APK 针对项目 build/outputs/apk/ 中的每种密度或 ABI 目录。

Gradle 会针对您配置了多 APK 构建的每种密度或 ABI 构建一个 APK。如果您同时针对密度和 ABI 启用多个 APK,Gradle 会创建一个 APK 每种密度和 ABI 的组合。

例如,以下 借助 build.gradle 代码段,您可以为 mdpihdpi 密度,以及 x86x86_64 ABI:

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 时,除了按密度生成的 APK 之外,Gradle 始终会生成一个包含适用于所有密度的代码和资源的通用 APK。

根据应用环境构建多个 APK 时, ABI,Gradle 只会生成一个 APK,其中包含 ABI(如果您在universalApk true build.gradle 文件中的 splits.abi 代码块 (适用于 Groovy)或isUniversalApk = true build.gradle.kts 文件中的 splits.abi 代码块 (针对 Kotlin 脚本)。

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 启用了 universalApk APK,Gradle 将 universal 用作通用 APK 的 ABI 部分 文件名。

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

例如,在针对mdpi 调试版 myApp 时,APK 文件名为 myApp-mdpi-debug.apk。发行 myApp 版本,配置为 mdpi 屏幕密度和 x86 ABI 的 APK 文件名为 myApp-mdpiX86-release.apk