Play Feature Delivery 概览

Google Play 的应用服务模型使用 Android App Bundle 针对每位用户的设备配置生成并提供经过优化的 APK,因此用户只需下载其运行您的应用所需的代码和资源。

Play Feature Delivery 使用了 app bundle 的多种高级功能,让您可按条件分发或按需下载应用的某些功能。 为此,首先需要将这些功能从基础应用中分离到功能模块中。

功能模块 build 配置

当您使用 Android Studio 创建新的功能模块时,IDE 会对该模块的 build.gradle 文件应用以下 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 配置中的内容

由于每个功能模块都依赖于基本模块,因此它还会继承某些配置。因此,您应该在功能模块的 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 规则会在构建时与其他模块(包括基本模块)的规则合并在一起。因此,尽管每个功能模块都可以指定一组新规则,但这些规则会应用于应用项目中的所有模块。

部署应用

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

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

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

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

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

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

虽然将应用作为 app bundle 上传时,默认即可获得高度优化的下载文件,但如需使用更高级且可自定义的 Feature Delivery 选项,您就必须使用功能模块对应用的功能进行额外的配置和模块化处理。也就是说,功能模块提供了用于创建模块化功能的基块,而您可以将各项功能配置为按需下载。

假设我们有一款让用户可在网络购物平台上买卖商品的应用。您可以合理地将该应用的以下各项功能模块化处理为单独的功能模块:

  • 账号登录与创建
  • 浏览在线购物平台
  • 上架商品
  • 处理付款

下表列出了功能模块支持的不同分发选项,以及如何使用这些选项优化示例购物平台应用的初始下载大小。

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

此外,您的应用可以在之后请求卸载功能。因此,如果您在安装应用时需要某些功能,但之后又不需要了,您可以请求从设备上移除相关功能来减小安装大小。

如果应用包含特定的指导 activity(比如关于如何在购物平台上买卖商品的交互式指南),可以配置为在应用安装时默认包含该功能。

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

使用未配置高级分发选项的功能模块对应用进行模块化处理

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

按需分发 允许您的应用根据需要请求和下载功能模块。 如果在使用购物平台应用的用户中,只有 20% 的人发布待售商品,有一个不错的策略可以减小大多数用户的初始下载大小,那就是将拍照、输入商品描述及上架商品的功能配置为按需下载。也就是说,您可以为应用的销售功能配置功能模块,使该功能仅在用户表现出有兴趣上架商品以在购物平台上待售时才下载。

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

创建功能模块并配置按需分发。然后,应用就可以使用 Play Feature Delivery 库请求按需下载该模块。
根据条件分发 允许您指定特定的用户设备需求(例如硬件特性、区域设置和最低 API 级别),以确定是否在安装应用时下载模块化功能。 如果购物平台应用的用户遍布全球,您可能需要支持仅在特定地区或区域常用的付款方式。为了减小应用的初始下载大小,您可以创建单独的功能模块处理特定类型的支付方式,并将这些模块根据用户的注册区域视条件安装在用户设备上。 创建功能模块并配置按条件分发
免安装分发 Google Play 免安装体验让用户无需在设备上安装应用即可与应用互动。用户可以通过 Google Play 商店中的“立即体验”按钮或您创建的网址体验您的应用。这种内容提供形式可让您更轻松地提高应用的使用率。

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

假设有一款游戏,游戏的前几个关卡包含在一个轻量级功能模块中。您可以启用该模块的免安装体验,这样用户就可以通过网址或“立即体验”按钮免安装体验游戏,而无需安装应用。 创建功能模块并配置免安装分发。然后,应用就可以使用 Play Feature Delivery 库请求按需下载该模块。

请注意,使用功能模块对应用功能进行模块化处理只是第一步。为了支持 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 分析器检查功能模块 APK 并确定软件包名称:

APK 分析器的屏幕截图,图中正在检查已编译资源文件的内容。

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

有关功能模块的注意事项

借助功能模块,您可以提高构建速度和工程工作速度,并广泛地自定义应用功能的分发,以缩减应用的大小。不过,在使用功能模块时,有一些限制和极端情况需要加以注意:

  • 通过按条件分发或按需分发方式在一台设备上安装 50 个或更多功能模块可能会导致性能问题。未配置为可移除的安装时模块会自动包含在基本模块中,并在每台设备上仅算作一个功能模块。
  • 将您为安装时分发配置的可移除模块数量限制为不超过 10 个。否则,应用的下载和安装时间可能会增加。
  • 只有搭载 Android 5.0(API 级别 21)及更高版本的设备才支持按需下载和安装功能。如需使功能适用于更低版本的 Android,请在创建功能模块时启用融合功能
  • 启用 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 会在 settings.gradle 文件中添加 ':features:dynamic_feature1' 作为子项目。构建 app bundle 时,Gradle 会将 <manifest split="dynamic_feature1"> 注入模块的清单。

android:isFeatureSplit="true | false"> 当 Android Studio 构建 app bundle 时,会包含该属性。因此,您不应手动添加或修改此属性

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

<dist:module 这一新的 XML 元素定义了一些属性,这些属性可确定如何打包模块并作为 APK 分发。
dist:instant="true | false" 指定是否应通过 Google Play 免安装体验为模块启用免安装体验。

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

在设置 <dist:on-demand/> 时,不能将此 XML 元素设置为 true。不过,您仍可使用 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)及更低版本的设备的 multi-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>
<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 Feature Delivery

收集的使用情况数据 设备元数据
应用版本
数据收集目的 我们会利用收集的数据来向设备提供合适的模块,以及在更新后及备份和恢复后保留已安装的模块。
数据加密 数据会加密。
数据分享 我们不会将收集的数据发送给任何第三方。
数据删除 我们会在固定保留期限过后删除收集的数据。

虽然我们力求做到尽可能公开透明,但对于 Google Play 的“数据安全”部分针对您的应用的用户数据收集、分享和安全做法提供的表单,您需自行负责决定如何回应。