支援應用程式內更新 (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.0.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.0.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.0.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.0.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 執行個體含有可用更新的狀態。依更新狀態的不同,執行個體也會包含以下內容:

  • 如果有可用的更新且允許更新,則執行個體也會試圖啟動更新。
  • 如果已在進行應用程式內更新,則執行個體也會報告正在進行中的更新狀態。

檢查更新是否過時

除了檢查是否有可用的更新,建議您也同時確認自從使用者上次收到 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 納入考量。

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,
    // 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 要求,並再次檢查更新是否有可用的更新,以及是否允許更新。

後續步驟取決於您要求的是彈性更新,還是立即更新

使用 AppUpdateOptions 設定更新

除了這種明確的更新流程類型,您也可以建構並傳遞一個 AppUpdateOptions 物件。除了 appUpdateType 欄位以外,AppUpdateOptions 物件也包含 AllowAssetPackDeletion 欄位,用於定義更新作業是否可以在裝置儲存空間有限時清除資產包。這個欄位會預設為 false,但您可以使用 setAllowAssetPackDeletion() 方法將此欄位改設為 true

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // 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,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE);

取得更新狀態的回呼

啟動更新後,您可以使用 onActivityResult() 回呼來處理更新失敗或取消:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == MY_REQUEST_CODE) {
        if (resultCode != RESULT_OK) {
            Log.e("MY_APP", "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:使用者已接受更新。如果是執行立即更新,您可能不會收到這則回呼,原因是更新作業應會在時間控制項回傳給應用程式之前結束。
  • 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 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() 時,平台會顯示全螢幕使用者介面,藉此在背景重新啟動應用程式。平台安裝更新後,應用程式會重新啟動並進入其主要活動中。

如果您在背景執行應用程式時呼叫 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);
            }
          });
}

更新流程傳回的結果如 startUpdateFlowForResult() 參考說明文件中所述。尤其是,您的應用程式應該要能處理使用者拒絕更新或取消下載的情況。當使用者執行上述任一操作時,Google Play 使用者介面都會關閉。您的應用程式應該要決定繼續的最佳方式。

如果可以的話,讓使用者在不更新的情況下繼續,之後再提示他們更新。如果應用程式在未更新的情況下無法正常運作,建議您先顯示一則資訊型訊息,然後再重新啟動更新流程或提示使用者關閉應用程式。這樣一來,使用者就能瞭解,當他們準備好要安裝必要的更新項目時,就可以重新啟動應用程式。

後續步驟

測試應用程式的應用程式內更新,以確認整合作業是否正常運作。