适用于设备端 AI 的 Play(测试版)

简介

适用于设备端 AI 的 Play 将 Android App Bundle 和 Google Play 交付的优势带到自定义 ML 模型分发中,这样您就可以在不增加额外成本的情况下,以更低的设备生态系统复杂性来提高模型性能。它允许您将包含代码、资源和 ML 模型的单个工件发布到 Play,并从多种交付模式和定位选项中进行选择。

优势

  • 将单个发布工件上传到 Google Play,并将托管、交付、更新和定位委托给 Play,无需额外费用。
  • 在安装时、快速跟踪或按需交付您的 ML 模型。
    • 安装时交付可以确保当您的应用打开时,存在一个非常大的模型。您的模型将作为 APK 进行安装。
    • 快速跟踪交付会在您的应用安装后自动在后台进行。用户可能在模型完全下载之前打开您的应用。您的模型将被下载到您应用的内部存储空间。
    • 按需交付允许您在运行时请求模型,这在模型仅在特定用户流程中需要时非常有用。您的模型将被下载到您应用的内部存储空间。
  • 根据设备型号、系统属性或 RAM 交付针对特定设备的 ML 模型变体。
  • 通过 Play 的自动修补功能,保持应用更新的小巧和优化,这意味着只需下载文件中的差异部分。

注意事项

  • 使用适用于设备端 AI 的 Play 即表示您同意 Google Play 开发者分发协议Play Core 软件开发工具包服务条款中的条款。
  • 适用于设备端 AI 的 Play 下载的模型只能由您的应用使用。模型不应提供给其他应用。
  • 单个 AI 包的最大压缩下载大小为 1.5GB。从您的应用包生成的任何版本应用的累积最大大小为 4GB。
  • 大小超过 1GB 的应用必须将最低 SDK 级别设置为 21 或更高。

如何使用适用于设备端 AI 的 Play

适用于设备端 AI 的 Play 使用 AI 包。您将准备好分发的自定义模型打包到应用包中的 AI 包中。您可以选择 AI 包是在安装时、快速跟踪还是按需交付。

通过将 AI 包与您的应用包打包在一起,您可以使用 Play 现有的所有测试和发布工具,例如测试轨道和分阶段发布,以通过您的自定义模型管理应用分发。

AI 包与应用二进制文件一起更新。如果您的新应用发布未对 AI 包进行更改,那么 Play 的自动修补过程将确保用户无需重新下载。Play 只会在更新应用时下载更改的部分。

AI 包仅包含模型。不允许包含 Java/Kotlin 和原生库。如果您需要随附库或代码来运行您的 ML 模型,请将其移至基本模块或功能模块中。您可以配置您的功能模块,使其具有与 AI 包相同的下载和定位设置。

将 LiteRT 和 MediaPipe 与 AI 包结合使用

您可以将 LiteRT 和 MediaPipe 与 AI 包结合使用。将您的模型打包到 AI 包中,然后按照安装时包快速跟踪和按需包的说明进行访问。

进一步阅读

AI 包入门

总的来说,以下是您如何开始使用适用于设备端 AI 的 Play 的方法

  1. 将您的模型打包到 Android App Bundle 中的 AI 包中,并指定 AI 包的交付方式。
  2. 【可选】如果您想将不同的模型交付给不同的设备,您可以为 AI 包配置设备定位。例如,您可以将 AI 包 A 交付给特定设备型号,将 AI 包 B 交付给 RAM 至少为 6GB 的设备,而所有其他设备则不接收模型。
  3. 【可选】如果您使用按需或快速跟踪交付,请将 Play AI Delivery Library 集成到您的应用中,以便按需下载 AI 包。
  4. 测试并将您的应用包发布到 Google Play。

检查 Android Gradle 插件版本

要使用 AI 包,请确保您的 Android Gradle 插件 (AGP) 版本至少为 8.8。此版本随附于 Android Studio Ladybug 2。

将您的模型提取到 AI 包中

以下步骤不需要 Android Studio。

  1. 在您的项目顶层目录中,为 AI 包创建一个目录。此目录名称用作 AI 包名称。AI 包名称必须以字母开头,且只能包含字母、数字和下划线。
  2. 在 AI 包目录中,创建 build.gradle 文件并添加以下代码。确保指定 AI 包的名称和一种交付类型。

    // In the AI pack's build.gradle file:
    plugins {
      id 'com.android.ai-pack'
    }
    
    aiPack {
        packName = "ai-pack-name" // Directory name for the AI pack
        dynamicDelivery {
            deliveryType = "[ install-time | fast-follow | on-demand ]"
        }
    }
    
  3. 在项目的应用 build.gradle 文件中,添加项目中每个 AI 包的名称,如下所示:

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":ai-pack-name", ":ai-pack2-name"]
    }
    
  4. 在项目的 settings.gradle 文件中,包含项目中所有的 AI 包,如下所示:

    // In the settings.gradle file:
    include ':app'
    include ':ai-pack-name'
    include ':ai-pack2-name'
    
  5. 在您的 AI 包内部,创建一个 src/main/assets/ 目录。

  6. 将您的模型放在 src/main/assets 目录中。您也可以在此处创建子目录。现在您的应用目录结构应如下所示:

    • build.gradle
    • settings.gradle
    • app/
    • ai-pack-name/build.gradle
    • ai-pack-name/src/main/assets/your-model-directories
  7. 添加代码以加载和运行您的模型。具体操作取决于您的 AI 包的交付模式。请参阅下文安装时快速跟踪/按需的说明。

  8. 【可选】配置设备定位以将不同的模型交付给不同的设备。

  9. 使用 Gradle 构建 Android App Bundle。在生成的应用包中,根级别目录现在包含以下内容:

    • ai-pack-name/manifest/AndroidManifest.xml:配置 AI 包的标识符和交付模式
    • ai-pack-name/assets/your-model-directories:包含作为 AI 包一部分交付的所有资源的目录

    Gradle 会为每个 AI 包生成清单文件,并为您输出 assets/ 目录。

配置安装时交付

配置为安装时交付的 AI 包在应用启动时立即可用。使用 Java AssetManager API 访问以此模式提供的 AI 包:

import android.content.res.AssetManager;
...
Context context = createPackageContext("com.example.app", 0);
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("model-name");

配置快速跟踪和按需交付

要通过快速跟踪或按需交付下载 AI 包,请使用 Play AI Delivery Library。

声明对 Play AI Delivery Library 的依赖

在您应用的 build.gradle 文件中,声明对 Play AI Delivery Library 的依赖:

dependencies {
  ...
  implementation "com.google.android.play:ai-delivery:0.1.1-alpha01"
}

检查状态

每个 AI 包都存储在应用内部存储空间中的一个单独文件夹中。使用 getPackLocation() 方法确定 AI 包的根文件夹。此方法返回以下值:

返回值 状态
有效的 AiPackLocation 对象 AI 包根文件夹已准备就绪,可在 assetsPath() 处立即访问
null 未知的 AI 包或 AI 包不可用

获取有关 AI 包的下载信息

使用
getPackStates() 方法来确定下载大小以及包是否已在下载。

Task<AiPackStates> getPackStates(List<String> packNames)

getPackStates() 是一个异步方法,它返回一个 TaskAiPackStates 对象的 packStates() 方法返回一个 Map。此映射包含每个请求的 AI 包的状态,以其名称作为键:

Map<String, AiPackState> AiPackStates#packStates()

最终请求如下所示:

final String aiPackName = "myAiPackName";

aiPackManager
    .getPackStates(Collections.singletonList(aiPackName))
    .addOnCompleteListener(new OnCompleteListener<AiPackStates>() {
        @Override
        public void onComplete(Task<AiPackStates> task) {
            AiPackStates aiPackStates;
            try {
                aiPackStates = task.getResult();
                AiPackState aiPackState =
                    aiPackStates.packStates().get(aiPackName);
            } catch (RuntimeExecutionException e) {
                Log.d("MainActivity", e.getMessage());
                return;
            });

以下 AiPackState 方法提供 AI 包的大小、迄今为止已下载的数量(如果已请求)以及已传输到应用的数量:

要获取 AI 包的状态,请使用 status() 方法,该方法返回一个整数状态,对应于 AiPackStatus 类中的一个常量字段。尚未安装的 AI 包的状态为 AiPackStatus.NOT_INSTALLED

如果请求失败,请使用 errorCode() 方法,其返回值对应于 AiPackErrorCode 类中的一个常量字段。

安装

使用 fetch() 方法首次下载 AI 包,或调用以完成 AI 包的更新。

Task<AiPackStates> fetch(List<String> packNames)

此方法返回一个 AiPackStates 对象,其中包含包列表及其初始下载状态和大小。如果通过 fetch() 请求的 AI 包已在下载中,则返回下载状态,并且不会启动额外的下载。

监控下载状态

您应该实现一个 AiPackStateUpdateListener 来跟踪 AI 包的安装进度。状态更新按包细分,以支持跟踪单个 AI 包的状态。您可以在请求的所有其他下载完成之前开始使用可用的 AI 包。

void registerListener(AiPackStateUpdateListener listener)
void unregisterListener(AiPackStateUpdateListener listener)
大文件下载

如果下载文件大于 200MB 且用户未连接 Wi-Fi,则下载不会开始,直到用户明确同意使用移动数据连接继续下载。同样,如果下载文件很大且用户失去 Wi-Fi 连接,则下载会暂停,需要明确同意才能使用移动数据连接继续。暂停的包状态为 WAITING_FOR_WIFI。要触发 UI 流程以提示用户同意,请使用 showConfirmationDialog() 方法。

请注意,如果应用不调用此方法,下载将暂停,并且只有当用户重新连接 Wi-Fi 时才会自动恢复。

需要用户确认

如果一个包的状态为 REQUIRES_USER_CONFIRMATION,则下载不会继续,直到用户接受通过 showConfirmationDialog() 显示的对话框。当 Play 不识别应用时(例如,如果应用是通过旁加载安装的),可能会出现此状态。请注意,在这种情况下调用 showConfirmationDialog() 将导致应用更新。更新后,您将需要再次请求 AI 包。

以下是监听器的示例实现:

AiPackStateUpdateListener aiPackStateUpdateListener = new AiPackStateUpdateListener() {
    private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =
      registerForActivityResult(
          new ActivityResultContracts.StartIntentSenderForResult(),
          new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == RESULT_OK) {
                Log.d(TAG, "Confirmation dialog has been accepted.");
              } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.d(TAG, "Confirmation dialog has been denied by the user.");
              }
            }
          });

    @Override
    public void onStateUpdate(AiPackState aiPackState) {
      switch (aiPackState.status()) {
        case AiPackStatus.PENDING:
          Log.i(TAG, "Pending");
          break;

        case AiPackStatus.DOWNLOADING:
          long downloaded = aiPackState.bytesDownloaded();
          long totalSize = aiPackState.totalBytesToDownload();
          double percent = 100.0 * downloaded / totalSize;

          Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
          break;

        case AiPackStatus.TRANSFERRING:
          // 100% downloaded and assets are being transferred.
          // Notify user to wait until transfer is complete.
          break;

        case AiPackStatus.COMPLETED:
          // AI pack is ready to use. Run the model.
          break;

        case AiPackStatus.FAILED:
          // Request failed. Notify user.
          Log.e(TAG, aiPackState.errorCode());
          break;

        case AiPackStatus.CANCELED:
          // Request canceled. Notify user.
          break;

        case AiPackStatus.WAITING_FOR_WIFI:
        case AiPackStatus.REQUIRES_USER_CONFIRMATION:
          if (!confirmationDialogShown) {
            aiPackManager.showConfirmationDialog(activityResultLauncher);
            confirmationDialogShown = true;
          }
          break;

        case AiPackStatus.NOT_INSTALLED:
          // AI pack is not downloaded yet.
          break;
        case AiPackStatus.UNKNOWN:
          Log.wtf(TAG, "AI pack status unknown")
          break;
      }
    }
}

或者,您可以使用 getPackStates() 方法获取当前下载的状态。AiPackStates 包含下载进度、下载状态和任何失败错误代码。

访问 AI 包

下载请求达到 COMPLETED 状态后,您可以使用文件系统调用访问 AI 包。使用 getPackLocation() 方法获取 AI 包的根文件夹。

AI 包存储在 AI 包根目录内的 assets 目录中。您可以使用便捷方法 assetsPath() 获取 assets 目录的路径。使用以下方法获取特定资源的路径:

private String getAbsoluteAiAssetPath(String aiPack, String relativeAiAssetPath) {
    AiPackLocation aiPackPath = aiPackManager.getPackLocation(aiPack);

    if (aiPackPath == null) {
        // AI pack is not ready
        return null;
    }

    String aiAssetsFolderPath = aiPackPath.assetsPath();
    // equivalent to: FilenameUtils.concat(aiPackPath.path(), "assets");
    String aiAssetPath = FilenameUtils.concat(aiAssetsFolderPath, relativeAiAssetPath);
    return aiAssetPath;
}

配置设备定位

您可以按照设备定位说明来指定应接收您的 AI 包的设备或设备组。

其他 Play AI Delivery API 方法

以下是一些您可能希望在应用中使用的其他 API 方法。

取消请求

使用 cancel() 取消活动的 AI 包请求。请注意,此请求是尽力而为的操作。

移除 AI 包

使用 removePack() 安排移除 AI 包。

获取多个 AI 包的位置

使用 getPackLocations() 批量查询多个 AI 包的状态,该方法返回一个 AI 包及其位置的映射。由 getPackLocations() 返回的映射包含每个当前已下载并已更新的包的条目。

设备定位

设备定位让您能够更精细地控制应用包的哪些部分交付到特定设备。例如,您可以确保大型模型仅交付给具有高 RAM 的设备,或者您可以将模型的不同版本交付给不同设备。

您可以定位设备属性,例如:

所需步骤概览

启用设备定位需要执行以下步骤:

  1. 在 XML 文件中定义您的设备组。
  2. 指定您的包的哪些部分应交付给哪些设备组。
  3. 【可选】在本地测试您的配置。
  4. 将您的包(包含 XML 文件)上传到 Google Play。

检查 Android Gradle 插件版本

要使用设备定位,请确保您的 Android Gradle 插件 (AGP) 版本至少为 8.10.0。此版本随附于 Android Studio(Meerkat 2 及更高版本)。下载最新稳定版 Android Studio

在 Android Gradle 插件中启用此功能

必须在您的 gradle.properties 文件中明确启用设备定位:

android.experimental.enableDeviceTargetingConfigApi=true

创建设备定位配置 XML 文件

设备定位配置文件是一个 XML 文件,您可以在其中定义您的自定义设备组。例如,您可以定义一个名为 qti_v79 的设备组,其中包含所有配备 Qualcomm SM8750 片上系统的设备:

<config:device-targeting-config
    xmlns:config="http://schemas.android.com/apk/config">

    <config:device-group name="qti_v79">
        <config:device-selector>
            <config:system-on-chip manufacturer="QTI" model="SM8750"/>
        </config:device-selector>
    </config:device-group>

</config:device-targeting-config>

一个设备组由最多 5 个设备选择器组成。如果设备满足其任何设备选择器,则该设备将包含在设备组中。

一个设备选择器可以有一个或多个设备属性。如果设备匹配选择器的所有设备属性,则选择该设备。

如果设备匹配多个组,它将接收 XML 文件中首先定义的组的内容。您在 XML 文件中定义组的顺序即为您的优先级顺序。

如果设备不匹配任何组,它将接收默认的“其他”组。此组是自动生成的,不应显式定义。

可用设备属性

  • device_ram:设备 RAM 要求
    • min_bytes (包含):最低所需 RAM(以字节为单位)
    • max_bytes (不包含):最高所需 RAM(以字节为单位)
  • included_device_ids:要包含在此选择器中的设备型号(每个组最多 10000 个 device_id)。如果设备与列表中的任何 device_id 匹配,则此属性得到满足。
    • build_brand:设备制造商
    • build_device:设备型号代码
  • excluded_device_ids:要从此选择器中排除的设备型号(每个组最多 10000 个 device_id)。如果设备与列表中的任何 device_id 都不匹配,则此属性得到满足。
    • build_brand:设备制造商
    • build_device:设备型号代码
  • required_system_features:设备需要具备才能包含在此选择器中的特性(每个组最多 100 个特性)。设备需要具备此列表中的所有系统特性才能满足此属性。

    系统特性参考

    • name:系统特性
  • forbidden_system_features:设备不得具备才能包含在此选择器中的特性(每个组最多 100 个特性)。如果设备具备此列表中的任何系统特性,则不满足此属性。

    系统特性参考

    • name:系统特性
  • system-on-chip:要包含在此选择器中的片上系统。设备需要具备此列表中的任何芯片才能满足此属性。

以下是显示所有可能设备属性的示例:

<config:device-targeting-config
    xmlns:config="http://schemas.android.com/apk/config">

    <config:device-group name="myCustomGroup1">
      <config:device-selector ram-min-bytes="8000000000">
        <config:included-device-id brand="google" device="redfin"/>
        <config:included-device-id brand="google" device="sailfish"/>
        <config:included-device-id brand="good-brand"/>
        <config:excluded-device-id brand="google" device="caiman"/>
        <config:system-on-chip manufacturer="Sinclair" model="ZX80"/>
        <config:system-on-chip manufacturer="Commodore" model="C64"/>
      </config:device-selector>
      <config:device-selector ram-min-bytes="16000000000"/>
    </config:device-group>

    <config:device-group name="myCustomGroup2">
      <config:device-selector ram-min-bytes="4000000000" ram-max-bytes="8000000000">
        <config:required-system-feature name="android.hardware.bluetooth"/>
        <config:required-system-feature name="android.hardware.location"/>
        <config:forbidden-system-feature name="android.hardware.camera"/>
        <config:forbidden-system-feature name="mindcontrol.laser"/>
      </config:device-selector>
    </config:device-group>

</config:device-targeting-config>

官方设备制造商和设备型号代码

您可以通过 Google Play 管理中心内的设备目录找到设备制造商和型号代码的正确格式,方法如下:

  • 使用设备目录检查单个设备,并在如下示例所示的位置查找制造商和型号代码(对于 Google Pixel 4a,制造商是“Google”,型号代码是“sunfish”)

    pixel 4a page in the device catalog

    pixel 4a page in the device catalog

  • 下载支持设备的 CSV 文件,并分别使用制造商型号代码作为 build_brandbuild_device 字段。

在您的应用包中包含设备定位配置文件

将以下内容添加到您主模块的 build.gradle 文件中:

android {
  ...
  bundle {
    deviceTargetingConfig = file('device_targeting_config.xml')
    deviceGroup {
      enableSplit = true   // split bundle by #group
      defaultGroup = "other"  // group used for standalone APKs
    }
  }
  ...
}

device_targeting_config.xml 是您的配置文件相对于主模块的路径。这可确保您的配置文件与您的应用包一起打包。

deviceGroup 子句确保从您的包生成的 APK 会按设备组进行拆分。

为您的 AI 包使用设备定位

您可以通过仅将大型模型交付给能够运行它们的设备来优化设备上的大小。

通过设备组细分您的 AI 包,方法是使用上一步中创建的现有 AI 包目录,并为相应的文件夹(如下所述)添加 #group_myCustomGroup1、#group_myCustomGroup2 等后缀。在应用中使用 AI 包时,您无需通过后缀来寻址文件夹(换句话说,后缀会在构建过程中自动剥离)。

执行上一步后,可能如下所示:

...
.../ai-pack-name/src/main/assets/image-classifier#group_myCustomGroup1/
.../ai-pack-name/src/main/assets/image-classifier#group_myCustomGroup2/
...

在此示例中,您将引用不带任何后缀的 ai-pack-name/assets/image-classifier/

myCustomGroup1 中的设备将接收 image-classifier#group_myCustomGroup1/ 下的所有资源,而 myCustomGroup2 中的设备将接收 image-classifier#group_myCustomGroup2/ 下的所有资源。

不属于 myCustomGroup1myCustomGroup2 的设备将收到一个空的 ai-pack-name 包。

这是因为不匹配任何设备组的设备将收到您的 AI 包的默认变体。这包括任何不在带有 #group_suffix 的目录中的内容。

下载 AI 包后,您可以使用AssetManager(用于安装时包)或AiPackManager(用于快速跟踪和按需包)检查模型是否存在。在示例应用中展示了所有交付模式的示例。

为您的功能模块使用设备定位

您也可以为功能模块使用设备定位。您不是按设备组细分功能模块,而是根据设备组的成员资格指定是否应交付整个模块。

要将功能模块交付给属于 myCustomGroup1myCustomGroup2 的设备,请修改其 AndroidManifest.xml

<manifest ...>
  ...
  <dist:module dist:title="...">
    <dist:delivery>
      <dist:install-time>
        <dist:conditions>
          <dist:device-groups>
            <dist:device-group dist:name="myCustomGroup1"/>
            <dist:device-group dist:name="myCustomGroup2"/>
          </dist:device-groups>
          ...
        </dist:conditions>
      </dist:install-time>
    </dist:delivery>
  </dist:module>
  ...
</manifest>

本地测试

在为您的新包创建发布之前,您可以使用内部应用共享或 Bundletool 进行本地测试。

内部应用共享

内部应用共享允许您使用应用包快速生成一个 URL,您可以在本地设备上点击该 URL,以安装 Google Play 为该设备安装的内容,就像该版本应用已在测试或生产轨道上一样。

请查看内部应用共享说明

Bundletool

或者,您可以使用 bundletool(1.18.0 或更高版本)生成 APK 并将其旁加载到您的设备上。按照以下步骤使用 bundletool 在本地测试您的应用:

  1. 使用 Android Studio 或 bundletool 构建您的应用包。

  2. 使用 --local-testing 标志生成 APK:

    java -jar bundletool-all.jar build-apks --bundle=path/to/your/bundle.aab \
      --output=output.apks --local-testing
    
  3. 连接设备并运行 bundletool 旁加载 APK:

    # Example without Device Targeting Configuration
    java -jar bundletool.jar install-apks --apks=output.apks
    
    # Example with Device Targeting Configuration (you must specify which groups the connected device belongs to)
    java -jar bundletool.jar install-apks --apks=output.apks --device-groups=myCustomGroup1,myCustomGroup2
    

使用 bundletool 进行本地测试的限制

以下是使用 bundletool 进行本地测试的限制:

  • fast-follow 包的行为与 on-demand 包类似。也就是说,当应用被旁加载时,它们不会自动获取。开发者需要在应用启动时手动请求它们;这不需要对您的应用进行任何代码更改。
  • 包是从外部存储而不是 Play 获取的,因此您无法测试代码在网络错误情况下的行为。
  • 本地测试不包括等待 Wi-Fi 的情况。
  • 不支持更新。在安装新版本的构建之前,请手动卸载旧版本。

验证是否正在安装正确的 APK

使用以下方法确保设备上仅安装正确的 APK:

adb shell pm path {packageName}

您应该会看到类似以下内容:

package:{...}/base.apk
package:{...}/split_config.en.apk
package:{...}/split_config.xxhdpi.apk
package:{...}/split_main_ai-pack-name.apk
package:{...}/split_main_ai-pack-name.config.group_myCustomGroup1.apk

请注意,您在此列表中只会看到由功能模块和安装时 AI 包生成的 APK。按需和快速跟踪 AI 包不作为 APK 安装。

在 Google Play 上测试和发布

我们建议您使用内部测试轨道在 Google Play 上对您的应用进行端到端测试。

完成此操作后,您可以通过分阶段发布逐步将您的应用更新发布到生产环境。

使用适用于设备端 AI 的 Play 的示例应用

下载示例应用

它演示了如何使用每种交付模式以及设备定位配置。请参阅本地测试部分以开始使用。

了解更多关于 Android App Bundle 的信息,并阅读 AI Delivery SDK 的参考资料。