Play Feature Delivery 概览

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

Play Feature Delivery 利用 App Bundle 的高级功能,允许按条件分发或按需下载应用的某些功能。要实现这一点,您首先需要将这些功能从基础应用中分离出来,放入功能模块(feature modules)中。

功能模块构建配置

当您使用 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 创建您的功能模块时,它会将 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 规则会在构建时与来自其他模块(包括基础模块)的规则合并。因此,虽然每个功能模块可以指定一套新规则,但这些规则适用于应用项目中的所有模块。

部署您的应用

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

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

  1. 从菜单栏选择 Run > Edit Configurations
  2. Run/Debug Configurations 对话框的左侧面板中,选择您所需的 Android App 配置。
  3. General 选项卡的 Dynamic features to deploy 下,勾选您想要在部署应用时包含的每个功能模块旁边的复选框。
  4. 点击 **OK**。

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

使用功能模块进行自定义分发

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

虽然将应用上传为 App Bundle 时默认会获得高度优化的下载,但更高级、可自定义的功能分发选项需要额外的配置,并使用功能模块对应用功能进行模块化。也就是说,功能模块为创建模块化功能提供了构建块,您可以将每个模块配置为根据需要下载。

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

  • 账户登录和创建
  • 浏览市场
  • 发布商品进行销售
  • 处理付款

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

分发选项 行为 示例用例 入门指南
安装时分发 未配置上述任何分发选项的功能模块默认在应用安装时下载。这是一个重要的行为,意味着您可以逐步采用高级分发选项。例如,您可以在完全实现使用 Play Feature Delivery 库进行的按需下载后,再从模块化应用功能中受益并启用按需分发。

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

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

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

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

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

按需分发 允许您的应用在需要时请求并下载功能模块。 如果只有 20% 的市场应用用户发布商品销售,那么为大多数用户减小初始下载大小的一个好策略是:将拍照、输入商品描述和发布商品销售的功能作为按需下载项。也就是说,您可以将该应用销售功能的功能模块配置为仅当用户表现出发布商品意愿时才下载。

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

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

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

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

请记住,使用功能模块对应用功能进行模块化只是第一步。要支持 Google Play Instant,应用的模块以及指定的即时启用功能模块的下载大小必须满足严格的大小限制。要了解更多信息,请阅读通过减小应用或游戏大小来启用即时体验

构建资源的 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 分析器来检查您的功能模块 APK 并确定包名:

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

功能模块的注意事项

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

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

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 Instant 作为即时体验提供。

如果您的应用包含一个或多个即时启用功能模块,您也必须对基础模块启用即时功能。使用 Android Studio 3.5 或更高版本时,当您创建即时启用功能模块时,IDE 会为您完成此操作。

您不能在将此 XML 元素设置为 true 的同时又设置 <dist:on-demand/>。但是,您仍然可以在即时体验使用 Play Feature Delivery 库请求对已启用即时功能模块进行按需下载。当用户下载并安装您的应用时,设备会默认与基础 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(Universal 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 Feature Delivery 库,即表示您同意 Play Core 软件开发套件服务条款。请在访问该库之前阅读并理解所有适用的条款和政策。

数据安全

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

额外语言 API

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

Play Feature Delivery

收集的关于使用的相关数据 设备元数据
应用版本
数据收集的目的 收集的数据用于将正确的模块分发到设备,并在更新以及备份和恢复后保留已安装的模块。
数据加密 数据已加密。
数据共享 数据不会传输给任何第三方。
数据删除 数据在固定的保留期后会被删除。

虽然我们力求尽可能透明,但您需要自行负责决定如何填写 Google Play 关于您的应用用户数据收集、共享和安全实践的数据安全部分表格。