アプリ内アップデートをサポートする

ユーザーのデバイスのアプリを最新の状態に保つと、ユーザーは新しい機能を試すことができ、パフォーマンスの向上とバグの修正というメリットを得られます。デバイスが定額制で接続されている間のみバックグラウンド アップデートを実施する設定をユーザーが有効にしている場合もありますが、アップデートの通知を必要とするユーザーもいます。アプリ内アップデートは Play Core Library の機能です。アクティブなユーザーにアプリのアップデートを促す新しいリクエスト フローを導入します。

アプリ内アップデートは Android 5.0(API レベル 21)以降のデバイスでのみ機能し、Play Core Library 1.5.0 以降の使用を必要とします。また、アプリ内アップデートは、Android モバイル デバイスとタブレット、Chrome OS デバイスのみで動作するアプリもサポートしています。

これらの要件を満たすと、アプリで以下のアプリ内アップデートの UX に対応できるようになります。

  • フレキシブル: バックグラウンドでのダウンロードとインストール、および正常状態のモニタリングを行うユーザー エクスペリエンス。この UX は、ユーザーがアップデートのダウンロード中にアプリの使用が可能な場合に適しています。たとえば、アプリのコア機能にとって重要ではない新機能をユーザーに試してもらうように促すような場合です。

    図 1.フレキシブルなアップデート フローの例

  • 即時: アプリの使用を継続するために、ユーザーがアプリを更新して再起動する必要があるフルスクリーンのユーザー エクスペリエンス。この UX は、アプリを継続して使用するためにアップデートが重要である場合に最適です。ユーザーが即時アップデートを承認すると、Google Play はアップデートのインストールとアプリの再起動を行います。

    図 2.即時アップデート フローの例

このページでは、Play Core Library を使用して、アプリ内アップデート(フレキシブルまたは即時)をリクエストおよび実行する方法について説明します。

アップデートの有無の確認

アップデートをリクエストする前にまず、アップデートがアプリで利用可能かどうかを確認する必要があります。アップデートについて確認するには、次に示すように 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 にはアップデートを開始するインテントも含まれます。アップデートを開始する方法については、次のセクションをご覧ください。

アプリ内アップデートがすでに進行中の場合、結果で進行中のアップデートのステータスも報告されます。

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

アップデートが利用可能かどうかを確認するだけでなく、ユーザーが 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 では、各アップデートの優先度を設定できます。 これにより、アプリはユーザーにアップデートを推奨する程度を決定できるようになります。たとえば、アップデートの優先度を設定するための次のような戦略を考えてみます。

  • UI の細かな点の改善: 優先度が低いアップデート。フレキシブル アップデートも即時アップデートも行わないようにリクエストします。
  • パフォーマンスの改善: 優先度が中程度のアップデート。フレキシブル アップデートをリクエストします。
  • 重要なセキュリティ アップデート: 優先度が高いアップデート。即時アップデートを要求します。

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

Google Play Developer API を使用して優先度を設定するには、編集ワークフローの作成、新しい APK やバンドルのアップロードとトラックへの割り当て、編集の commit を行います。Play Developer API のドキュメントの説明をご覧ください。 アプリ内アップデートの優先度は、Edits.tracks: update メソッドで渡される Edits.tracks resource で指定する必要があります。たとえば、バージョン コード 88 の APK をリリースする場合、inAppUpdatePriority を 5 にします。

{
  "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 インスタンスを使用してアップデートを 1 回だけ開始できます。 失敗した場合にアップデートを再試行するには、新しい 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 はバックグラウンドでアップデートのダウンロードを開始します。ダウンロードの開始後、アプリはアップデートの状態をモニタリングして、アップデートをインストールできるタイミングを把握し、アプリの UI に進行状況を表示します。

ステータス アップデートをインストールするリスナーを登録することにより、進行中のアップデートの状態をモニタリングできます。また、アプリの UI に進行状況バーを表示して、ダウンロードの進行状況をユーザーに知らせることもできます。

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 はアプリの再起動を行いません。 これは、フレキシブル アップデートの間、ユーザーはアップデートのインストールを決定するまでアプリを使用し続けることを期待しているためです。

そのため、インストールの準備ができたことをユーザーに伝え、アプリの再起動に対するユーザーの確認を求める通知(またはその他の UI 表示)を行うことをおすすめします。

たとえば、図 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() を呼び出すと、バックグラウンドでアプリを再起動するフルスクリーン UI がプラットフォームで表示されます。アップデートがインストールされると、アプリはメイン アクティビティで再起動します。

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

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

内部アプリ共有を使用したテスト

内部アプリ共有では、テストする App Bundle を Play Console にアップロードすることで、内部チームやテスターと App Bundle または APK をすぐに共有できます。

内部アプリ共有を使用して、次に示しように、アプリ内アップデートをテストすることもできます。

  1. テストデバイスで、次の要件を満たすバージョンのアプリがインストールされていることを確認します。
    • 内部アプリ共有 URL を使用してアプリをインストールした
    • アプリ内アップデートに対応している
    • バージョン コードがアプリの更新版よりも低いものを使用している
  2. アプリを内部で共有する方法の Play Console の手順に従います。テストデバイスにインストールしたバージョンよりも高いバージョン コードを使用するアプリのバージョンをアップロードするようにしてください。
  3. テストデバイスで、更新版アプリの内部アプリ共有リンクのみをクリックします。リンクをクリックした後に表示される Google Play ストアのページからアプリをインストールしないでください
  4. デバイスのアプリドロワーまたはホーム画面でアプリを開きます。これで、アップデートをアプリで利用でき、アプリ内アップデートの実装をテストできるようになります。

トラブルシューティング

このセクションでは、テスト中にアプリ内アップデートが期待どおりに動作しない場合に、考えられる解決策について説明します。

  • アプリ内アップデートは、アプリを所有するユーザー アカウントでのみ利用できます。そのため、アカウントを使用してアプリ内アップデートをテストする前に、使用しているアカウントで Google Play から少なくとも 1 回アプリをダウンロードできていることを確認してください。
  • アプリ内アップデートをテストするアプリのアプリケーション ID が同じであり、Google Play から入手できるものと同じ署名キーで署名されていることを確認してください。
  • Google Play によるアプリのアップデートは、より高いバージョン コードにのみ可能であるため、テストしているアプリがアップデートのバージョン コードよりも低いバージョン コードであることを確認してください。
  • アカウントが対象であり、Google Play キャッシュが最新であることを確認してください。これを行うには、テストデバイスで Google Play ストア アカウントにログインして、次の手順を実行します。
    1. 完全に Google Play ストア アプリを閉じます
    2. Google Play ストア アプリを開き、[マイアプリ&ゲーム] タブに移動します。
    3. テスト中のアプリに利用可能なアップデートが表示されない場合は、テストトラックの設定が適切であることを確認します。