集成 Asset Delivery(原生)

您可以按照本指南中的步骤,从 C 和 C++ 代码获取应用的资源包。

GitHub 上提供了集成代码示例。

针对原生代码构建

您可以按照以下步骤将 Play Asset Delivery 内置到项目的 Android App Bundle 中。您无需使用 Android Studio 即可执行这些步骤。

  1. 在项目的 build.gradle 文件中将 Android Gradle 插件的版本更新为 4.0.0 或更高版本。

  2. 在项目的顶级目录中,为资源包创建一个目录。此目录名称将用作资源包名称。资源包名称必须以字母开头,并且只能包含字母、数字和下划线。

  3. 在资源包目录中,创建一个 build.gradle 文件并添加以下代码。请务必指定资源包的名称,并且仅指定一种分发类型:

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

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":asset-pack-name", ":asset-pack2-name"]
    }
  5. 在项目的 settings.gradle 文件中,添加项目中的所有资源包,如下所示:

    // In the settings.gradle file:
    include ':app'
    include ':asset-pack-name'
    include ':asset-pack2-name'
  6. 在资源包目录中,创建以下子目录:src/main/assets

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

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. 使用 Gradle 构建 Android App Bundle。在生成的 app bundle 中,根级目录现在包含以下内容:

    • asset-pack-name/manifest/AndroidManifest.xml:此目录用于配置资源包的标识符和分发模式
    • asset-pack-name/assets/your-asset-directories:此目录包含作为资源包的一部分分发的所有资源

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

  9. (可选)配置 app bundle 以支持不同的纹理压缩格式

与 Play Asset Delivery 库集成

您可以根据希望获取的资源包的分发类型来实现此 API。具体步骤如以下流程图所示。

针对原生代码的资源包流程图

图 1. 获取资源包的流程图

Play Core 原生 SDK 提供了用于请求资源包、管理下载内容以及获取资源的 C 头文件 play/asset_pack.h

为 Play Core 原生 SDK 设置开发环境

Download Play Core Native SDK

Before downloading, you must agree to the following terms and conditions.

Terms and Conditions

Last modified: September 24, 2020
  1. By using the Play Core Software Development Kit, you agree to these terms in addition to the Google APIs Terms of Service ("API ToS"). If these terms are ever in conflict, these terms will take precedence over the API ToS. Please read these terms and the API ToS carefully.
  2. For purposes of these terms, "APIs" means Google's APIs, other developer services, and associated software, including any Redistributable Code.
  3. “Redistributable Code” means Google-provided object code or header files that call the APIs.
  4. Subject to these terms and the terms of the API ToS, you may copy and distribute Redistributable Code solely for inclusion as part of your API Client. Google and its licensors own all right, title and interest, including any and all intellectual property and other proprietary rights, in and to Redistributable Code. You will not modify, translate, or create derivative works of Redistributable Code.
  5. Google may make changes to these terms at any time with notice and the opportunity to decline further use of the Play Core Software Development Kit. Google will post notice of modifications to the terms at https://developer.android.com/guide/playcore/license. Changes will not be retroactive.
Download Play Core Native SDK

play-core-native-sdk-1.15.3.zip

  1. 执行以下其中一项操作:

    • 安装 Android Studio 4.0 或更高版本。使用 SDK 管理器界面安装 Android SDK Platform 版本 10.0(API 级别 29)。
    • 安装 Android SDK 命令行工具,然后使用 sdkmanager 安装 Android SDK Platform 版本 10.0(API 级别 29)。
  2. 使用 SDK 管理器安装最新的 CMake 和 Android 原生开发套件 (NDK),让 Android Studio 做好原生开发准备。如需详细了解如何创建或导入原生项目,请参阅 NDK 入门指南

  3. 下载 zip 文件并将其解压缩到您的项目所在位置。

    下载链接 大小 SHA-256 校验和
    37.8 MiB 9db60185185342f28d2c278b60222333608c67bc022e458a25224eaea8c4c4b7
  4. 更新应用的 build.gradle 文件,如下所示:

    Groovy

        // App build.gradle
    
        plugins {
          id 'com.android.application'
        }
    
        // Define a path to the extracted Play Core SDK files.
        // If using a relative path, wrap it with file() since CMake requires absolute paths.
        def playcoreDir = file('../path/to/playcore-native-sdk')
    
        android {
            defaultConfig {
                ...
                externalNativeBuild {
                    cmake {
                        // Define the PLAYCORE_LOCATION directive.
                        arguments "-DANDROID_STL=c++_static",
                                  "-DPLAYCORE_LOCATION=$playcoreDir"
                    }
                }
                ndk {
                    // Skip deprecated ABIs. Only required when using NDK 16 or earlier.
                    abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                }
            }
            buildTypes {
                release {
                    // Include Play Core Library proguard config files to strip unused code while retaining the Java symbols needed for JNI.
                    proguardFile '$playcoreDir/proguard/common.pgcfg'
                    proguardFile '$playcoreDir/proguard/gms_task.pgcfg'
                    proguardFile '$playcoreDir/proguard/per-feature-proguard-files'
                    ...
                }
                debug {
                    ...
                }
            }
            externalNativeBuild {
                cmake {
                    path 'src/main/CMakeLists.txt'
                }
            }
        }
    
        dependencies {
            // Import these feature-specific AARs for each Google Play Core library.
            implementation 'com.google.android.play:app-update:2.1.0'
            implementation 'com.google.android.play:asset-delivery:2.2.2'
            implementation 'com.google.android.play:integrity:1.4.0'
            implementation 'com.google.android.play:review:2.0.2'
    
            // Import these common dependencies.
            implementation 'com.google.android.gms:play-services-tasks:18.0.2'
            implementation files("$playcoreDir/playcore-native-metadata.jar")
            ...
        }
        

    Kotlin

    // App build.gradle
    
    plugins {
        id("com.android.application")
    }
    
    // Define a path to the extracted Play Core SDK files.
    // If using a relative path, wrap it with file() since CMake requires absolute paths.
    val playcoreDir = file("../path/to/playcore-native-sdk")
    
    android {
        defaultConfig {
            ...
            externalNativeBuild {
                cmake {
                    // Define the PLAYCORE_LOCATION directive.
                    arguments += listOf("-DANDROID_STL=c++_static", "-DPLAYCORE_LOCATION=$playcoreDir")
                }
            }
            ndk {
                // Skip deprecated ABIs. Only required when using NDK 16 or earlier.
                abiFilters.clear()
                abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
            }
        }
        buildTypes {
            release {
                // Include Play Core Library proguard config files to strip unused code while retaining the Java symbols needed for JNI.
                proguardFile("$playcoreDir/proguard/common.pgcfg")
                proguardFile("$playcoreDir/proguard/gms_task.pgcfg")
                proguardFile("$playcoreDir/proguard/per-feature-proguard-files")
                ...
            }
            debug {
                ...
            }
        }
        externalNativeBuild {
            cmake {
                path = "src/main/CMakeLists.txt"
            }
        }
    }
    
    dependencies {
        // Import these feature-specific AARs for each Google Play Core library.
        implementation("com.google.android.play:app-update:2.1.0")
        implementation("com.google.android.play:asset-delivery:2.2.2")
        implementation("com.google.android.play:integrity:1.4.0")
        implementation("com.google.android.play:review:2.0.2")
    
        // Import these common dependencies.
        implementation("com.google.android.gms:play-services-tasks:18.0.2")
        implementation(files("$playcoreDir/playcore-native-metadata.jar"))
        ...
    }
  5. 更新应用的 CMakeLists.txt 文件,如下所示:

    cmake_minimum_required(VERSION 3.6)
    
    ...
    
    # Add a static library called “playcore” built with the c++_static STL.
    include(${PLAYCORE_LOCATION}/playcore.cmake)
    add_playcore_static_library()
    
    // In this example “main” is your native code library, i.e. libmain.so.
    add_library(main SHARED
            ...)
    
    target_include_directories(main PRIVATE
            ${PLAYCORE_LOCATION}/include
            ...)
    
    target_link_libraries(main
            android
            playcore
            ...)
    

数据收集

为便于 Google 改进产品,Play Core 原生 SDK 可能会收集版本相关数据,包括:

  • 应用的软件包名称
  • 应用的软件包版本
  • Play Core 原生 SDK 的版本

您将应用软件包上传到 Play 管理中心时,系统会收集这些数据。如需停用此数据收集流程,请移除 build.gradle 文件中的 $playcoreDir/playcore-native-metadata.jar 导入项。

请注意,这种与使用 Play Core 原生 SDK 相关的数据收集行为,以及 Google 使用所收集数据的行为,与您将应用软件包上传至 Play 管理中心时 Google 收集在 Gradle 中声明的库依赖项无关且相互独立。

安装时分发

配置为 install-time 的资源包可以在应用启动后立即使用。您可以使用 NDK AAssetManager API 获取在此模式下提供的资源:

#include <android/asset_manager.h>
#include <android_native_app_glue.h>
...
AAssetManager* assetManager = app->activity->assetManager;
AAsset* asset = AAssetManager_open(assetManager, "asset-name", AASSET_MODE_BUFFER);
size_t assetLength = AAsset_getLength(asset);
char* buffer = (char*) malloc(assetLength + 1);
AAsset_read(asset, buffer, assetLength);

快速跟进式分发和按需分发

以下几部分介绍了如何初始化该 API、如何在下载资源包前获取其相关信息、如何调用该 API 以开始下载,以及如何获取已下载的资源包。这几部分适用于 fast-follow 资源包和 on-demand 资源包。

应用启动

请务必先调用 AssetPackManager_init() 以初始化 Asset Pack API,然后再调用其他任何函数。您还应检查是否存在任何资源包错误代码

#include "play/asset_pack.h"
...
AssetPackErrorCode AssetPackManager_init(JavaVM* jvm, jobject android_context);

此外,还应确保调用 ANativeActivityCallbacksonPause()onResume() 中的以下函数:

获取有关资源包的下载信息

在提取资源包之前,应用必须披露下载内容的大小。您可以使用 AssetPackManager_requestInfo() 函数来启动异步请求,以确定下载内容的大小,以及资源包是否已在下载。然后,使用 AssetPackManager_getDownloadState() 轮询下载状态(例如,在游戏循环中,每帧调用一次此函数)。如果请求失败,请检查资源包错误代码

AssetPackErrorCode AssetPackManager_requestInfo();      // Call once
AssetPackErrorCode AssetPackManager_getDownloadState(); // Call once per frame in your game loop

AssetPackManager_getDownloadState() 函数会返回不透明类型 AssetPackDownloadState 作为输出指针。您可以使用此指针调用以下函数:

AssetPackDownloadState* state;
AssetPackErrorCode error_code = AssetPackManager_getDownloadState(asset-pack-name, &state);
AssetPackDownloadStatus status = AssetPackDownloadState_getStatus(state);
uint64_t downloadedBytes = AssetPackDownloadState_getBytesDownloaded(state);
uint64_t totalBytes = AssetPackDownloadState_getTotalBytesToDownload(state));
AssetPackDownloadState_destroy(state);

安装

您可以使用 AssetPackManager_requestDownload() 开始首次下载资源包或请求完成资源包更新:

AssetPackErrorCode AssetPackManager_requestDownload();  // Call once
AssetPackErrorCode AssetPackManager_getDownloadState(); // Call once per frame in your game loop

AssetPackManager_getDownloadState() 函数会返回不透明类型 AssetPackDownloadState。如需了解如何使用此类型,请参阅获取下载信息

下载内容较大

如果下载内容超过 200MB 并且用户未连接到 Wi-Fi,那么在用户明确同意使用移动网络连接继续下载前,下载不会开始。同样,如果下载内容较大并且用户的 Wi-Fi 连接断开,那么下载会暂停,需要用户明确同意才能使用移动网络连接继续下载。已暂停的资源包状态为 WAITING_FOR_WIFI。如需触发界面流程来提示用户同意,请使用以下函数:

需要用户确认

如果软件包的状态为 REQUIRES_USER_CONFIRMATION,则在用户接受显示 AssetPackManager_showConfirmationDialog() 的对话框之前,系统不会继续下载。如果 Play 无法识别应用,可能会出现此状态。请注意,在这种情况下,调用 AssetPackManager_showConfirmationDialog() 会导致应用更新。更新后,请重新请求资源。

获取资源包

在下载请求达到 COMPLETED 状态后,您可以使用文件系统调用来获取资源包。每个资源包都存储于应用的内部存储空间内单独的目录中。您可以使用 AssetPackManager_getAssetPackLocation() 获取指定资源包的 AssetPackLocation。您还可以对该位置使用 AssetPackLocation_getStorageMethod() 以确定存储方法:

  • ASSET_PACK_STORAGE_APK:资源包以 APK 的形式进行安装。如需获取这些资源,请参阅安装时分发
  • ASSET_PACK_STORAGE_FILES:您可以使用 AssetPackLocation_getAssetsPath() 获取包含相应资源的目录的文件路径;如果尚未下载相应资源,则为 null。请勿修改此文件路径中已下载的文件。
AssetPackLocation* location;

AssetPackErrorCode error_code = AssetPackManager_getAssetPackLocation(asset-pack-name, &location);

if (error_code == ASSET_PACK_NO_ERROR) {
    AssetPackStorageMethod storage_method = AssetPackLocation_getStorageMethod(location);
    const char* assets_path = AssetPackLocation_getAssetsPath(location);
    AssetPackLocation_destroy(location);
}

找到资源后,您可以使用 fopenifstream 等函数访问相应的文件。

其他 Play Core API 方法

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

取消请求

您可以使用 AssetPackManager_cancelDownload() 取消有效的资源包请求。请注意,系统只能尽量满足您的此项请求。

请求移除

您还可以使用 AssetPackManager_requestRemoval() 安排移除资源包。

后续步骤

在本地以及通过 Google Play 测试 Play Asset Delivery