Obsługa aktualizacji w aplikacji (Kotlin lub Java)

Z tego przewodnika dowiesz się, jak obsługiwać aktualizacje w aplikacji za pomocą języka Kotlin lub Java. Istnieją osobne przewodniki dotyczące przypadków, w których implementacja używa kodu natywnego (C/C++) i języka Unity.

Konfigurowanie środowiska programistycznego

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

Odlotowy

// 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. Użyj AppUpdateManager, aby 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 też intencję do rozpoczęcia aktualizacji.
  • Jeśli trwa już aktualizacja w aplikacji, instancja informuje też o jej stanie.

Sprawdzanie braku aktualizacji aktualizacji

Oprócz sprawdzenia, czy jest dostępna aktualizacja, możesz też sprawdzić, ile czasu upłynęło od ostatniego powiadomienia użytkownika o niej w Sklepie Play. Pomoże Ci to zdecydować, czy należy przeprowadzić aktualizację elastyczną czy natychmiastową. Możesz na przykład odczekać kilka dni, zanim powiadomimy użytkownika o elastycznej aktualizacji, a potem kilka dni później.

Na stronie clientVersionStalenessDays() możesz sprawdzić, ile dni upłynęł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 umożliwia określenie priorytetu każdej aktualizacji. Dzięki temu aplikacja może zdecydować, jak mocno zarekomendować użytkownikowi aktualizację. Oto przykładowa strategia ustawiania priorytetu aktualizacji:

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

Google Play określa priorytet przy użyciu liczby całkowitej z zakresu od 0 do 5, gdzie 0 to wartość domyślna, a 5 – najwyższy. Aby ustawić priorytet aktualizacji, użyj pola inAppUpdatePriority w polu Edits.tracks.releases w interfejsie Google Play Developer API. Wszystkie nowo dodane wersje w danej wersji mają ten sam priorytet. Priorytetu można ustawić tylko podczas wdrażania nowej wersji. Nie można go później zmienić.

Ustaw priorytet, korzystając z 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. Ten przykład przedstawia publikowanie aplikacji z kodami wersji 88 i inAppUpdatePriority 5:

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

W kodzie aplikacji możesz sprawdzić priorytet danej aktualizacji za pomocą funkcji updatePriority(). Zwracany priorytet uwzględnia inAppUpdatePriority w przypadku wszystkich kodów wersji aplikacji między wersją zainstalowaną a najnowszą dostępną wersją, niezależnie od ścieżki wersji. Weźmy na przykład taki scenariusz:

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

Gdy użytkownicy, którzy przejdą z wersji 1 na 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 jej aktualizację, korzystając z 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ć tylko do jednego uruchomienia aktualizacji. Aby w przypadku niepowodzenia ponowić próbę aktualizacji, poproś o nowy AppUpdateInfo i ponownie sprawdź, czy aktualizacja jest dostępna i dozwolona.

Menu z wynikami działań możesz zarejestrować za pomocą wbudowanej umowy ActivityResultContracts.StartIntentSenderForResult. Zapoznaj się z sekcją na temat uzyskiwania wywołania zwrotnego w celu sprawdzenia stanu aktualizacji.

Kolejne kroki zależą od tego, czy prosisz o elastyczną aktualizację czy 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 ograniczonej ilości miejsca na urządzeniu. To pole jest domyślnie ustawione na false, ale możesz użyć metody setAllowAssetPackDeletion(), aby ustawić tę wartość 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());

Poproś o oddzwonienie, aby poznać stan aktualizacji

Po rozpoczęciu aktualizacji wywołanie zwrotne zarejestrowanego wyniku aktywności otrzyma wynik w oknie 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 mieć kilka wartości:

  • RESULT_OK: użytkownik zaakceptował aktualizację. Możesz nie otrzymać tego wywołania zwrotnego, ponieważ aktualizacja powinna się już zakończyć w momencie, gdy zostanie przyznany aplikacji.
  • RESULT_CANCELED: użytkownik odrzucił lub anulował aktualizację.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: Jakiś inny błąd uniemożliwił użytkownikowi udzielenie zgody lub kontynuację aktualizacji.

Elastyczna aktualizacja

Gdy rozpoczniesz elastyczną aktualizację, najpierw wyświetli się użytkownikowi okno z prośbą o zgodę. Jeśli użytkownik wyrazi zgodę, pobieranie rozpocznie się w tle i będzie można kontynuować korzystanie z aplikacji. Z tej sekcji dowiesz się, jak monitorować i wykonywać elastyczne aktualizacje w aplikacji.

Monitorowanie stanu aktualizacji elastycznej

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

Możesz monitorować stan trwającej aktualizacji, 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 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 ponownego uruchamiania aplikacji w celu wykonania aktualizacji elastycznej. Dzieje się tak, ponieważ użytkownik oczekuje, że w trakcie tej aktualizacji będzie nadal korzystać z aplikacji, dopóki nie zdecyduje się na jej zainstalowanie.

Przed ponownym uruchomieniem aplikacji zalecamy wysłanie powiadomienia (lub innego oznaczenia w interfejsie) z informacją, że aktualizacja jest gotowa do zainstalowania, oraz poproszenie użytkownika o potwierdzenie.

Ten przykład pokazuje implementację paska powiadomień Material Design, który wymaga od użytkownika potwierdzenia 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 spowoduje ponowne uruchomienie aplikacji w tle. Po zainstalowaniu aktualizacji przez platformę nastąpi ponowne uruchomienie aplikacji i skorzystanie z głównej aktywności.

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

Za każdym razem, gdy użytkownik ustawi aplikację na pierwszym planie, sprawdź, czy ma jej aktualizację, która czeka na zainstalowanie. Jeśli Twoja aplikacja ma stan DOWNLOADED, poproś użytkownika o jej zainstalowanie. W przeciwnym razie dane aktualizacji nadal będą 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();
              }
          });
}

Natychmiastowa aktualizacja

Gdy rozpoczniesz 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 interfejsu użytkownika postęp aktualizacji. Jeśli użytkownik zamknie lub zamknie aplikację podczas jej aktualizacji, powinna ona być nadal pobierana i instalowana w tle bez dodatkowego potwierdzenia.

Jednak gdy aplikacja wraca do pierwszego planu, sprawdź, czy aktualizacja nie jest wstrzymywana w stanie UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Jeśli aktualizacja jest opóźniona w takim 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 zgodnie z opisem w dokumentacji referencyjnej funkcji startUpdateFlowForResult(). W szczególności aplikacja powinna być w stanie obsługiwać sytuacje, w których użytkownik odrzuca aktualizację lub pobieranie. Gdy użytkownik wykona jedną z tych czynności, interfejs Google Play zostanie zamknięty. Aplikacja powinna określać najlepszy sposób postępowania.

Jeśli to możliwe, pozwól użytkownikowi kontynuować bez aktualizacji i poproś o ponowne wyświetlenie później. Jeśli aplikacja nie może działać bez aktualizacji, przed ponownym uruchomieniem aktualizacji warto wyświetlić odpowiednią wiadomość lub poprosić użytkownika o jej zamknięcie. Dzięki temu użytkownik będzie rozumieć, że może ponownie uruchomić aplikację, gdy będzie gotowy do zainstalowania wymaganej aktualizacji.

Dalsze kroki

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