Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

编译多个 APK

如果您将应用发布到 Google Play,您应编译并上传 Android App Bundle。当您执行此操作时,Google Play 会针对每位用户的设备配置自动生成并提供经过优化的 APK,因此用户只需下载运行您的应用所需的代码和资源。如果您不将应用发布到 Google Play,那么发布多个 APK 很有用,但您必须自行编译、签署和管理每个 APK。

虽然您应尽可能编译一个 APK 来支持所有目标设备,但由于需要许多文件来支持多种屏幕密度应用二进制接口 (ABI),因此这样做可能会导致生成非常大的 APK。要减小 APK 的大小,一种方法是创建多个 APK,让每个 APK 包含适用于特定屏幕密度或 ABI 的文件。

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

针对多个 APK 配置编译

要针对多个 APK 配置编译,请向您的模块级 build.gradle 文件中添加 splits 代码块。在 splits 代码块内,提供一个 density 代码块,指定 Gradle 应如何按密度生成 APK;或提供一个 abi 代码块,指定 Gradle 应如何按 ABI 生成 APK。您可以同时提供密度和 ABI 代码块,编译系统将针对每个密度和 ABI 组合创建一个 APK。

针对屏幕密度配置多个 APK

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

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

enable
如果将此元素设为 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 we want 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 不匹配的设备提供一种回退机制。

以下示例针对支持的屏幕范围中列出的每种屏幕密度(ldpixxhdpixxxhdpi 除外)生成了单独的 APK,方法是使用 exclude 从包含所有密度的默认列表中移除了三种密度。

    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 Gradle should not 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
如果将此元素设为 true,Gradle 会根据您定义的 ABI 生成多个 APK。默认值为 false
exclude
以逗号分隔列表的形式指定 Gradle 不应针对哪些 ABI 生成单独的 APK。如果您要针对大多数 ABI 生成 APK,但需要排除您的应用不支持的一些 ABI,请使用 exclude
reset()
清空默认的 ABI 列表。只与 include 元素结合使用以指定要添加的 ABI。以下代码段通过先调用 reset() 以清空 ABI 列表,然后再使用 include,将该列表设为仅包含 x86x86_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 应针对哪些 ABI 生成 APK。只与 reset() 结合使用以指定确切的 ABI 列表。
universalApk
如果设为 true,那么除了按 ABI 生成的 APK 之外,Gradle 还会生成一个通用 APK。通用 APK 将适用于所有 ABI 的代码和资源包含在一个 APK 中。默认值为 false。请注意,此选项仅在 splits.abi 代码块中可用。根据屏幕密度编译多个 APK 时,Gradle 始终会生成一个包含适用于所有屏幕密度的代码和资源的通用 APK。

以下示例针对每个 ABI(即 x86x86_64)生成了单独的 APK,方法是先使用 reset() 清空 ABI 列表,然后再使用 include 添加一些 ABI(它们将各自获得一个 APK)。

    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 we only
          // want APKs for x86 and x86_64.

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

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

          // Specifies that we do not want to also generate a universal APK that includes all ABIs.
          universalApk false
        }
      }
    }
    

如需查看支持的 ABI 的列表,请参阅支持的 ABI

mips、mips64 和 armeabi

Android Plugin for Gradle 3.1.0 及更高版本在默认情况下不再针对以下 ABI 生成 APK:mipsmips64armeabi。这是因为,NDK r17 及更高版本不再将这些 ABI 作为支持的目标。

建议您先在 Google Play 管理中心查看,验证是否有用户下载以这些 ABI 为目标的应用 APK。如果没有,您可能希望从编译中省略此类 APK。如果您要继续编译以这些 ABI 为目标的 APK,则必须使用 NDK r16b 或更低版本,并在 build.gradle 文件中指定相应 ABI,具体代码如下所示:

    splits {
        abi {
            include 'armeabi', 'mips', 'mips64'
            ...
        }
    }
    

已知问题:如果您将 Android Plugin for Gradle 3.0.1 或更低版本与 NDK r17 或更高版本一起使用,可能会收到以下错误:Error:ABIs [mips64, armeabi, mips] are not supported for platform.。这是因为,当您按 ABI 编译 APK 时,旧版插件在默认情况下仍包含不受支持的 ABI。要解决此问题,请更新到最新版本的插件,或者在应用的 build.gradle 文件中重置插件的默认 ABI 列表,使其仅包含您需要的受支持的 ABI,具体代码如下所示:

    ...
    splits {
        abi {
            ...
            reset()
            include "x86", "armeabi-v7a", "arm64-v8a", "x86_64"
        }
    }
    

配置版本控制

默认情况下,当 Gradle 生成多个 APK 时,每个 APK 都有相同的版本信息,该信息在模块级 build.gradle 文件中指定。由于 Google Play 商店不允许同一个应用的多个 APK 全都具有相同的版本信息,因此在上传到 Play 商店之前,您需要确保每个 APK 都有自己唯一的 versionCode

您可以配置模块级 build.gradle 文件以替换每个 APK 的 versionCode。通过创建一种映射关系来为您配置了多 APK 编译的每种 ABI 和密度分配一个唯一的数值,您可以将输出版本代码替换为一个将在 defaultConfigproductFlavors 代码块中定义的版本代码与分配给相应密度或 ABI 的数值组合在一起的值。

在以下示例中,x86 ABI 的 APK 的 versionCode 将为 2004,x86_64 ABI 的 APK 的版本代码将为 3004。如果以较大的增量(如 1000)分配版本代码,那么当您以后需要更新应用时,就可以分配唯一的版本代码。例如,如果 defaultConfig.versionCode 在后续更新中迭代到 5,那么 Gradle 为 x86 APK 分配的 versionCode 将为 2005,为 x86_64 APK 分配的版本代码将为 3005。

提示:如果您的编译包含一个通用 APK,您为其分配的 versionCode 应低于为其他任何 APK 分配的版本代码。由于 Google Play 商店会安装既与目标设备兼容又具有最高 versionCode 的应用版本,因此为通用 APK 分配一个较低的 versionCode 可以确保 Google Play 商店在回退到通用 APK 之前先尝试安装您的某个 APK。以下示例代码的处理方式是不替换通用 APK 的默认 versionCode

    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 like this:
    // 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 does not override the version code for universal APKs.
        // However, because we 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 simply
          // causes Gradle to use the value of variant.versionCode for the APK.
          output.versionCodeOverride =
                  baseAbiVersionCode * 1000 + variant.versionCode
        }
      }
    }
    

如需查看备用版本代码方案的更多示例,请参阅分配版本代码

编译多个 APK

将模块级 build.gradle 文件配置为编译多个 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 编译。

    ...
      splits {
        density {
          enable true
          reset()
          include "mdpi", "hdpi"
        }
        abi {
          enable 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 文件的 splits.abi 代码块中指定了 universalApk 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”,APK 文件名为 myApp-mdpiX86-release.apk