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

本指南介绍了如何使用 Kotlin 或 Java 在应用中支持应用内更新。如果您的实现使用原生代码 (C/C++),或者您的实现使用 UnityUnreal Engine,则有单独的指南可供参考。

设置您的开发环境

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 契约注册 activity 结果启动器。请查看关于获取更新状态回调的部分。

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

使用 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());

获取更新状态回调

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

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() 回调中接收到以下几个值:

处理灵活更新

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

监控灵活更新状态

灵活更新的下载开始后,您的应用需要监控更新状态,以便了解何时可以安装更新,并在应用的 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,该 UI 会在后台重启应用。平台安装更新后,您的应用会重启并进入其主 activity。

如果您在应用处于后台时调用 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 会关闭。您的应用应确定最佳处理方式。

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

后续步骤

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