支持应用内更新(原生)

本指南介绍了如何在应用程序中使用原生代码(C 或 C++)支持 应用内更新。对于使用 Kotlin 编程语言或 Java 编程语言 的实现,以及使用 Unity 的实现,有单独的指南。

原生 SDK 概述

Play Core 原生 SDK 是 Play Core SDK 系列的一部分。原生 SDK 包含一个 C 头文件 app_update.h,它包装了来自 Java Play 应用内更新库的 AppUpdateManager。此头文件允许您的应用程序直接从原生代码调用应用内更新的 API。

设置您的开发环境

下载 Play Core 原生 SDK

在下载之前,您必须同意以下条款和条件。

条款和条件

上次修改日期:2020 年 9 月 24 日
  1. 通过使用 Play Core 软件开发工具包,您同意这些条款,以及 Google API 服务条款(“API 服务条款”)。如果这些条款之间存在任何冲突,则这些条款将优先于 API 服务条款。请仔细阅读这些条款和 API 服务条款。
  2. 在本条款中,“API”是指 Google 的 API、其他开发者服务和相关软件,包括任何可再发行代码。
  3. “可再发行代码”是指 Google 提供的调用 API 的目标代码或头文件。
  4. 根据这些条款和 API 服务条款,您只能将可再发行代码复制和分发作为您 API 客户端的一部分进行包含。Google 及其许可方拥有可再发行代码的所有权利、所有权和权益,包括任何及所有知识产权和其他专有权利。您不得修改、翻译或创建可再发行代码的衍生作品。
  5. Google 可随时通过通知和拒绝您进一步使用 Play Core 软件开发工具包的机会,更改这些条款。Google 将在 https://developer.android.com/guide/playcore/license 上发布对这些条款的修改通知。更改不会具有追溯力。
下载 Play Core 原生 SDK

play-core-native-sdk-1.14.0.zip

  1. 执行以下任一操作

  2. 使用 SDK 管理器 安装最新的 CMake 和 Android Native Development Kit (NDK),从而为 Android Studio 准备原生开发。有关创建或导入原生项目的更多信息,请参阅 NDK 入门

  3. 下载 zip 文件并将其解压缩到项目旁边。

    下载链接 大小 SHA-256 校验和
    36 MiB 782a8522d937848c83a715c9a258b95a3ff2879a7cd71855d137b41c00786a5e
  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.1'
    
            // 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.1")
    
        // 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
            ...)
    

数据收集

Play Core Native SDK 可能会收集与版本相关的数据,以帮助 Google 改进产品,包括

  • 应用程序的包名
  • 应用程序的包版本
  • Play Core Native SDK 的版本

当您将 应用程序包 上传到 Play 管理中心时,将收集这些数据。要选择退出此数据收集过程,请从 build.gradle 文件中删除 $playcoreDir/playcore-native-metadata.jar 导入。

请注意,与您使用 Play Core Native SDK 相关的数据收集以及 Google 对所收集数据的使用,与 Google 在您将应用程序包上传到 Play 管理中心时在 Gradle 中声明的库依赖项的收集是分开且独立的。

将 Play Core Native SDK 集成到项目中后,请在包含 API 调用的文件中包含以下行

#include "play/app_update.h"

初始化应用内更新 API

使用应用内更新 API 时,请先通过调用 AppUpdateManager_init() 函数来初始化它,如下面的使用 android_native_app_glue.h 构建的示例所示

void android_main(android_app* app) {
  app->onInputEvent = HandleInputEvent;

  AppUpdateErrorCode error_code =
    AppUpdateManager_init(app->activity->vm, app->activity->clazz);
  if (error_code == APP_UPDATE_NO_ERROR) {
    // You can use the API.
  }
}

检查更新可用性

在请求更新之前,请检查您的应用程序是否有可用的更新。 AppUpdateManager_requestInfo() 启动一个异步请求,该请求收集启动应用内更新流程所需的信息。如果请求成功启动,则该函数将返回 APP_UPDATE_NO_ERROR

AppUpdateErrorCode error_code = AppUpdateManager_requestInfo()

if (error_code == APP_UPDATE_NO_ERROR) {
    // The request has successfully started, check the result using
    // AppUpdateManager_getInfo.
}

您可以使用 AppUpdateManager_getInfo() 跟踪请求的进行过程和结果。除了错误代码之外,此函数还返回一个 AppUpdateInfo 不透明结构,您可以使用它来检索有关更新请求的信息。例如,您可能希望在每个游戏循环中调用此函数,直到它返回 info 的非空结果

AppUpdateInfo* info;
GameUpdate() {

   // Keep calling this in every game loop until info != nullptr
   AppUpdateErrorCode error_code = AppUpdateManager_getInfo(&info);


   if (error_code == APP_UPDATE_NO_ERROR && info != nullptr) {
       // Successfully started, check the result in the following functions
   }
...
}

检查更新陈旧程度

除了检查更新是否可用外,您可能还希望检查自用户上次通过 Play 商店收到更新通知以来已经过去了多长时间。这可以帮助您决定是否应启动灵活更新或立即更新。例如,您可能需要等待几天才能向用户通知灵活更新,然后再等待几天才能要求立即更新。

使用 AppUpdateInfo_getClientVersionStalenessDays() 检查自更新通过 Play 商店提供以来已经过去了多少天

int32_t staleness_days = AppUpdateInfo_getClientVersionStalenessDays(info);

检查更新优先级

Google Play 开发者 API 允许您设置每个更新的优先级。这使您的应用程序能够决定以多大的力度向用户推荐更新。例如,考虑以下关于设置更新优先级的策略

  • 轻微的 UI 改进:**低优先级** 更新;既不要求灵活更新,也不要求立即更新。仅在用户不与您的应用程序交互时更新。
  • 性能改进:**中优先级** 更新;请求灵活更新。
  • 关键的安全更新:**高优先级** 更新;请求立即更新。

为了确定优先级,Google Play 使用介于 0 到 5 之间的整数,其中 0 为默认值,5 为最高优先级。要设置更新的优先级,请使用 Edits.tracks.releases 下的 inAppUpdatePriority 字段,该字段位于 Google Play 开发者 API 中。发布版中所有新添加的版本都被认为与发布版具有相同的优先级。仅在推出新发布版时才能设置优先级,之后无法更改。

Play 开发者 API 文档 中所述,使用 Google Play 开发者 API 设置优先级。在 Edit.tracks 资源中指定应用内更新优先级,该资源传递给 Edit.tracks: update 方法。以下示例演示了如何发布版本代码为 88 且 inAppUpdatePriority 为 5 的应用程序

{
  "releases": [{
      "versionCodes": ["88"],
      "inAppUpdatePriority": 5,
      "status": "completed"
  }]
}

在应用程序的代码中,您可以使用 AppUpdateInfo_getPriority() 检查给定更新的优先级级别

int32_t priority = AppUpdateInfo_getPriority(info);

启动更新

确认更新可用后,您可以使用 AppUpdateManager_requestStartUpdate() 请求更新。在请求更新之前,请获取最新的 AppUpdateInfo 对象,并创建一个 AppUpdateOptions 对象来配置更新流程。 AppUpdateOptions 对象定义应用内更新流程的选项,包括更新是灵活的还是立即的。

以下示例创建了一个用于灵活更新流程的 AppUpdateOptions 对象

// Creates an AppUpdateOptions configuring a flexible in-app update flow.
AppUpdateOptions* options;
AppUpdateErrorCode error_code = AppUpdateOptions_createOptions(APP_UPDATE_TYPE_FLEXIBLE, &options);

以下示例创建了一个用于立即更新流程的 AppUpdateOptions 对象

// Creates an AppUpdateOptions configuring an immediate in-app update flow.
AppUpdateOptions* options;
AppUpdateErrorCode error_code = AppUpdateOptions_createOptions(APP_UPDATE_TYPE_IMMEDIATE, &options);

AppUpdateOptions 对象还包含一个 AllowAssetPackDeletion 字段,该字段定义是否允许更新在存储空间有限的情况下清除 资源包。默认情况下,此字段设置为 false,但您可以使用 AppUpdateOptions_setAssetPackDeletionAllowed() 方法将其设置为 true

bool allow = true;
AppUpdateErrorCode error_code = AppUpdateOptions_setAssetPackDeletionAllowed(options, allow);

获取最新的 AppUpdateInfo 对象和正确配置的 AppUpdateOptions 对象后,请调用 AppUpdateManager_requestStartUpdate() 异步请求更新流程,并将 Android Activity jobject 传递给最后一个参数。

AppUpdateErrorCode request_error_code =
AppUpdateManager_requestStartUpdate(info, options, app->activity->clazz);

为了释放资源,请分别调用 AppUpdateInfo_destroy()AppUpdateOptions_destroy() 来释放不再需要的 AppUpdateInfoAppUpdateOptions 实例。

AppUpdateInfo_destroy(info);
AppUpdateOptions_destroy(options);

对于立即更新流程,Google Play 会显示一个用户确认页面。当用户接受请求时,Google Play 会自动在前台下载并安装更新,然后在安装成功后将应用程序重新启动到更新的版本。

对于灵活更新流程,您可以继续请求最新的 AppUpdateInfo 对象,以在用户继续与应用程序交互时跟踪当前更新状态。下载成功完成后,您必须通过调用 AppUpdateManager_requestCompleteUpdate() 来触发更新的完成,如下面的示例所示

AppUpdateStatus status = AppUpdateInfo_getStatus(info);
if (status == APP_UPDATE_DOWNLOADED) {
    AppUpdateErrorCode error_code = AppUpdateManager_requestCompleteUpdate();
    if (error_code != APP_UPDATE_NO_ERROR)
    {
      // There was an error while completing the update flow.
    }
}

应用程序完成使用 API 后,请调用 AppUpdateManager_destroy() 函数来释放资源。

错误处理

本节介绍如何解决由特定 AppUpdateErrorCode 值指示的常见错误

  • 错误代码 -110, APP_UPDATE_INITIALIZATION_NEEDED 表示 API 未成功初始化。调用 AppUpdateManager_init() 来初始化 API。
  • 错误代码 -4, APP_UPDATE_INVALID_REQUEST 表示更新流程请求的一些参数格式不正确。请检查以确保 AppUpdateInfoAppUpdateOptions 对象不为空且格式正确。
  • 错误代码 -5, APP_UPDATE_UNAVAILABLE 表示没有可用的适用更新。请确保目标版本具有相同的 包名应用程序 ID签名密钥。如果存在可用的更新,请清除应用程序的缓存并再次调用 AppUpdateManager_requestAppUpdateInfo() 以刷新 AppUpdateInfo
  • 错误代码 -6, APP_UPDATE_NOT_ALLOWED 表示 AppUpdateOption 对象指示的更新类型不允许。在启动更新流程之前,请检查 AppUpdateInfo 对象是否指示更新类型允许。

后续步骤

测试应用程序的应用内更新,以验证您的集成是否正常工作。