第二个 Android 11 开发者预览版现已推出,快来测试并分享您的反馈吧

支持应用内更新

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

应用内更新仅适用于搭载 Android 5.0(API 级别 21)或更高版本的设备,并且要求您使用 Play 核心库 1.5.0 或更高版本。满足这些要求后,您的应用可以为应用内更新提供以下用户体验支持:

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

    图 1. 灵活更新流程示例

  • 立即更新:需要用户更新并重启应用才能继续使用应用的全屏用户体验。这种用户体验最适合需要更新才能继续使用应用的情况。用户接受立即更新后,Google Play 会处理更新安装和应用重启事宜。

    图 2. 立即更新流程示例

本页介绍了如何使用 Play 核心库请求并执行灵活或立即应用内更新。

检查是否有可用更新

在请求更新之前,您需要先检查您的应用是否有可用更新。要检查是否有更新,请使用 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。要了解如何启动更新,请参阅下一部分。

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

启动更新

确认可以更新应用后,您可以使用 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 ->
        // Show module progress, 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 -> {
        // Show module progress, 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 下载过一次应用。
  • 确保您用于测试应用内更新的应用具有相同的应用 ID,并使用与 Google Play 提供的签名密钥相同的签名密钥进行签名。
  • 由于 Google Play 只能将应用更新为更高版本的代码,因此请确保您测试的应用的版本代码低于更新版本代码。
  • 请确保帐号有效,且 Google Play 缓存已是最新版本。为此,在测试设备上登录 Google Play 商店帐号后,请按以下步骤继续操作:
    1. 确保您完全关闭 Google Play 商店应用
    2. 打开 Google Play 商店应用,然后转到我的应用和游戏标签页。
    3. 如果您要测试的应用显示没有可用更新,请检查您是否已正确设置测试轨道