支持应用内更新(Kotlin 或 Java)

本指南将介绍如何使用 Kotlin 或 Java 在您的应用中支持应用内更新。我们针对实现使用原生代码 (C/C++) 以及 Unity 的情况提供了单独的指南。

检查是否有可用更新

在请求更新之前,请检查您的应用是否有可用更新。您可以使用 AppUpdateManager 检查是否有更新:

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // This example applies an immediate update. To apply a flexible update
          // instead, pass in AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

返回的 AppUpdateInfo 实例包含更新可用性状态。该实例也会包含以下内容,具体取决于更新的状态:

  • 如果有可用更新且允许更新,该实例也会包含一个用于启动更新的 intent。
  • 如果应用内更新已在进行中,该实例也会报告正在进行的更新的状态。

检查更新是否已过时

除了检查是否有可用更新之外,您可能还需要检查自通过 Play 商店通知用户更新以来已经过了多长时间。这可帮助您决定应发起灵活更新还是立即更新。例如,您可能需要等待几天后再通知用户进行灵活更新,在此之后再等待几天,然后再要求用户进行立即更新。

您可以使用 clientVersionStalenessDays() 检查自 Play 商店中提供更新以来已经过了多少天:

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.clientVersionStalenessDays() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
});

检查更新优先级

借助 Google Play Developer API,您可以设置每项更新的优先级。这样,您的应用就可以决定以多么强烈的口吻向用户推荐更新。例如,我们考虑使用以下策略来设置更新优先级的策略:

  • 细微的界面改进:低优先级更新;既不请求灵活更新,也不请求立即更新。仅在用户没有与您的应用互动时进行更新。
  • 性能提升:中优先级更新;请求灵活更新。
  • 关键安全更新:高优先级更新;请求立即更新。

Google Play 使用 0 到 5 之间的整数值来确定优先级,其中 0 是默认值,5 代表最高优先级。如需设置更新的优先级,请使用 Google Play Developer API 中 Edits.tracks.releases 下的 inAppUpdatePriority 字段。发布版本中的所有新增版本都被视为与发布版本具有相同的优先级。只有在发布新版本时才能设置优先级,且以后无法更改。

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

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

在应用的代码中,您可以使用 updatePriority() 检查给定更新的优先级。返回的优先级会考虑介于已安装版本与最新可用版本之间的所有应用版本号的 inAppUpdatePriority

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
});

启动更新

在确认有可用更新后,您可以使用 AppUpdateManager.startUpdateFlowForResult() 请求更新:

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
    AppUpdateType.IMMEDIATE,
    // The current activity making the update request.
    this,
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE)

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
    AppUpdateType.IMMEDIATE,
    // The current activity making the update request.
    this,
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE);

每个 AppUpdateInfo 实例只能用于启动一次更新。如需在失败时重试更新,您可以请求新的 AppUpdateInfo 并再次检查是否有可用更新以及是否允许更新。

后续步骤取决于您请求的是灵活更新还是立即更新

使用 AppUpdateOptions 配置更新

或者,您也可以构建并传递 AppUpdateOptions 对象,而不是显式更新流程类型。除了 appUpdateType 字段之外,AppUpdateOptions 对象还包含 AllowAssetPackDeletion 字段,用于定义是否允许更新在设备存储空间有限的情况下清除资源包。此字段默认设置为 false,但您可以使用 setAllowAssetPackDeletion() 方法将其设置为 true

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE)

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE);

获取更新状态的回调

启动更新后,您可以使用 onActivityResult() 回调来处理更新失败或取消:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == MY_REQUEST_CODE) {
        if (resultCode != RESULT_OK) {
            Log.e("MY_APP", "Update flow failed! Result code: $resultCode")
            // If the update is cancelled or fails,
            // you can request to start the update again.
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == MY_REQUEST_CODE) {
    if (resultCode != RESULT_OK) {
      log("Update flow failed! Result code: " + resultCode);
      // If the update is cancelled or fails,
      // you can request to start the update again.
    }
  }
}

您可能会从 onActivityResult() 回调收到以下几个值:

  • RESULT_OK:用户已接受更新。对于立即更新,您可能不会收到此回调,因为在将控制权交还给您的应用之前,更新应该已经完成了。
  • RESULT_CANCELED:用户已拒绝或取消更新。
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED:发生了一些其他错误,使得用户无法同意更新或更新无法继续进行。

处理灵活更新

当您启动灵活更新时,系统会先向用户显示一个对话框以征得用户同意。如果用户同意,那么系统会在后台启动下载,用户可以继续与您的应用互动。本部分介绍了如何监控和完成灵活的应用内更新。

监控灵活更新状态

对于灵活更新,下载开始后,您的应用需要监控更新状态以了解何时可以安装更新并在应用的界面中显示进度。

您可以通过注册监听器来安装状态更新,从而监控正在进行的更新的状态。您还可以在应用的界面中提供一个进度条,以告知用户下载的进度。

Kotlin

// Create a listener to track request state updates.
val listener = InstallStateUpdatedListener { state ->
    // (Optional) Provide a download progress bar.
    if (state.installStatus() == InstallStatus.DOWNLOADING) {
      val bytesDownloaded = state.bytesDownloaded()
      val totalBytesToDownload = state.totalBytesToDownload()
      // Show update progress bar.
    }
    // Log state or install the update.
}

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener)

Java

// Create a listener to track request state updates.
InstallStateUpdatedListener listener = state -> {
  // (Optional) Provide a download progress bar.
  if (state.installStatus() == InstallStatus.DOWNLOADING) {
      long bytesDownloaded = state.bytesDownloaded();
      long totalBytesToDownload = state.totalBytesToDownload();
      // Implement progress bar.
  }
  // Log state or install the update.
};

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener);

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener);

安装灵活更新

在检测到 InstallStatus.DOWNLOADED 状态时,您需要重启应用以安装更新。

与立即更新不同,Google Play 不会自动触发应用重启以进行灵活更新。这是因为,在灵活更新期间,用户希望在他们决定想要安装更新之前能够继续与应用互动。

建议您提供通知(或其他一些界面指示)以告知用户更新已可供安装,并在重启应用之前请求用户确认。

以下示例演示了如何实现 Material Design 信息提示控件,请求用户确认来重启应用:

Kotlin

val listener = { state ->
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate()
    }
    ...
}

// Displays the snackbar notification and call to action.
fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
        findViewById(R.id.activity_main_layout),
        "An update has just been downloaded.",
        Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") { appUpdateManager.completeUpdate() }
        setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
        show()
    }
}

Java

InstallStateUpdatedListener listener = state -> {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate();
    }
    ...
};

// Displays the snackbar notification and call to action.
private void popupSnackbarForCompleteUpdate() {
  Snackbar snackbar =
      Snackbar.make(
          findViewById(R.id.activity_main_layout),
          "An update has just been downloaded.",
          Snackbar.LENGTH_INDEFINITE);
  snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
  snackbar.setActionTextColor(
      getResources().getColor(R.color.snackbar_action_text_color));
  snackbar.show();
}

当您在前台调用 appUpdateManager.completeUpdate() 时,平台会显示一个用于在后台重启应用的全屏界面。平台安装完更新后,您的应用会重启并进入其主 activity。

如果您改为在应用处于后台时调用 completeUpdate(),系统会在不发出提示的情况下安装更新,而不会遮挡设备界面。

每当用户将您的应用调入前台时,请检查您的应用是否有等待安装的更新。如果您的应用有处于 DOWNLOADED 状态的更新,请提示用户安装该更新。否则,更新数据会继续占用用户的设备存储空间。

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            // If the update is downloaded but not installed,
            // notify the user to complete the update.
            if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate()
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(appUpdateInfo -> {
              ...
              // If the update is downloaded but not installed,
              // notify the user to complete the update.
              if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                  popupSnackbarForCompleteUpdate();
              }
          });
}

处理立即更新

在您启动立即更新且用户同意开始更新后,Google Play 会在整个更新期间在应用界面顶部显示更新进度。如果用户在更新期间关闭或终止您的应用,更新应继续在后台下载并安装,无需额外得到用户确认。

不过,当您的应用返回到前台时,您应确认更新未在 UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS 状态下停止。如果更新在此状态下停止,请继续更新:

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
            ) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                    appUpdateInfo,
                    IMMEDIATE,
                    this,
                    MY_REQUEST_CODE
                )
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(
          appUpdateInfo -> {
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                    appUpdateInfo,
                    IMMEDIATE,
                    this,
                    MY_REQUEST_CODE);
            }
          });
}

更新流程会返回 startUpdateFlowForResult() 参考文档中所述的结果。具体而言,您的应用应能够处理用户拒绝更新或取消下载的情况。当用户执行其中任一操作时,Google Play 界面会关闭。您的应用应确定继续操作的最佳方式。

如有可能,不妨让用户在没有更新的情况下继续操作,稍后再次提示他们。如果您的应用在没有更新的情况下无法正常运行,不妨考虑在重新启动更新流程之前先显示一条信息性消息,或者提示用户关闭应用。这样,用户就会知道,当他们准备好安装必需的更新时,可以重新启动您的应用。

后续步骤

测试应用的应用内更新,以验证您的集成是否正常运行。