アプリ内アップデートをサポートする(Kotlin または Java)

このガイドでは、Kotlin または Java を使用して、アプリ内でアプリ内アップデートをサポートする方法について説明します。実装にネイティブ コード(C / C++)を使用するケースと Unity を使用するケースについては、個別のガイドが用意されています。

開発環境をセットアップする

Play In-App Update Library は Google Play Core Library の一部です。Play In-App Update Library を統合するには、次の Gradle 依存関係を含めてください。

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 インスタンスに、アップデートの有無を示すステータスが含まれます。アップデートのステータスに応じて、インスタンスには以下も含まれます。

  • アップデートが利用可能で、許可されている場合、インスタンスにはアップデートを開始するインテントも含まれます。
  • アプリ内アップデートがすでに進行中の場合、インスタンスは進行中の更新のステータスも報告します。

アップデートの新しさの確認

アップデートが適用可能であるかどうかを確認するだけでなく、ユーザーが Google Play ストアからアップデートの通知を受信してから経過した期間を確認することもできます。これは、フレキシブル アップデートと即時アップデートのどちらを開始したらよいかを判断する際に有用です。たとえば、ユーザーにフレキシブル アップデートを通知するまで数日待ち、その後、即時アップデートを要求するまでさらに数日待つこともできます。

アップデートが Google Play ストアで入手できるようになってからの日数の確認には、clientVersionStalenessDays() を使用します。

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 では、各アップデートの優先度を設定できます。 これにより、アプリはユーザーにアップデートを推奨する度合いを決定できるようになります。たとえば、アップデートの優先度を設定するための次のような戦略を考えてみます。

  • UI の細かな改善: 優先度が低いアップデート。フレキシブル アップデートも即時アップデートもリクエストしません。ユーザーがアプリを操作していないときにのみ更新します。
  • パフォーマンスの改善: 優先度が中程度のアップデート。フレキシブル アップデートをリクエストします。
  • 重要なセキュリティ アップデート: 優先度が高いアップデート。即時アップデートをリクエストします。

優先度を決定するにあたり、Google Play は 0~5 の整数値を使用します。0 はデフォルトの値、5 は優先度が最も高い値です。アップデートの優先度を設定するには、Google Play Developer API の Edits.tracks.releases の下にある inAppUpdatePriority フィールドを使用します。リリースで新たに追加されたすべてのバージョンの優先度は、リリースと同じとみなされます。優先度は新しいリリースの公開時にのみ設定できます。後から変更することはできません。

Google Play Developer API を使用した優先度の設定については、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 に更新すると、バージョン 2 が別のトラックで公開されている場合でも優先度 5 が割り当てられます。

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 インスタンスを使用してアップデートを開始できる回数は 1 回のみです。失敗した場合にアップデートを再試行するには、新しい AppUpdateInfo をリクエストし、アップデートが利用可能かつ許可されていることを再度確認します。

組み込みの ActivityResultContracts.StartIntentSenderForResult コントラクトを使用して、アクティビティ結果ランチャーを登録できます。アップデート ステータスのコールバックの取得のセクションをご覧ください。

次のステップは、フレキシブル アップデート即時アップデートのどちらをリクエストしているかによって異なります。

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

アップデート ステータスのコールバックの取得

アップデートを開始すると、登録済みのアクティビティ結果ランチャー コールバックが確認ダイアログの結果を取得します。

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: 他のエラーにより、ユーザーが同意できなかったか、アップデートを続行できませんでした。

フレキシブル アップデートの処理

フレキシブル アップデートを開始すると、ユーザーに同意を求めるダイアログが最初に表示されます。ユーザーが同意すると、バックグラウンドでダウンロードが開始され、ユーザーはアプリの操作を続けることができます。このセクションでは、アプリ内フレキシブル アップデートを確認、完了する方法について説明します。

フレキシブル アップデートの状態の確認

フレキシブル アップデートのダウンロードが開始されたら、アプリはアップデートの状態を確認して、アップデートをインストールできるタイミングを把握し、アプリの 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 表示)を行うことをおすすめします。

次の例は、アプリの再起動に対するユーザーの確認を求めるマテリアル デザインのスナックバーの実装方法を示しています。

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 がプラットフォームに表示されます。アップデートがインストールされると、アプリはメイン アクティビティで再起動します。

代わりに、アプリがバックグラウンドで実行されているときに 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 は終了します。続行するために、アプリは最善の方法を決定する必要があります。

可能であれば、ユーザーがアップデートを行わずに継続できるようにして、後でもう一度アップデートを促すようにしてください。アップデートを行わないとアプリを使用できない場合は、その旨をメッセージで表示した後で、アップデート フローを再開するか、ユーザーにアプリを終了するように促すことを検討してください。そうすることで、ユーザーは、必要なアップデートをインストールする準備ができるようになったらアプリを再起動できると理解します。

次のステップ

アプリ内アップデートをテストして、統合が正しく機能していることを確認する。