Google 致力于为黑人社区推动种族平等。查看具体举措

支持应用内更新

使您的应用在用户设备上保持最新状态可让用户试用新功能,并从性能提升和问题修复中获益。虽然一些用户会启用在设备连接到不按流量计费的连接时进行的后台更新,但还有一些用户可能需要收到提醒来决定是否更新。应用内更新是一项 Play Core 库功能,它引入了一个新的请求流程,用于提示活跃用户更新您的应用。

应用内更新仅适用于搭载 Android 5.0(API 级别 21)或更高版本的设备,并且要求您使用 Play Core 库 1.5.0 或更高版本。此外,应用内更新仅支持在 Android 移动设备和平板电脑以及 Chrome 操作系统设备上运行的应用。

满足这些要求后,您的应用就可以支持以下应用内更新用户体验:

  • 灵活更新:提供后台下载和安装同时妥善监控状态的用户体验。如果可以让用户在下载更新的同时使用应用,这种用户体验非常合适。例如,您希望鼓励用户试用对应用的核心功能而言并不是至关重要的新功能。

    图 1. 灵活更新流程示例

  • 立即更新:要求用户更新并重启应用才能继续使用应用的全屏用户体验。当更新对继续使用应用而言至关重要时,这种用户体验最为合适。用户接受立即更新后,Google Play 会处理更新安装和应用重启。

    图 2. 立即更新流程示例

本页介绍了如何使用 Play Core 库请求并执行“灵活”或“立即”类型的应用内更新。

检查是否有可用更新

在请求更新之前,您需要先检查您的应用是否有可用更新。如需检查是否有更新,请使用 AppUpdateManager,如下所示:

Kotlin

// Creates instance of the manager.
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
        // For a flexible update, use AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Java

// Creates instance of the manager.
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
          // For a flexible update, use AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

结果包含更新可用性状态。如果有可用更新且允许更新,则返回的 AppUpdateInfo 也会包含一个用于启动更新的 intent。如需了解如何启动更新,请参阅下一部分。

如果应用内更新已在进行中,则结果也会报告正在进行的更新的状态。

检查更新过时

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

如需检查自 Google Play 商店得知更新以来已经过去的天数,请使用 clientVersionStalenessDays(),如下所示:

Kotlin

// Creates instance of the manager.
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() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
}

Java

// Creates instance of the manager.
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 设置优先级,您可以创建一个修改工作流,上传您的新 APK 或捆绑包,将其分配给轨道并提交您所做的修改,如 Play Developer API 文档中所述。应用内更新优先级应在 Edits.tracks: update 方法中传递的 Edits.tracks resource 中指定。例如,如需发布一个版本代码为 88 且 inAppUpdatePriority 为 5 的 APK,请使用以下代码:

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

在应用的代码中,您可以使用 updatePriority() 检查给定更新的优先级,如下所示:

Kotlin

// Creates instance of the manager.
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() >= HIGH_PRIORITY_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
}

Java

// Creates instance of the manager.
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() >= HIGH_PRIORITY_UPDATE
          && 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 并再次检查是否有可用更新以及是否允许更新。

您请求的更新类型决定了您需要执行的后续步骤。如需了解详情,请参阅有关如何处理立即更新处理灵活更新的部分。

获取更新状态的回调

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

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    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.
        }
    }
}

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

处理灵活更新

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

监控灵活更新状态

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

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

Kotlin

// Create a listener to track request state updates.
val listener = { 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 信息提示控件,请求用户确认来重启应用,如图 1 所示。

以下代码示例展示了在灵活更新下载完成后向用户显示的信息提示控件通知。

Kotlin

override fun onStateUpdate(state: InstallState) {
    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

@Override
public void onStateUpdate(InstallState 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。

如果您改为在应用处于后台时调用 appUpdateManager.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);
            }
          });
}

您的应用应能够处理用户拒绝更新或取消下载的情况。当用户执行其中任一操作时,Google Play 界面会关闭,您的应用应确定继续操作的最佳方式。

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

使用内部应用分享功能进行测试

借助内部应用分享功能,您可以通过将要测试的 app bundle 上传到 Play 管理中心,快速与您的内部团队和测试人员分享 app bundle 或 APK。

您也可以使用内部应用分享功能来测试应用内更新,如下所示:

  1. 在测试设备上,确保您已安装满足以下要求的应用版本:
    • 应用是使用内部应用分享网址安装的
    • 支持应用内更新
    • 使用的版本代码低于更新后的应用版本
  2. 按照 Play 管理中心内有关如何在内部分享应用的说明进行操作。确保您上传的应用版本使用的版本代码高于测试设备上已安装的版本代码。
  3. 在测试设备上,仅针对更新后的应用版本点击内部应用分享链接。请勿从点击该链接后看到的 Google Play 商店页面安装应用。
  4. 从设备的应用抽屉或主屏幕打开应用。更新现在应可供您的应用使用,您可以测试应用内更新的实现。

问题排查

本部分介绍了针对测试期间应用内更新可能无法按预期运行的情况给出的一些可行解决方案。

  • 只有拥有应用的用户帐号才能使用应用内更新。因此,在使用帐号测试应用内更新之前,请确保您使用的帐号至少从 Google Play 下载过一次应用。
  • 确保您用于测试应用内更新的应用具有相同的应用 ID,并使用与 Google Play 提供的签名密钥相同的签名密钥进行签名。
  • 由于 Google Play 只能将应用更新为更高的版本代码,因此请确保您测试的应用的版本代码低于更新版本代码。
  • 确保帐号符合条件且 Google Play 缓存保持最新状态。为此,在测试设备上登录 Google Play 商店帐号后,请按以下步骤操作:
    1. 确保您完全关闭 Google Play 商店应用
    2. 打开 Google Play 商店应用,然后转到我的应用和游戏标签页。
    3. 如果您要测试的应用显示没有可用更新,请检查您是否已正确设置测试轨道