注意:从 2021 年 8 月开始,所有新应用都必须以 App Bundle 格式发布。如果您将应用发布到 Google Play,请构建并上传 Android App Bundle。当您执行此操作时,Google Play 会针对每个用户的设备配置自动生成并提供经过优化的 APK,以便他们仅下载运行您的应用所需的代码和资源。如果您要将应用发布到不支持 AAB 格式的商店,那么发布多个 APK 会非常有用。在这种情况下,您必须自行构建和管理每个 APK 并为其签名。
虽然最好构建一个 APK 来支持所有的目标设备,但由于文件支持多种 屏幕密度或应用二进制接口 (ABI),因此这样做可能会导致生成非常大的 APK。如需缩减 APK 的大小,一种方法是创建多个 APK,让其中包含适用于特定屏幕密度或 ABI 的文件。
Gradle 可以创建单独的 APK,让每个 APK 仅包含每种密度或 ABI 专用的代码和资源。本页介绍了如何配置 build 以生成多个 APK。如果您需要创建不基于屏幕密度或 ABI 的不同应用版本,请改用 build 变体。
针对多个 APK 配置 build
如需针对多个 APK 配置 build,请将 splits
代码块添加到模块级 build.gradle
文件。在 splits
代码块内,提供一个 density
代码块,指定您希望 Gradle 如何按密度生成 APK;或提供一个 abi
代码块,指定 Gradle 如何按 ABI 生成 APK。您可以同时提供密度和 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
,将密度列表设置为仅包含ldpi
和xxhdpi
: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 不匹配的设备提供回退机制。
以下示例针对除 ldpi
、xxhdpi
和 xxxhdpi
之外的每种屏幕密度生成了单独的 APK。方法是使用 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 配置多个 APK:
-
enable
(对于 Groovy)或isEnable
(对于 Kotlin 脚本) - 如果将此元素设为
true
,Gradle 会根据您定义的 ABI 生成多个 APK。默认值为false
。 -
exclude
-
以逗号分隔列表的形式指定您不希望 Gradle 针对哪些 ABI 生成单独的 APK。如果您要针对大多数 ABI 生成 APK,但需要排除您的应用不支持的一些 ABI,请使用
exclude
。 -
reset()
-
清除默认的 ABI 列表。只与
include
元素结合使用以指定要添加的 ABI。以下代码段将 ABI 列表设为仅包含
x86
和x86_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 针对哪些 ABI 生成 APK。只与
reset()
结合使用,以指定确切的 ABI 列表。 -
universalApk
(对于 Groovy)或isUniversalApk
(对于 Kotlin 脚本) -
如果设为
true
,那么除了按 ABI 生成的 APK 之外,Gradle 还会生成一个通用 APK。通用 APK 将适用于所有 ABI 的代码和资源包含在一个 APK 中。默认值为false
。请注意,此选项仅在
splits.abi
代码块中可用。根据屏幕密度构建多个 APK 时,Gradle 始终会生成一个通用 APK,其中包含适用于所有屏幕密度的代码和资源。
以下示例针对每个 ABI(即 x86
和 x86_64
)生成了单独的 APK,方法是先使用 reset()
先清空 ABI 列表,再使用 include
再使用 ABI 列表,其中每个 ABI 都会获得一个 APK。
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 面板有两列:Module 和 Active Build Variant,如图 1 所示。
图 1. 对于不含原生/C++ 代码的项目,Build Variants 面板有两列。
模块的 Active Build Variant 值决定了已部署且在编辑器中可见的 build 变体。如需在变体之间切换,请点击模块的 Active Build Variant 单元格,然后从列表字段中选择所需的变体。
含有原生/C++ 代码的项目
对于含有原生/C++ 代码的项目,Build Variants 面板包含三列:Module、Active Build Variant 和 Active ABI,如图 2 所示。
图 2. 对于含有原生/C++ 代码的项目,Build Variants 面板增加了 Active ABI 列。
模块的 Active Build Variant 值决定了已部署并显示在编辑器中的 build 变体。对于原生模块,Active ABI 值决定了编辑器使用的 ABI,但不会影响部署的内容。
如需更改 build 类型或 ABI,请执行以下操作:
- 点击 Active Build Variant 或 Active ABI 列的单元格。
- 从列表字段中选择所需的变体或 ABI。系统会自动运行新的同步。
更改应用或库模块的任一列都会将更改应用于所有相关行。
配置版本控制
默认情况下,当 Gradle 生成多个 APK 时,每个 APK 都有相同的版本信息,该信息在模块级 build.gradle
或 build.gradle.kts
文件中指定。由于 Google Play 商店不允许同一个应用的多个 APK 全都具有相同的版本信息,因此在上传到 Play 商店之前,您需要确保每个 APK 都具有唯一的
versionCode
。
您可以配置模块级 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,将 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.gradle
或 build.gradle.kts
文件配置为构建多个 APK 后,依次点击 Build > Build APK,为 Project 窗格中当前选定的模块构建所有 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 时,除了按密度生成的 APK 之外,Gradle 始终会生成一个包含适用于所有密度的代码和资源的通用 APK。
根据 ABI 构建多个 APK 时,只有在 build.gradle
文件(对于 Groovy)的 splits.abi
代码块中或 build.gradle.kts
文件的 splits.abi
代码块(对于 Kotlin 脚本)的 splits.abi
代码块中,Gradle 才会生成一个包含所有 ABI 的代码和资源的 APK。
universalApk true
isUniversalApk = true
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
-
指定正在构建的 build 变体,如
debug
。
例如,在为调试版本的 myApp 构建 mdpi
屏幕密度 APK 时,APK 文件名为 myApp-mdpi-debug.apk
。对于配置为同时针对 mdpi
屏幕密度和 x86
ABI 构建多个 APK 的 myApp 发布版本,其 APK 文件名为 myApp-mdpiX86-release.apk
。