Obsługa aktualizacji w aplikacji (Kotlin lub Java)

Z tego przewodnika dowiesz się, jak obsługiwać aktualizacje w aplikacji w aplikacji napisanej w Kotlinie lub Javie. Istnieją osobne przewodniki dla przypadków, w których implementacja korzysta z kodu natywnego (C/C++), oraz dla przypadków, w których implementacja korzysta z 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 to zależności 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")
    ...
}

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 elementy:

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

Sprawdzanie aktualności aktualizacji

Oprócz sprawdzania, 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ć elastyczną aktualizację czy aktualizację natychmiastową. Na przykład możesz odczekać kilka dni, zanim powiadomisz użytkownika o elastycznej aktualizacji, a potem kilka dni, zanim zażądasz natychmiastowej aktualizacji.

Użyj funkcji clientVersionStalenessDays(), aby sprawdzić liczbę dni 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 mocno zalecać użytkownikowi aktualizację. Rozważ na przykład tę strategię ustawiania priorytetu aktualizacji:

  • Drobne ulepszenia interfejsu: aktualizacja o niskim priorytecie, która nie wymaga elastycznej ani natychmiastowej aktualizacji. Aktualizuj tylko wtedy, gdy użytkownik nie wchodzi w interakcję z aplikacją.
  • Poprawa wydajności: aktualizacja o średnim priorytecie; poproś o elastyczną aktualizację.
  • Krytyczna aktualizacja zabezpieczeń: aktualizacja o wysokim priorytecie; natychmiast poproś o aktualizację.

Aby określić priorytet, Google Play używa liczby całkowitej z zakresu od 0 do 5, przy czym 0 to wartość domyślna, a 5 – 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 taki sam priorytet jak 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ć poziom priorytetu danej aktualizacji za pomocą funkcji updatePriority(). Zwrócony priorytet uwzględnia inAppUpdatePriority dla wszystkich kodów wersji aplikacji między zainstalowaną wersją a najnowszą dostępną wersją, niezależnie od ścieżki publikacji. Rozważmy na przykład ten scenariusz:

  • Publikujesz wersję 1 na ścieżce produkcyjnej bez priorytetu.
  • Wdrażasz wersję 2 na ścieżce testów wewnętrznych z priorytetem 5.
  • Publikujesz wersję 3 na ścieżce produkcyjnej bez priorytetu.

Gdy użytkownicy wersji produkcyjnej zaktualizują aplikację z wersji 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.
    }
});

Rozpoczęcie aktualizacji

Gdy potwierdzisz, że aktualizacja jest dostępna, możesz poprosić o jej przesłanie, 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ć do rozpoczęcia aktualizacji tylko raz. Jeśli aktualizacja się nie powiedzie, spróbuj ponownie, wysyłając nowe żądanie AppUpdateInfo i sprawdzając, czy aktualizacja jest dostępna i dozwolona.

Możesz zarejestrować moduł uruchamiający wynik działania za pomocą wbudowanego kontraktu ActivityResultContracts.StartIntentSenderForResult. Zapoznaj się z sekcją otrzymywania wywołania zwrotnego w celu sprawdzenia stanu aktualizacji.

Dalsze czynności zależą od tego, czy prosisz o elastyczną aktualizację czy natychmiastową aktualizację.

Konfigurowanie aktualizacji za pomocą klasy 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. Domyślnie to pole jest 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());

Otrzymywanie informacji o stanie aktualizacji

Po rozpoczęciu aktualizacji zarejestrowane wywołanie zwrotne uruchamiające 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 natychmiastowych aktualizacji możesz nie otrzymać tego wywołania zwrotnego, ponieważ aktualizacja powinna być już zakończona, zanim kontrola zostanie przekazana z powrotem do Twojej aplikacji.
  • RESULT_CANCELED: użytkownik odrzucił lub anulował aktualizację.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: wystąpił inny błąd, który uniemożliwił użytkownikowi wyrażenie zgody lub uniemożliwił przeprowadzenie aktualizacji.

Obsługa elastycznej aktualizacji

Gdy rozpoczynasz elastyczną aktualizację, użytkownikowi najpierw wyświetla 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. W tej sekcji opisujemy, 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 odbiornik aktualizacji stanu instalacji. Możesz też wyświetlać w interfejsie aplikacji pasek postępu, 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 elastycznej aktualizacji

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

W przeciwieństwie do aktualizacji natychmiastowych Google Play nie powoduje automatycznego ponownego uruchomienia aplikacji 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świetlanie powiadomienia (lub innego elementu interfejsu), które informuje użytkownika, że aktualizacja jest gotowa do zainstalowania, i prosi o potwierdzenie przed ponownym uruchomieniem aplikacji.

Poniższy przykład pokazuje implementację 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 appUpdateManager.completeUpdate() na pierwszym planie, platforma wyświetli interfejs na pełnym ekranie, który ponownie uruchomi aplikację w tle. Po zainstalowaniu aktualizacji przez platformę aplikacja uruchomi się ponownie w głównym działaniu.

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

Za każdym razem, gdy użytkownik przeniesie aplikację na pierwszy plan, sprawdź, czy oczekuje ona na aktualizację. Jeśli aplikacja ma aktualizację w stanie DOWNLOADED, poproś użytkownika o jej zainstalowanie. W przeciwnym razie dane aktualizacji nadal będą zajmować miejsce na urządzeniu 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 na interfejsie aplikacji przez cały czas jej trwania. Jeśli użytkownik zamknie lub zakończy działanie aplikacji podczas aktualizacji, powinna ona nadal pobierać i instalować się w tle bez dodatkowego potwierdzenia ze strony użytkownika.

Gdy jednak aplikacja wróci na pierwszy plan, sprawdź, czy aktualizacja nie utknęła w stanie UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Jeśli aktualizacja utknęła w tym stanie, wznow 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 funkcji startUpdateFlowForResult(). W szczególności aplikacja powinna obsługiwać przypadki, w których użytkownik odrzuci 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 ponownie wyświetl mu prośbę później. Jeśli aplikacja nie może działać bez aktualizacji, rozważ wyświetlenie informacyjnego komunikatu przed ponownym uruchomieniem procesu aktualizacji lub poproś użytkownika o zamknięcie aplikacji. Dzięki temu użytkownik zrozumie, ż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.