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

  • 签名配置:应用捆绑包使用您在基本模块中指定的签名配置进行签名。
  • minifyEnabled 属性:您可以从基本模块的构建配置中 启用代码压缩 以压缩整个应用项目。因此,您应该从功能模块中省略此属性。但是,您可以 为每个功能模块指定其他 ProGuard 规则
  • versionCodeversionName:构建应用捆绑包时,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 规则在构建时会与来自其他模块(包括基本模块)的规则合并。因此,虽然每个功能模块都可能指定一组新的规则,但这些规则适用于应用程序项目中的所有模块。

部署您的应用程序

在使用支持功能模块的应用程序进行开发时,您可以通过选择菜单栏中的 **运行 > 运行**(或点击工具栏中的 **运行** )像往常一样将应用程序部署到已连接的设备。

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

  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 会将 ':features:dynamic_feature1' 添加为 settings.gradle 文件中的子项目。构建应用捆绑包时,Gradle 随后将在模块的清单中注入 <manifest split="dynamic_feature1">

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

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

<dist:module 此新的 XML 元素定义确定模块如何打包和作为 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" />
</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 Feature Delivery 库,您同意 Play Core 软件开发工具包服务条款。在访问该库之前,请阅读并了解所有适用的条款和政策。

数据安全

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

其他语言 API

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

Play 功能交付

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

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