Obsługa aktualizacji w aplikacji (Kotlin lub Java)

Z tego przewodnika dowiesz się, jak obsługiwać aktualizacje w aplikacji w środowisku Kotlin lub Java. Istnieją osobne przewodniki dla przypadków, w których implementacja korzysta z kodu natywnego (C/C++), i przypadku, gdy implementacja korzysta z Unity.

Konfigurowanie środowiska programistycznego

Biblioteka aktualizacji Google Play w aplikacji jest częścią podstawowych bibliotek Google Play. Aby zintegrować bibliotekę aktualizacji Play w aplikacji, uwzględnij tę zależność Gradle.

Odlotowe

// 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")
    ...
}

Sprawdź dostępność aktualizacji

Zanim poprosisz o aktualizację, sprawdź, czy jest dostępna aktualizacja Twojej aplikacji. Kliknij AppUpdateManager, by sprawdzić dostępność aktualizacji:

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.
    }
});

Zwrócona instancja AppUpdateInfo zawiera stan dostępności aktualizacji. W zależności od stanu aktualizacji instancja zawiera też:

  • Jeśli aktualizacja jest dostępna, a aktualizacja jest dozwolona, instancja zawiera również intencję rozpoczęcia aktualizacji.
  • Jeśli aktualizacja w aplikacji jest już w toku, instancja raportuje również stan trwającej aktualizacji.

Sprawdź aktualność aktualizacji

Oprócz sprawdzenia, czy jest dostępna aktualizacja, możesz też sprawdzić, ile czasu upłynęło od ostatniego powiadomienia użytkownika o aktualizacji w Sklepie Play. Pomoże Ci to podjąć decyzję, czy aktualizacja jest elastyczna czy natychmiastowa. Możesz np. odczekać kilka dni przed powiadomieniem użytkownika o elastycznej aktualizacji i kilka dni później, zanim zażądasz natychmiastowej aktualizacji.

Użyj narzędzia clientVersionStalenessDays(), aby sprawdzić, ile dni minęło od udostępnienia aktualizacji w Sklepie 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.
    }
});

Sprawdź priorytet aktualizacji

Interfejs Google Play Developer API pozwala określić priorytet każdej aktualizacji. Dzięki temu aplikacja może zdecydować, jak zdecydowanie zalecić aktualizację użytkownikowi. Rozważ na przykład tę strategię ustalania priorytetu aktualizacji:

  • Drobne ulepszenia interfejsu użytkownika: aktualizacja o niskim priorytecie; nie wymaga elastycznych aktualizacji ani natychmiastowej aktualizacji. Aktualizuj tylko wtedy, gdy użytkownik nie wchodzi w interakcję z aplikacją.
  • Ulepszenia wydajności: aktualizacja o średnim priorytecie; prośba o elastyczną aktualizację.
  • Krytyczna aktualizacja zabezpieczeń: aktualizacja o wysokim priorytecie – prośba o natychmiastową aktualizację.

Aby określić priorytet, Google Play używa wartości całkowitej z zakresu od 0 do 5, gdzie 0 oznacza wartość domyślną, a 5 – najwyższy priorytet. Priorytet aktualizacji możesz ustawić w polu inAppUpdatePriority w polu Edits.tracks.releases w interfejsie Google Play Developer API. Wszystkie nowo dodane wersje w danej wersji są traktowane jako mające ten sam priorytet co wersja. Priorytet można ustawić tylko podczas wdrażania nowej wersji. Nie można go później zmienić.

Ustaw priorytet za pomocą interfejsu Google Play Developer API zgodnie z opisem w dokumentacji interfejsu Play Developer API. Priorytet aktualizacji w aplikacji należy określić w zasobie Edit.tracks przekazywanym w metodzie Edit.tracks: update. Poniższy przykład pokazuje publikowanie aplikacji z kodem wersji 88 i inAppUpdatePriority 5:

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

W kodzie aplikacji możesz sprawdzić priorytet danej aktualizacji, używając polecenia updatePriority(). Zwracany priorytet uwzględnia inAppUpdatePriority wszystkich kodów wersji aplikacji między wersją zainstalowaną a najnowszą dostępną wersją, niezależnie od ścieżki wersji. Przeanalizujmy ten scenariusz:

  • Publikujesz wersję 1 na ścieżce produkcyjnej bez priorytetu.
  • Publikujesz wersję 2 na ścieżce testu wewnętrznego o priorytecie 5.
  • Publikujesz wersję 3 na ścieżce produkcyjnej bez priorytetu.

Gdy użytkownicy wersji produkcyjnej zaktualizują aplikację z wersji 1 do wersji 3, uzyskają priorytet 5, mimo że wersja 2 została opublikowana na innej ścieżce.

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.
    }
});

Rozpocznij aktualizację

Po potwierdzeniu, że aktualizacja jest dostępna, możesz poprosić o nią za pomocą narzędzia 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());

Każdej instancji AppUpdateInfo można użyć do rozpoczęcia aktualizacji tylko raz. Aby w razie niepowodzenia ponowić aktualizację, poproś o nowy obiekt AppUpdateInfo i jeszcze raz sprawdź, czy aktualizacja jest dostępna i zezwolona.

Możesz zarejestrować program uruchamiający wyniki aktywności, korzystając z wbudowanej umowy ActivityResultContracts.StartIntentSenderForResult. Zapoznaj się z sekcją dotyczącą otrzymywania wywołania zwrotnego w przypadku stanu aktualizacji.

Kolejne kroki zależą od tego, czy chodzi o elastyczną aktualizację, czy o natychmiastową aktualizację.

Konfigurowanie aktualizacji przy użyciu AppUpdateOptions

AppUpdateOptions zawiera pole AllowAssetPackDeletion, które określa, czy aktualizacja może usuwać pakiety zasobów w przypadku ograniczonego miejsca na dane. To pole jest domyślnie ustawione na false, ale możesz zamiast tego ustawić wartość true za pomocą metody setAllowAssetPackDeletion():

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

Poproś o oddzwonienie, aby sprawdzić stan aktualizacji

Po rozpoczęciu aktualizacji wywołanie zwrotne launchera wyników zarejestrowanych działań otrzymuje wynik z oknem potwierdzenia:

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.
            }
        }
    });

Wywołanie zwrotne onActivityResult() może zawierać kilka wartości:

  • RESULT_OK: użytkownik zaakceptował aktualizację. Możesz nie otrzymać tego wywołania zwrotnego w przypadku natychmiastowych aktualizacji, ponieważ aktualizacja powinna już się zakończyć, gdy kontrola czasu jest zwracana do aplikacji.
  • RESULT_CANCELED: użytkownik odrzucił aktualizację lub ją anulował.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: inny błąd uniemożliwił użytkownikowi wyrażenie zgody lub kontynuację aktualizacji.

Elastyczna aktualizacja

Po rozpoczęciu elastycznej aktualizacji użytkownikowi najpierw wyświetli się okno z prośbą o zgodę. Jeśli użytkownik wyrazi zgodę, pobieranie rozpocznie się w tle, a użytkownik będzie mógł nadal korzystać z aplikacji. Z tej sekcji dowiesz się, jak monitorować i przeprowadzać elastyczną aktualizację w aplikacji.

Monitorowanie stanu elastycznej aktualizacji

Po rozpoczęciu pobierania elastycznej aktualizacji aplikacja musi monitorować stan aktualizacji, aby wiedzieć, kiedy można ją zainstalować, i wyświetlać postęp w interfejsie aplikacji.

Stan trwającej aktualizacji możesz monitorować, rejestrując detektor aktualizacji stanu instalacji. W interfejsie aplikacji możesz też umieścić pasek postępu, który będzie informował użytkowników o postępie pobierania.

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

Instalowanie elastycznej aktualizacji

Po wykryciu stanu InstallStatus.DOWNLOADED musisz ponownie uruchomić aplikację, aby zainstalować aktualizację.

W przeciwieństwie do szybkich aktualizacji Google Play nie powoduje automatycznego ponownego uruchomienia aplikacji w przypadku elastycznej aktualizacji. Wynika to z faktu, że podczas elastycznej aktualizacji użytkownik oczekuje, że będzie z niej korzystać, dopóki nie zdecyduje, że chce ją zainstalować.

Zaleca się powiadomienie użytkownika (lub inny komunikat w interfejsie) o tym, że aktualizacja jest gotowa do zainstalowania, oraz wysłanie prośby o potwierdzenie przed ponownym uruchomieniem aplikacji.

Ten przykład pokazuje wdrożenie paska powiadomień Material Design, który prosi użytkownika o potwierdzenie ponownego uruchomienia aplikacji:

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

Gdy wywołasz metodę appUpdateManager.completeUpdate() na pierwszym planie, platforma wyświetli pełnoekranowy interfejs, który uruchomi aplikację ponownie w tle. Gdy platforma zainstaluje aktualizację, aplikacja uruchomi się ponownie i zacznie swoje główne działanie.

Jeśli zamiast tego wywołujesz completeUpdate(), gdy aplikacja działa w tle, aktualizacja zostanie zainstalowana dyskretnie i nie zasłania interfejsu urządzenia.

Za każdym razem, gdy użytkownik uruchamia Twoją aplikację na pierwszym planie, sprawdź, czy nie ma aktualizacji czekającej na jej zainstalowanie. Jeśli aplikacja ma aktualizację w stanie DOWNLOADED, poproś użytkownika o jej zainstalowanie. W przeciwnym razie dane aktualizacji nadal będą zajmować miejsce w pamięci urządzenia użytkownika.

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

Natychmiastowa aktualizacja

Gdy uruchomisz natychmiastową aktualizację, a użytkownik wyrazi zgodę na jej rozpoczęcie, przez cały czas jej trwania Google Play będzie wyświetlać u góry UI aplikacji postęp aktualizacji. Jeśli użytkownik zamknie aplikację w trakcie aktualizacji, aktualizacja powinna być nadal pobierana i instalowana w tle bez dodatkowego potwierdzenia ze strony użytkownika.

Gdy jednak aplikacja wróci na pierwszy plan, sprawdź, czy aktualizacja nie jest wstrzymana w stanie UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Jeśli aktualizacja utknęła w tym stanie, wznów ją:

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

Proces aktualizacji zwraca wynik zgodny z opisem w dokumentacji referencyjnej metody startUpdateFlowForResult(). Aplikacja powinna w szczególności być w stanie obsługiwać przypadki, gdy użytkownik odrzuci aktualizację lub anuluje jej pobieranie. Gdy użytkownik wykona jedną z tych czynności, interfejs Google Play zostanie zamknięty. Aplikacja powinna określić najlepszy sposób postępowania.

Jeśli to możliwe, pozwól użytkownikowi kontynuować bez aktualizacji i poproś go o to później. Jeśli Twoja aplikacja bez aktualizacji nie będzie działać bez aktualizacji, przed ponownym uruchomieniem procesu aktualizacji warto wyświetlić komunikat z informacją na ten temat lub poprosić użytkownika o jej zamknięcie. Dzięki temu będzie on wiedział, że gdy będzie gotowy do zainstalowania wymaganej aktualizacji, może ponownie uruchomić aplikację.

Dalsze kroki

Przetestuj aktualizacje aplikacji, aby sprawdzić, czy integracja działa poprawnie.