Play 功能分发概览

Google Play 的应用分发模型使用 Android App Bundle 为每个用户的设备配置生成并分发经过优化的 APK,因此用户只需下载运行应用所需的代码和资源。

Play 功能分发利用 App Bundle 的高级功能,允许有条件地分发或按需下载您应用的某些功能。为此,首先您需要将这些功能从您的基本应用中分离出来,放入功能模块中。

功能模块构建配置

当您使用 Android Studio 创建新的功能模块时,IDE 会将以下 Gradle 插件应用于该模块的 build.gradle 文件。

// The following applies the dynamic-feature plugin to your feature module.
// The plugin includes the Gradle tasks and properties required to configure and build
// an app bundle that includes your feature module.

plugins {
  id 'com.android.dynamic-feature'
}

适用于 标准应用插件 的许多属性也适用于您的功能模块。以下部分介绍了您应该和不应该包含在功能模块构建配置中的属性。

功能模块构建配置中不应包含的内容

因为每个功能模块都依赖于基本模块,所以它也会继承某些配置。因此,您应该在功能模块的 build.gradle 文件中省略以下内容:

  • 签名配置:App Bundle 是使用您在基本模块中指定的签名配置进行签名的。
  • minifyEnabled 属性:您只能通过基本模块的构建配置为您的整个应用项目启用代码缩减。因此,您应该从功能模块中省略此属性。但是,您可以为每个功能模块指定额外的 ProGuard 规则
  • versionCodeversionName:构建 App Bundle 时,Gradle 使用基本模块提供的应用版本信息。您应该从功能模块的 build.gradle 文件中省略这些属性。

建立与基本模块的关系

当 Android Studio 创建您的功能模块时,它通过向基本模块的 build.gradle 文件添加 android.dynamicFeatures 属性,使其对基本模块可见,如下所示:

// In the base module’s build.gradle file.
android {
    ...
    // Specifies feature modules that have a dependency on
    // this base module.
    dynamicFeatures = [":dynamic_feature", ":dynamic_feature2"]
}

此外,Android Studio 还将基本模块作为功能模块的依赖项包含在内,如下所示:

// In the feature module’s build.gradle file:
...
dependencies {
    ...
    // Declares a dependency on the base module, ':app'.
    implementation project(':app')
}

指定额外的 ProGuard 规则

尽管只有基本模块的构建配置才能为您的应用项目启用代码缩减,但您可以使用 proguardFiles 属性为每个功能模块提供自定义 ProGuard 规则,如下所示。

android.buildTypes {
     release {
         // You must use the following property to specify additional ProGuard
         // rules for feature modules.
         proguardFiles 'proguard-rules-dynamic-features.pro'
     }
}

请注意,这些 ProGuard 规则在构建时会与来自其他模块(包括基本模块)的规则合并。因此,虽然每个功能模块都可以指定一组新规则,但这些规则适用于应用项目中的所有模块。

部署您的应用

在开发支持功能模块的应用时,您可以像往常一样将应用部署到连接的设备上,方法是从菜单栏中选择运行 > 运行(或点击工具栏中的运行 )。

如果您的应用项目包含一个或多个功能模块,您可以通过修改现有的运行/调试配置来选择在部署应用时包含哪些功能,具体操作如下:

  1. 从菜单栏中选择运行 > 编辑配置
  2. 运行/调试配置对话框的左侧面板中,选择您想要的 Android 应用配置。
  3. 常规标签页的要部署的动态功能下,选中您要在部署应用时包含的每个功能模块旁边的复选框。
  4. 点击确定

默认情况下,Android Studio 不会使用 App Bundle 来部署您的应用。相反,IDE 会构建并将 APK 安装到您的设备上,这些 APK 针对部署速度进行了优化,而不是 APK 大小。要配置 Android Studio 以从 App Bundle 构建和部署 APK 和免安装体验,请修改您的运行/调试配置

使用功能模块实现自定义分发

功能模块的一个独特优势是能够自定义您的应用的不同功能何时以及如何下载到运行 Android 5.0 (API 级别 21) 或更高版本的设备上。例如,为了减小应用的初始下载大小,您可以将某些功能配置为按需下载,或者仅由支持特定功能(例如拍照能力或支持增强现实功能)的设备下载。

尽管您在以 App Bundle 形式上传应用时默认会获得高度优化的下载,但更高级和可定制的功能分发选项需要使用功能模块对应用的功能进行额外配置和模块化。也就是说,功能模块提供了构建模块化功能的基础,您可以将每个功能配置为按需下载。

考虑一个允许用户在线市场上买卖商品的应用程序。您可以合理地将应用的以下每个功能模块化为单独的功能模块:

  • 账号登录和创建
  • 浏览市场
  • 发布待售商品
  • 处理支付

下表描述了功能模块支持的不同分发选项,以及它们如何用于优化示例市场应用的初始下载大小。

分发选项 行为 示例用例 开始使用
安装时分发 默认情况下,未配置上述任何分发选项的功能模块会在应用安装时下载。这是一个重要的行为,因为这意味着您可以逐步采用高级分发选项。例如,您可以受益于将应用功能模块化,并仅在您使用 Play 功能分发库完全实现按需下载后才启用按需分发。

此外,您的应用可以稍后请求卸载功能。因此,如果您在应用安装时需要某些功能,但之后不再需要,您可以通过请求从设备中移除该功能来减小安装大小。

如果应用包含某些培训活动,例如关于如何在市场中买卖商品的交互式指南,您可以默认在应用安装时包含该功能。

然而,为了减小应用的安装大小,应用可以在用户完成培训后请求删除该功能。

使用不配置任何高级分发选项的功能模块对您的应用进行模块化

要了解如何通过移除用户可能不再需要的某些功能模块来减小应用的安装大小,请阅读管理已安装模块

按需分发 允许您的应用根据需要请求和下载功能模块。 如果只有 20% 的市场应用用户发布待售商品,那么一个减小大多数用户初始下载大小的好策略是,将拍照、添加商品描述和发布待售商品的功能作为按需下载提供。也就是说,您可以配置应用的销售功能模块,使其仅在用户表现出在市场中发布待售商品的兴趣时才下载。

此外,如果用户在一段时间后不再销售商品,应用可以通过请求卸载该功能来减小其安装大小。

创建一个功能模块并配置按需分发。然后,您的应用可以使用 Play 功能分发库来请求按需下载该模块。
条件分发 允许您指定某些用户设备要求,例如硬件功能、语言区域和最低 API 级别,以确定模块化功能是否在应用安装时下载。 如果市场应用具有全球覆盖范围,您可能需要支持仅在某些地区或本地流行的支付方式。为了减小初始应用下载大小,您可以为处理某些类型的支付方式创建单独的功能模块,并根据用户的注册语言区域有条件地将其安装到用户的设备上。 创建一个功能模块并配置条件分发
免安装分发 Google Play 免安装体验允许用户无需在设备上安装应用即可与您的应用互动。相反,他们可以通过 Google Play 商店上的“立即试用”按钮或您创建的 URL 来体验您的应用。这种内容分发形式使您更容易提高与应用的互动。

通过免安装分发,您可以利用 Google Play 免安装体验,让您的用户无需安装即可即时体验您的应用的某些功能。

考虑一个将游戏的前几个关卡包含在一个轻量级功能模块中的游戏。您可以启用该模块的免安装功能,以便用户可以通过 URL 链接或“立即试用”按钮即时体验游戏,而无需安装应用。 创建一个功能模块并配置免安装分发。然后,您的应用可以使用 Play 功能分发库来请求按需下载该模块。

请记住,使用功能模块对您的应用功能进行模块化只是第一步。为了支持 Google Play 免安装体验,您的应用基本模块和给定免安装功能模块的下载大小必须满足严格的大小限制。要了解更多信息,请阅读通过减小应用或游戏大小来启用免安装体验

为资源构建 URI

如果您想使用 URI 访问存储在功能模块中的资源,以下是使用 Uri.Builder() 生成功能模块资源 URI 的方法:

Kotlin

val uri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                .authority(context.getPackageName()) // Look up the resources in the application with its splits loaded
                .appendPath(resources.getResourceTypeName(resId))
                .appendPath(String.format("%s:%s",
                  resources.getResourcePackageName(resId), // Look up the dynamic resource in the split namespace.
                  resources.getResourceEntryName(resId)
                  ))
                .build()

Java

String uri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                .authority(context.getPackageName()) // Look up the resources in the application with its splits loaded
                .appendPath(resources.getResourceTypeName(resId))
                .appendPath(String.format("%s:%s",
                  resources.getResourcePackageName(resId), // Look up the dynamic resource in the split namespace.
                  resources.getResourceEntryName(resId)
                  ))
                .build().toString();

资源路径的每个部分都在运行时构建,确保在加载拆分 APK 后生成正确的命名空间。

以下是 URI 生成方式的示例,假设您有一个应用和功能模块,其名称如下:

  • 应用包名:com.example.my_app_package
  • 功能的资源包名:com.example.my_app_package.my_dynamic_feature

如果上述代码片段中的 resId 指的是功能模块中名为“my_video”的原始文件资源,那么上述 Uri.Builder() 代码将输出以下内容:

android.resource://com.example.my_app_package/raw/com.example.my_app_package.my_dynamic_feature:my_video

然后,您的应用可以使用此 URI 访问功能模块的资源。

要验证 URI 中的路径,您可以使用 APK Analyzer 检查您的功能模块 APK 并确定包名:

A screenshot of the APK Analyzer inspecting the contents of a compiled resource file.
图 1. 使用 APK Analyzer 检查已编译资源文件中的包名。

功能模块的注意事项

借助功能模块,您可以提高构建速度和工程效率,并广泛自定义应用功能的分发,以减小应用大小。但是,在使用功能模块时,需要牢记一些限制和边缘情况:

  • 通过条件分发或按需分发在单个设备上安装 50 个或更多功能模块可能会导致性能问题。未配置为可移除的安装时模块会自动包含在基本模块中,并且在每台设备上仅算作一个功能模块。
  • 将您为安装时分发配置为可移除的模块数量限制在 10 个或更少。否则,您的应用的下载和安装时间可能会增加。
  • 只有运行 Android 5.0 (API 级别 21) 及更高版本的设备才支持按需下载和安装功能。要使您的功能可用于早期版本的 Android,请在创建功能模块时启用 Fusing
  • 启用 SplitCompat,以便您的应用可以访问按需分发已下载的功能模块。
  • 功能模块不应在其清单中指定活动,并将 android:exported 设置为 true。这是因为当另一个应用尝试启动该活动时,无法保证设备已下载该功能模块。此外,您的应用在尝试访问其代码和资源之前,应确认该功能已下载。要了解更多信息,请阅读管理已安装模块
  • 由于 Play 功能分发要求您使用 App Bundle 发布应用,因此请务必了解 App Bundle 的已知问题

功能模块清单参考

当您使用 Android Studio 创建新的功能模块时,IDE 会包含该模块作为功能模块所需的大部分清单属性。此外,一些属性在编译时由构建系统注入,因此您无需自行指定或修改它们。下表描述了对功能模块很重要的清单属性。

属性 说明
<manifest 这是一个典型的 <manifest> 块。
xmlns:dist="http://schemas.android.com/apk/distribution" 指定一个新的 dist: XML 命名空间,该命名空间将在下文进一步描述。
split="split_name" 当 Android Studio 构建您的 App Bundle 时,它会为您包含此属性。因此,您不应自行包含或修改此属性

定义模块的名称,您的应用在使用 Play 功能分发库请求按需模块时会指定此名称。

Gradle 如何确定此属性的值

默认情况下,当您使用 Android Studio 创建功能模块时,IDE 会使用您指定的模块名称,在您的 Gradle 设置文件中将该模块标识为 Gradle 子项目。

当您构建 App Bundle 时,Gradle 使用子项目路径的最后一个元素将此清单属性注入模块的清单中。例如,如果您在 MyAppProject/features/ 目录中创建了一个新的功能模块,并将其模块名称指定为 "dynamic_feature1",则 IDE 会将 ':features:dynamic_feature1' 作为子项目添加到您的 settings.gradle 文件中。构建 App Bundle 时,Gradle 随后会将 <manifest split="dynamic_feature1"> 注入模块的清单中。

android:isFeatureSplit="true | false"> 当 Android Studio 构建您的 App Bundle 时,它会为您包含此属性。因此,您不应手动包含或修改此属性

指定此模块是一个功能模块。基本模块和配置 APK 中的清单要么省略此属性,要么将其设置为 false

<dist:module 定义用于确定模块如何打包和作为 APK 分发的属性。
dist:instant="true | false" 指定模块是否应通过 Google Play 免安装体验作为免安装体验提供。

如果您的应用包含一个或多个已启用免安装功能的功能模块,您还必须启用基本模块的免安装功能。当您使用 Android Studio 3.5 或更高版本时,当您创建支持免安装的功能模块时,IDE 会为您执行此操作。

您不能在将此 XML 元素设置为 true 的同时也将 <dist:on-demand/> 设置为 true。但是,您仍然可以使用 Play 功能分发库作为免安装体验请求按需下载已启用免安装功能的功能模块。当用户下载并安装您的应用时,设备默认会下载并安装您的应用的已启用免安装功能的功能模块以及基本 APK。

dist:title="@string/feature_name"> 指定模块的用户可见标题。例如,设备在请求下载确认时可能会显示此标题。

您需要将此标题的字符串资源包含在基本模块的 module_root/src/source_set/res/values/strings.xml 文件中。

<dist:fusing dist:include="true | false" /> 指定是否将模块包含在针对运行 Android 4.4 (API 级别 20) 及更低版本的设备的多 APK 中。

此外,当您使用 bundletool 从 App Bundle 生成 APK 时,只有将此属性设置为 true 的功能模块才会包含在通用 APK 中——通用 APK 是一个单一的 APK,包含您的应用支持的所有设备配置的代码和资源。

<dist:delivery> 封装自定义模块分发的选项,如下所示。请记住,每个功能模块只能配置其中一种自定义分发选项。
<dist:install-time> 指定模块应在安装时可用。这是未指定其他类型自定义分发选项的功能模块的默认行为。

要了解有关安装时下载的更多信息,请阅读配置安装时分发

此节点还可以指定限制模块仅适用于满足特定要求(例如设备功能、用户国家/地区或最低 API 级别)的设备的条件。要了解更多信息,请阅读配置条件分发

<dist:removable dist:value="true | false" />

当未设置或设置为 false 时,bundletool 将在从 Bundle 生成拆分 APK 时将安装时模块融合到基本模块中。由于融合会减少拆分 APK 的数量,此设置可能会提高您应用的性能。

removable 设置为 true 时:安装时模块将不会融合到基本模块中。如果您将来想要卸载模块,请设置为 true。但是,将太多模块配置为可移除可能会增加您应用的安装时间。

默认为 false。仅当您想为功能模块禁用融合时才需要在清单中设置此值。

注意:此功能仅在使用 Android Gradle 插件 4.2 或从命令行使用 bundletool v1.0 时可用。

</dist:install-time>  
<dist:on-demand /> 指定模块应作为按需下载可用。也就是说,模块在安装时不可用,但您的应用可能会稍后请求下载它。

要了解有关按需下载的更多信息,请阅读配置按需分发

</dist:delivery>
</dist:module>
<application
android:hasCode="true | false">
...
</application>
如果功能模块不生成 DEX 文件(即,它不包含后来编译为 DEX 文件格式的代码),您必须执行以下操作(否则,您可能会遇到运行时错误):
  1. 在功能模块的清单中将 android:hasCode 设置为 "false"
  2. 将以下内容添加到您的基本模块的清单中:
    <application
      android:hasCode="true"
      tools:replace="android:hasCode">
      ...
    </application>
...
</manifest>

其他资源

要了解更多关于使用功能模块的信息,请尝试以下资源。

博客文章

视频

服务条款和数据安全

通过访问或使用 Play 功能分发库,您同意 Play Core 软件开发工具包服务条款。在访问该库之前,请阅读并理解所有适用的条款和政策。

数据安全

Play Core 库是您的应用与 Google Play 商店的运行时接口。因此,当您在应用中使用 Play Core 时,Play 商店会运行自己的进程,其中包括根据Google Play 服务条款管理数据。以下信息描述了 Play Core 库如何处理数据以响应来自您的应用的特定请求。

附加语言 API

使用情况数据收集 已安装语言列表
数据收集目的 收集的数据用于分发不同语言版本的应用,并在应用更新后保留已安装的语言。
数据加密 数据已加密。
数据共享 数据不会传输给任何第三方。
数据删除 数据在固定保留期后删除。

Play 功能分发

使用情况数据收集 设备元数据
应用版本
数据收集目的 收集的数据用于向设备提供正确的模块,并在更新、备份和恢复后保留已安装的模块。
数据加密 数据已加密。
数据共享 数据不会传输给任何第三方。
数据删除 数据在固定保留期后删除。

尽管我们力求尽可能透明,但您全权负责决定如何回应 Google Play 数据安全部分表格中关于您的应用的用户数据收集、共享和安全实践的问题。