Obsługa aktualizacji w aplikacji (Kotlin lub Java)

Z tego przewodnika dowiesz się, jak obsługiwać aktualizacje w aplikacji w języku Kotlin lub Java. Dostępne są osobne przewodniki dotyczące przypadków, w których implementacja używa kodu natywnego (C/C++), oraz przypadków, w których implementacja używa Unity lub Unreal Engine.

Konfigurowanie środowiska programistycznego

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

Dynamiczny

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

Sprawdzanie dostępności aktualizacji

Zanim poprosisz o aktualizację, sprawdź, czy jest ona dostępna dla Twojej aplikacji. Aby sprawdzić, czy jest dostępna aktualizacja, użyj 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.
    }
});

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

  • Jeśli aktualizacja jest dostępna i dozwolona, instancja zawiera też intencję rozpoczęcia aktualizacji.
  • Jeśli aktualizacja aplikacji jest już w toku, instancja zgłasza też stan tej aktualizacji.

Sprawdzanie aktualności aktualizacji

Oprócz sprawdzenia, czy aktualizacja jest dostępna, możesz też sprawdzić, ile czasu minęło od ostatniego powiadomienia użytkownika o aktualizacji w Sklepie Play. Pomoże Ci to zdecydować, czy zainicjować aktualizację elastyczną czy natychmiastową. Możesz na przykład odczekać kilka dni, zanim powiadomisz użytkownika o aktualizacji elastycznej, i kolejne kilka dni, zanim zażądasz natychmiastowej aktualizacji.

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

Sprawdzanie priorytetu aktualizacji

Interfejs Google Play Developer API umożliwia ustawienie priorytetu każdej aktualizacji. Dzięki temu aplikacja może określić, jak bardzo zalecać użytkownikowi aktualizację. Rozważmy na przykład taką strategię ustawiania priorytetu aktualizacji:

  • Drobne ulepszenia interfejsu: aktualizacja o niskim priorytecie; nie proś o aktualizację elastyczną ani natychmiastową. Aktualizuj tylko wtedy, gdy użytkownik nie korzysta z aplikacji.
  • Ulepszenia wydajności: aktualizacja o średnim priorytecie; poproś o aktualizację elastyczną.
  • Krytyczna aktualizacja zabezpieczeń: aktualizacja o wysokim priorytecie; prośba o natychmiastową aktualizację.

Do określenia priorytetu Google Play używa liczby całkowitej z zakresu od 0 do 5, gdzie 0 to wartość domyślna, a 5 to najwyższy priorytet. Aby ustawić priorytet aktualizacji, użyj pola inAppUpdatePriority w sekcji Edits.tracks.releases w interfejsie Google Play Developer API. Wszystkie nowo dodane wersje w wersji są traktowane jako mające ten sam priorytet co wersja. Priorytet można ustawić tylko podczas wdrażania nowej wersji i 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 przekazanym w metodzie Edit.tracks: update. Poniższy przykład pokazuje, jak opublikować aplikację z kodem wersji 88 i inAppUpdatePriority 5:

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

W kodzie aplikacji możesz sprawdzić poziom priorytetu danej aktualizacji, używając updatePriority(). Zwrócony priorytet uwzględnia wartość inAppUpdatePriority dla wszystkich kodów wersji aplikacji między zainstalowaną wersją a najnowszą dostępną wersją, niezależnie od ścieżki wersji. Rozważmy na przykład taką sytuację:

  • Wersję 1 publikujesz na ścieżce produkcyjnej bez priorytetu.
  • Wersję 2 publikujesz na ścieżce testu wewnętrznego z priorytetem 5.
  • Wersję 3 publikujesz na ścieżce produkcyjnej bez priorytetu.

Gdy użytkownicy wersji produkcyjnej zaktualizują wersję 1 do wersji 3, otrzymają 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.
    }
});

Rozpoczynanie aktualizacji

Gdy potwierdzisz, że aktualizacja jest dostępna, możesz poprosić o jej zainstalowanie za pomocą 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 ponowić próbę aktualizacji w przypadku niepowodzenia, poproś o nowy AppUpdateInfo i ponownie sprawdź, czy aktualizacja jest dostępna i dozwolona.

Możesz zarejestrować moduł uruchamiający wynik aktywności, korzystając z wbudowanej umowy ActivityResultContracts.StartIntentSenderForResult. Zapoznaj się z sekcją dotyczącą odbierania wywołania zwrotnego ze stanem aktualizacji.

Dalsze kroki zależą od tego, czy prosisz o aktualizację elastyczną czy natychmiastową.

Konfigurowanie aktualizacji za pomocą AppUpdateOptions

AppUpdateOptions zawiera pole AllowAssetPackDeletion, które określa, czy aktualizacja może wyczyścić pakiety zasobów w przypadku ograniczonej ilości pamięci urządzenia. To pole jest domyślnie ustawione na false, ale możesz użyć metody setAllowAssetPackDeletion(), aby ustawić je na 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());

Oddzwoń, aby sprawdzić stan aktualizacji

Po rozpoczęciu aktualizacji zarejestrowane wywołanie zwrotne programu uruchamiającego wynik aktywności otrzymuje wynik okna 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 zwrócić kilka wartości:

  • RESULT_OK: użytkownik zaakceptował aktualizację. W przypadku aktualizacji natychmiastowych możesz nie otrzymać tego wywołania zwrotnego, ponieważ aktualizacja powinna już zostać zakończona, zanim kontrola zostanie przekazana z powrotem do aplikacji.
  • RESULT_CANCELED: użytkownik odrzucił lub anulował aktualizację.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: inny błąd uniemożliwił użytkownikowi wyrażenie zgody lub aktualizację.

Obsługa aktualizacji elastycznej

Gdy rozpoczynasz elastyczną aktualizację, najpierw pojawia się okno z prośbą o zgodę użytkownika. Jeśli użytkownik wyrazi zgodę, pobieranie rozpocznie się w tle, a użytkownik będzie mógł dalej korzystać z aplikacji. W tej sekcji opisano, 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 aktualizacji w toku możesz sprawdzać, rejestrując detektor aktualizacji stanu instalacji. Możesz też dodać pasek postępu w interfejsie aplikacji, aby informować użytkowników o postępach 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 aktualizacji elastycznej

Gdy wykryjesz stan InstallStatus.DOWNLOADED, musisz ponownie uruchomić aplikację, aby zainstalować aktualizację.

W przeciwieństwie do aktualizacji natychmiastowych Google Play nie uruchamia automatycznie aplikacji ponownie w przypadku aktualizacji elastycznej. Dzieje się tak, ponieważ podczas elastycznej aktualizacji użytkownik oczekuje, że będzie mógł nadal korzystać z aplikacji, dopóki nie zdecyduje, że chce zainstalować aktualizację.

Zalecamy wyświetlenie powiadomienia (lub innego elementu interfejsu), aby poinformować użytkownika, że aktualizacja jest gotowa do zainstalowania, i poprosić o potwierdzenie przed ponownym uruchomieniem aplikacji.

Poniższy przykład pokazuje, jak zaimplementować pasek 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 appUpdateManager.completeUpdate() na pierwszym planie, platforma wyświetli interfejs pełnoekranowy, który ponownie uruchomi aplikację w tle. Po zainstalowaniu aktualizacji przez platformę aplikacja uruchomi się ponownie w głównej aktywności.

Jeśli zamiast tego wywołasz metodę completeUpdate(), gdy aplikacja działa w tle, aktualizacja zostanie zainstalowana dyskretnie, bez zasłaniania interfejsu urządzenia.

Za każdym razem, gdy użytkownik przenosi aplikację na pierwszy plan, sprawdź, czy nie czeka na nią aktualizacja. Jeśli aplikacja ma aktualizację w stanie DOWNLOADED, poproś użytkownika o jej zainstalowanie. W przeciwnym razie dane aktualizacji będą nadal zajmować pamięć 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();
              }
          });
}

Obsługa natychmiastowej aktualizacji

Gdy rozpoczniesz natychmiastową aktualizację, a użytkownik wyrazi zgodę na jej rozpoczęcie, Google Play będzie wyświetlać postęp aktualizacji nad interfejsem aplikacji przez cały czas jej trwania. Jeśli użytkownik zamknie lub zakończy działanie aplikacji podczas aktualizacji, powinna ona nadal pobierać się i instalować w tle bez dodatkowego potwierdzenia ze strony użytkownika.

Gdy jednak aplikacja wróci na pierwszy plan, sprawdź, czy aktualizacja nie została zatrzymana 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 opisany w dokumentacji referencyjnej startUpdateFlowForResult(). W szczególności aplikacja powinna obsługiwać przypadki, w których użytkownik odrzuca aktualizację lub anuluje 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 później ponownie wyświetl prośbę. Jeśli aplikacja nie może działać bez aktualizacji, rozważ wyświetlenie komunikatu informacyjnego przed ponownym uruchomieniem procesu aktualizacji lub poproszeniem użytkownika o zamknięcie aplikacji. Dzięki temu użytkownik będzie wiedział, że może ponownie uruchomić aplikację, gdy będzie gotowy do zainstalowania wymaganej aktualizacji.

Dalsze kroki

Przetestuj aktualizacje w aplikacji, aby sprawdzić, czy integracja działa prawidłowo.