В этом руководстве описывается, как поддерживать обновления внутри приложения, используя Kotlin или Java. Существуют отдельные руководства для случаев, когда ваша реализация использует нативный код (C/C++) , и для случаев, когда ваша реализация использует Unity или Unreal Engine .
Настройте среду разработки.
Библиотека обновлений приложений Play является частью основных библиотек Google Play . Для интеграции библиотеки обновлений приложений Play добавьте следующую зависимость Gradle.
Классный
// 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. } }
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. } });
Возвращаемый экземпляр 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. } }
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. } });
Проверить приоритет обновления
API для разработчиков Google Play позволяет устанавливать приоритет каждого обновления. Это позволяет вашему приложению решать, насколько настоятельно рекомендовать пользователю обновление. Например, рассмотрим следующую стратегию установки приоритета обновлений:
- Незначительные улучшения пользовательского интерфейса: обновление с низким приоритетом ; не запрашивайте ни гибкое, ни немедленное обновление. Обновляйте приложение только тогда, когда пользователь с ним не взаимодействует.
- Улучшения производительности: обновление со средним приоритетом ; запросите гибкое обновление.
- Критическое обновление безопасности: обновление с высоким приоритетом ; запросите немедленное обновление.
Для определения приоритета Google Play использует целочисленное значение от 0 до 5, где 0 — значение по умолчанию, а 5 — наивысший приоритет. Чтобы установить приоритет обновления, используйте поле inAppUpdatePriority в разделе Edits.tracks.releases в Google Play Developer API. Все вновь добавленные версии в релизе считаются имеющими тот же приоритет, что и сам релиз. Приоритет можно установить только при выпуске нового релиза, и его нельзя изменить позже.
Установите приоритет, используя 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. } }
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. } });
Начать обновление
После подтверждения наличия обновления вы можете запросить его с помощью 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())
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());
Каждый экземпляр 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())
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());
Запросите обратный звонок для уточнения статуса.
После начала обновления функция обратного вызова для запуска результатов зарегистрированных действий получает результат диалогового окна подтверждения:
Котлин
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. } } });
Из функции обратного вызова 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)
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);
Установите гибкое обновление
Если вы обнаружили состояние 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() } }
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(); }
При вызове метода 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() } } }
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(); } }); }
Выполните немедленное обновление.
Когда вы запускаете немедленное обновление и пользователь дает согласие на его начало, 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()) } } }
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()); } }); }
Процесс обновления возвращает результат, как описано в справочной документации для startUpdateFlowForResult() . В частности, ваше приложение должно уметь обрабатывать случаи, когда пользователь отклоняет обновление или отменяет загрузку. При выполнении пользователем любого из этих действий пользовательский интерфейс Google Play закрывается. Ваше приложение должно определить наилучший способ дальнейших действий.
По возможности, позвольте пользователю продолжить работу без обновления и предложите ему сделать это позже. Если ваше приложение не может функционировать без обновления, рассмотрите возможность отображения информационного сообщения перед перезапуском процесса обновления или предложения пользователю закрыть приложение. Таким образом, пользователь поймет, что он может перезапустить приложение, когда будет готов установить необходимое обновление.
Следующие шаги
Проверьте обновления внутри приложения, чтобы убедиться в корректной работе интеграции.