Поддержка обновлений внутри приложения (Kotlin или Java), Поддержка обновлений внутри приложения (Kotlin или Java)

В этом руководстве описывается, как поддерживать обновления внутри приложения с помощью Kotlin или Java. Существуют отдельные руководства для случаев, когда ваша реализация использует собственный код (C/C++), и случаев, когда ваша реализация использует Unity .

Настройте среду разработки

Библиотека обновлений внутри приложения Play является частью библиотек Google Play Core . Пожалуйста, включите следующую зависимость Gradle для интеграции библиотеки обновлений Play в приложении.

классный

// 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'
    ...
}

Котлин

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

Проверьте наличие обновлений

Прежде чем запрашивать обновление, проверьте, доступно ли обновление для вашего приложения. Используйте AppUpdateManager для проверки наличия обновлений:

Котлин

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

Ява

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

Возвращенный экземпляр AppUpdateInfo содержит состояние доступности обновлений. В зависимости от статуса обновления экземпляр также содержит следующее:

  • Если обновление доступно и обновление разрешено, экземпляр также содержит намерение запустить обновление.
  • Если обновление в приложении уже выполняется, экземпляр также сообщает о состоянии текущего обновления.

Проверка устаревания обновлений

Помимо проверки доступности обновления, вы также можете проверить, сколько времени прошло с тех пор, как пользователь в последний раз был уведомлен об обновлении через Play Store. Это может помочь вам решить, следует ли вам инициировать гибкое обновление или немедленное обновление. Например, вы можете подождать несколько дней, прежде чем уведомить пользователя о гибком обновлении, и еще несколько дней после этого, прежде чем требовать немедленного обновления.

Используйте clientVersionStalenessDays() чтобы проверить количество дней с момента появления обновления в Play Store:

Котлин

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

Ява

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

Проверьте приоритет обновления

API разработчика Google Play позволяет вам устанавливать приоритет каждого обновления. Это позволяет вашему приложению решать, насколько настоятельно рекомендовать обновление пользователю. Например, рассмотрим следующую стратегию установки приоритета обновления:

  • Незначительные улучшения пользовательского интерфейса: Обновление с низким приоритетом ; не запрашивайте ни гибкого обновления, ни немедленного обновления. Обновляйте только тогда, когда пользователь не взаимодействует с вашим приложением.
  • Улучшения производительности: обновление среднего приоритета ; запросите гибкое обновление.
  • Критическое обновление безопасности: Высокоприоритетное обновление; запросите немедленное обновление.

Для определения приоритета Google Play использует целочисленное значение от 0 до 5, где 0 — значение по умолчанию, а 5 — наивысший приоритет. Чтобы установить приоритет обновления, используйте поле inAppUpdatePriority в разделе Edits.tracks.releases в API разработчика Google Play. Все новые версии, добавленные в выпуск, считаются имеющими тот же приоритет, что и сам выпуск. Приоритет можно установить только при выпуске новой версии и нельзя изменить позже.

Установите приоритет с помощью Google Play Developer API, как описано в документации Play Developer API . Приоритет обновления внутри приложения должен быть указан в ресурсе Edit.tracks передаваемом в методе Edit.tracks: update . В следующем примере демонстрируется выпуск приложения с кодом версии 88 и inAppUpdatePriority 5:

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

В коде вашего приложения вы можете проверить уровень приоритета для данного обновления с помощью updatePriority() . Возвращаемый приоритет учитывает inAppUpdatePriority для всех кодов версий приложения между установленной версией и последней доступной версией, независимо от версии выпуска. Например, рассмотрим следующий сценарий:

  • Вы выпускаете версию 1 в производственную версию без приоритета.
  • Вы выпускаете версию 2 на внутреннюю тестовую версию с приоритетом 5.
  • Вы выпускаете версию 3 в производственную версию без приоритета.

Когда рабочие пользователи обновятся с версии 1 до версии 3, они получат приоритет 5, даже если версия 2 была опубликована на другой дорожке.

Котлин

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

Ява

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

Начать обновление

После того, как вы подтвердите, что обновление доступно, вы можете запросить его с помощью AppUpdateManager.startUpdateFlowForResult() :

Котлин

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

Ява

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

Каждый экземпляр AppUpdateInfo можно использовать для запуска обновления только один раз. Чтобы повторить попытку обновления в случае сбоя, запросите новый AppUpdateInfo и еще раз проверьте, что обновление доступно и разрешено.

Вы можете зарегистрировать средство запуска результатов активности с помощью встроенного контракта ActivityResultContracts.StartIntentSenderForResult . Проверьте раздел об обратном вызове для получения статуса обновления .

Следующие шаги зависят от того, запрашиваете ли вы гибкое обновление или немедленное обновление .

Настройте обновление с помощью AppUpdateOptions.

AppUpdateOptions содержит поле AllowAssetPackDeletion , которое определяет, разрешено ли обновлению очищать пакеты ресурсов в случае ограниченного объема памяти устройства. По умолчанию для этого поля установлено значение false , но вместо этого вы можете использовать метод setAllowAssetPackDeletion() чтобы установить для него значение true :

Котлин

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

Ява

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

Получите обратный звонок для получения статуса обновления

После запуска обновления обратный вызов средства запуска зарегистрированных результатов активности получает результат диалогового окна подтверждения:

Котлин

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

Ява

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

Существует несколько значений, которые вы можете получить от обратного вызова onActivityResult() :

  • RESULT_OK : пользователь принял обновление. Для немедленных обновлений вы можете не получить этот обратный вызов, поскольку обновление уже должно быть завершено к тому моменту, когда управление временем будет возвращено вашему приложению.
  • RESULT_CANCELED : пользователь отклонил или отменил обновление.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED : какая-то другая ошибка не позволила пользователю предоставить согласие или продолжить обновление.

Обеспечьте гибкое обновление

При запуске гибкого обновления пользователю сначала открывается диалоговое окно для запроса согласия. Если пользователь дает согласие, загрузка начинается в фоновом режиме, и пользователь может продолжать взаимодействовать с вашим приложением. В этом разделе описывается, как отслеживать и выполнять гибкое обновление внутри приложения.

Отслеживайте состояние гибкого обновления

После начала загрузки гибкого обновления вашему приложению необходимо отслеживать состояние обновления, чтобы знать, когда обновление можно будет установить, и отображать ход выполнения в пользовательском интерфейсе вашего приложения.

Вы можете отслеживать состояние выполняемого обновления, зарегистрировав прослушиватель обновлений состояния установки. Вы также можете разместить индикатор выполнения в пользовательском интерфейсе приложения, чтобы информировать пользователей о ходе загрузки.

Котлин

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

Ява

// 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 не запускает автоматический перезапуск приложения для гибкого обновления. Это связано с тем, что во время гибкого обновления пользователь ожидает, что продолжит взаимодействовать с приложением до тех пор, пока не решит, что хочет установить обновление.

Рекомендуется предоставить уведомление (или какое-либо другое указание пользовательского интерфейса), чтобы проинформировать пользователя о том, что обновление готово к установке, и запросить подтверждение перед перезапуском приложения.

В следующем примере демонстрируется реализация панели снэк-бара Material Design , которая запрашивает у пользователя подтверждение перезапуска приложения:

Котлин

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

Ява

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

Когда вы вызываете appUpdateManager.completeUpdate() на переднем плане, платформа отображает полноэкранный пользовательский интерфейс, который перезапускает приложение в фоновом режиме. После того как платформа установит обновление, ваше приложение перезапустится и начнет свою основную деятельность.

Если вместо этого вы вызываете completeUpdate() когда ваше приложение находится в фоновом режиме , обновление устанавливается автоматически, не закрывая пользовательский интерфейс устройства.

Всякий раз, когда пользователь выводит ваше приложение на передний план, проверьте, есть ли в вашем приложении обновление, ожидающее установки. Если в вашем приложении есть обновление в состоянии DOWNLOADED , предложите пользователю установить это обновление. В противном случае данные обновления продолжают занимать память устройства пользователя.

Котлин

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

Ява

// 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 отображает ход обновления поверх пользовательского интерфейса вашего приложения на протяжении всего времени обновления. Если пользователь закроет или завершит работу вашего приложения во время обновления, обновление продолжит загрузку и установку в фоновом режиме без дополнительного подтверждения пользователя.

Однако когда ваше приложение вернется на передний план, вам следует убедиться, что обновление не остановлено в состоянии UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS . Если обновление зависло в этом состоянии, возобновите обновление:

Котлин

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

Ява

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

Поток обновления возвращает результат, как описано в справочной документации для startUpdateFlowForResult() . В частности, ваше приложение должно иметь возможность обрабатывать случаи, когда пользователь отклоняет обновление или отменяет загрузку. Когда пользователь выполняет любое из этих действий, пользовательский интерфейс Google Play закрывается. Ваше приложение должно определить лучший способ действий.

Если возможно, позвольте пользователю продолжить работу без обновления и запросите его снова позже. Если ваше приложение не может работать без обновления, рассмотрите возможность отображения информационного сообщения перед перезапуском процесса обновления или предложением пользователю закрыть приложение. Таким образом, пользователь понимает, что он может перезапустить ваше приложение, когда будет готов установить необходимое обновление.

Следующие шаги

Проверьте обновления внутри приложения, чтобы убедиться, что ваша интеграция работает правильно.