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

本指南介绍如何使用 Kotlin 或 Java 在您的应用中支持应用内更新。对于您的实现使用原生代码 (C/C++) 和使用Unity 的情况,有单独的指南。

设置开发环境

Play 应用内更新库是Google Play Core 库的一部分。请包含以下 Gradle 依赖项以集成 Play 应用内更新库。

Groovy

// In your app’s build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:app-update:2.1.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.1.0'
    ...
}

Kotlin

// In your app’s build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.1.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.1.0")
    ...
}

检查更新可用性

在请求更新之前,请检查您的应用是否有更新可用。使用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 { appUpdateInfo ->
    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 开发者 API 允许您设置每个更新的优先级。这允许您的应用决定向用户推荐更新的力度。例如,考虑以下设置更新优先级的策略

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

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

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

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

在应用代码中,您可以使用 updatePriority() 检查给定更新的优先级。返回的优先级会考虑从已安装版本到最新可用版本之间所有应用版本代码的 inAppUpdatePriority,而不管发布轨道如何。例如,请考虑以下场景

  • 您将版本 1 发布到生产轨道,且没有优先级。
  • 您将版本 2 发布到内部测试轨道,优先级为 5。
  • 您将版本 3 发布到生产轨道,且没有优先级。

当生产用户从版本 1 更新到版本 3 时,他们将获得优先级 5,即使版本 2 发布在不同的轨道上。

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,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());

每个 AppUpdateInfo 实例只能用于启动一次更新。如果更新失败,要重试,请请求一个新的 AppUpdateInfo 并再次检查更新是否可用且已获允许。

您可以使用内置的 ActivityResultContracts.StartIntentSenderForResult 合同注册活动结果启动器。请查看有关 获取更新状态回调 的部分。

接下来的步骤取决于您是请求 灵活更新 还是 立即更新

使用 AppUpdateOptions 配置更新

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

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build());

获取更新状态的回调

启动更新后,注册的活动结果启动器回调会获取确认对话框的结果

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult ->
    // handle callback
    if (result.resultCode != RESULT_OK) {
        log("Update flow failed! Result code: " + result.resultCode);
        // If the update is canceled or fails,
        // you can request to start the update again.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // handle callback
            if (result.getResultCode() != RESULT_OK) {
                log("Update flow failed! Result code: " + result.getResultCode());
                // If the update is canceled or fails,
                // you can request to start the update again.
            }
        }
    });

您可能会从 onActivityResult() 回调中收到几个值

  • RESULT_OK:用户已接受更新。对于立即更新,您可能不会收到此回调,因为在将控制权交还给您的应用时,更新应该已经完成。
  • RESULT_CANCELED:用户已拒绝或取消更新。
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED:某些其他错误阻止用户提供同意或更新继续进行。

处理灵活更新

启动灵活更新时,首先会向用户显示一个对话框以请求同意。如果用户同意,则下载将在后台开始,用户可以继续与您的应用交互。本部分介绍如何监视和完成灵活的应用内更新。

监视灵活更新状态

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

您可以通过注册安装状态更新的侦听器来监视正在进行的更新的状态。您还可以在应用的 UI 中提供进度条,以告知用户下载的进度。

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 不会自动触发灵活更新的应用重新启动。这是因为在灵活更新期间,用户期望在决定安装更新之前继续与应用交互。

建议您提供通知(或其他 UI 指示),以告知用户更新已准备好安装,并在重新启动应用之前请求确认。

以下示例演示了如何实现一个 Material Design SnackBar,该 SnackBar 会请求用户确认是否重新启动应用

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() 时,平台会显示一个全屏 UI,在后台重新启动应用。平台安装更新后,您的应用将重新启动到其主活动。

如果您在应用 处于后台 时调用 completeUpdate(),则更新将静默安装,而不会遮挡设备 UI。

无论何时用户将您的应用带到前景,请检查您的应用是否有一个等待安装的更新。如果您的应用有一个处于 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 会在整个更新期间在您的应用 UI 的顶部显示更新进度。如果用户在更新期间关闭或终止您的应用,更新应继续在后台下载和安装,无需额外用户确认。

但是,当您的应用返回前景时,您应该确认更新是否未停留在 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,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())
            }
        }
}

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,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
          });
}

更新流程会返回一个结果,如 startUpdateFlowForResult() 参考文档中所述。特别是,您的应用应该能够处理用户拒绝更新或取消下载的情况。当用户执行这两个操作中的任何一个时,Google Play UI 将关闭。您的应用应确定最佳的继续方式。

如果可能,请允许用户在没有更新的情况下继续,并在稍后再次提示他们。如果您的应用无法在没有更新的情况下运行,请考虑在重新启动更新流程或提示用户关闭应用之前显示一条信息性消息。这样,用户就会明白,当他们准备好安装所需的更新时,可以重新启动您的应用。

后续步骤

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