Play 功能交付概述

Google Play 的应用服务模型使用 Android 应用包 为每个用户的设备配置生成和提供优化的 APK,因此用户只需下载运行您的应用所需的代码和资源。

Play 功能交付利用应用包的高级功能,允许有条件地交付或按需下载应用的某些功能。为此,您首先需要将这些功能从基本应用中分离到功能模块中。

功能模块构建配置

当您使用 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 文件中省略以下内容

建立与基本模块的关系

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

// 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 不会使用应用捆绑包部署您的应用。相反,IDE 会构建并安装到您的设备上的 APK,这些 APK 针对部署速度进行了优化,而不是 APK 大小。要配置 Android Studio 改为从应用捆绑包构建和部署 APK 和即时体验,请修改您的运行/调试配置

使用功能模块进行自定义交付

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

虽然将应用上传为应用捆绑包时,默认情况下会获得高度优化的下载,但更高级和可自定义的功能交付选项需要使用功能模块对应用的功能进行额外的配置和模块化。也就是说,功能模块提供了创建模块化功能的基础,您可以将每个功能配置为根据需要下载。

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

  • 帐户登录和创建
  • 浏览市场
  • 发布商品出售
  • 处理付款

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

交付选项 行为 示例用例 入门
安装时交付 默认情况下,未配置上述任何交付选项的功能模块在应用安装时下载。这是一种重要的行为,因为它意味着您可以逐步采用高级交付选项。例如,您可以从模块化应用的功能中获益,并且仅在完全使用 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 后生成正确的命名空间。

例如,假设您有一个应用和具有以下名称的功能模块

  • 应用包名: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 分析器检查您的功能模块 APK 并确定包名

A screenshot of the APK Analyzer inspecting the contents of a compiled resource file.

图 2. 使用 APK 分析器检查已编译资源文件中的包名。

功能模块的注意事项

使用功能模块,您可以提高构建速度和工程速度,并广泛自定义应用功能的交付以减小应用的大小。但是,在使用功能模块时,需要牢记一些约束和边缘情况

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

功能模块清单参考

使用 Android Studio 创建新的功能模块时,IDE 包含模块正常运行所需的大多数清单属性。此外,某些属性由构建系统在编译时注入,因此您无需自己指定或修改它们。下表描述了对功能模块很重要的清单属性。

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

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

Gradle 如何确定此属性的值

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

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

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

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

<dist:module 此新的 XML 元素定义确定模块如何打包和分发为 APK 的属性。
dist:instant="true | false" 指定模块是否应通过Google Play 即时体验作为即时体验提供。

如果您的应用包含一个或多个支持即时体验的功能模块,则还必须启用基本模块的即时体验功能。使用 Android Studio 3.5 或更高版本时,在您创建支持即时体验的功能模块时,IDE 会自动为您执行此操作。

您不能将此 XML 元素设置为true,同时还设置<dist:on-demand/>。但是,您仍然可以作为即时体验请求对支持即时体验的功能模块进行按需下载使用 Play 功能交付库。当用户下载并安装您的应用时,设备会默认下载并安装应用的支持即时体验的功能模块以及基本 APK。

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

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

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

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

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

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

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

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

如果未设置或设置为false,则 bundletool 在从包生成拆分 APK 时会将安装时模块融合到基本模块中。由于融合后拆分 APK 会减少,因此此设置可能会提高应用的性能。

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

默认为false。只有当您希望禁用功能模块的融合时,才需要在清单中设置此值。

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

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

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

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

其他资源

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

博文

视频

服务条款和数据安全

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

数据安全

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

其他语言 API

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

Play Feature Delivery

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

虽然我们力求尽可能透明,但您全权负责决定如何响应 Google Play 的数据安全部分表单,该表单涉及您应用的用户数据收集、共享和安全实践。