支持应用内更新(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 Developer API,您可以设置每项更新的优先级。这样,应用就可以决定向用户推荐更新的强烈程度。例如,您可以考虑使用以下策略来设置更新优先级:

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

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

Play Developer API 文档中所述使用 Google Play Developer API 设置优先级。应用内更新优先级应在以 Edit.tracks: update 方法传递的 Edit.tracks 资源中指定。以下示例演示了如何发布一个版本代码为 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() 回调收到以下几个值:

  • 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,
                  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 界面会关闭。您的应用应确定继续操作的最佳方式。

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

后续步骤

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