В этом руководстве рассказывается, как реализовать поддержку обновлений в приложении на Kotlin или Java. У нас есть отдельные руководства для случаев, когда в реализации используется нативный код (C/C++), Unity или Unreal Engine.
Как настроить среду разработки
Библиотека Play In-App Update входит в состав библиотек Google Play Core. Чтобы интегрировать библиотеку Play In-App Update, добавьте следующую зависимость 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' ... }
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") ... }
Проверьте наличие обновлений
Прежде чем запрашивать обновление, проверьте, доступно ли оно для вашего приложения.
Для этого используйте 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. } });
Возвращенный экземпляр AppUpdateInfo содержит статус доступности обновления. В зависимости от статуса обновления экземпляр также содержит следующее:
- Если обновление доступно и разрешено, экземпляр также содержит намерение запустить обновление.
- Если обновление в приложении уже выполняется, экземпляр также сообщает о его статусе.
Проверка актуальности обновления
Помимо проверки наличия обновления, вы также можете проверить, сколько времени прошло с момента последнего уведомления пользователя об обновлении через Google Play. Это поможет вам решить, какое обновление запустить: гибкое или немедленное. Например, вы можете подождать несколько дней, прежде чем уведомить пользователя о гибком обновлении, и ещё несколько дней, прежде чем потребовать немедленного обновления.
Используйте clientVersionStalenessDays(), чтобы узнать, сколько дней прошло с момента, когда обновление стало доступно в Google 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. } });
Проверить приоритет обновления
Google Play Developer API позволяет задавать приоритет для каждого обновления. Это позволяет приложению решить, насколько настойчиво рекомендовать пользователю обновление. Например, можно использовать следующую стратегию:
- Незначительные улучшения интерфейса: низкий приоритет. Не запрашивайте ни гибкое, ни срочное обновление. Обновляйте виджет, только когда пользователь не взаимодействует с приложением.
- Улучшение производительности. Средний приоритет. Запросите гибкое обновление.
- Критическое обновление системы безопасности – высокий приоритет, требуется немедленное обновление.
Для определения приоритета 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 была опубликована в другом типе версии.
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. } });
Как начать обновление
Если обновление доступно, вы можете запросить его с помощью 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());
Каждый экземпляр AppUpdateInfo можно использовать для запуска обновления только один раз. Чтобы повторить попытку обновления в случае сбоя, запросите новый AppUpdateInfo и снова проверьте, доступно ли обновление и разрешено ли оно.
Вы можете зарегистрировать средство запуска результатов объекта activity с помощью встроенного контракта ActivityResultContracts.StartIntentSenderForResult. Ознакомьтесь с разделом Получение обратного вызова для статуса обновления.
Дальнейшие действия зависят от того, запрашиваете ли вы гибкое или немедленное обновление.
Как настроить обновление с помощью AppUpdateOptions
AppUpdateOptions содержит поле AllowAssetPackDeletion, которое определяет, разрешено ли обновлению удалять пакеты объектов, если на устройстве недостаточно места. По умолчанию для этого поля задано значение false, но вы можете использовать метод setAllowAssetPackDeletion(), чтобы задать значение 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());
Как получить обратный вызов для обновления статуса
После начала обновления зарегистрированный обратный вызов средства запуска результатов объекта activity получает результат диалогового окна подтверждения:
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. } } });
Обратный вызов onActivityResult() может возвращать несколько значений:
RESULT_OK– пользователь принял обновление. При немедленном обновлении вы можете не получить этот обратный вызов, поскольку обновление должно быть завершено к моменту возврата управления вашему приложению.RESULT_CANCELED– пользователь отклонил или отменил обновление.ActivityResult.RESULT_IN_APP_UPDATE_FAILED– другая ошибка, из-за которой пользователь не смог предоставить согласие или не удалось выполнить обновление.
Как обработать гибкое обновление
При запуске гибкого обновления сначала появляется диалоговое окно с запросом согласия пользователя. Если пользователь дает согласие, скачивание начинается в фоновом режиме, и пользователь может продолжать взаимодействовать с приложением. В этом разделе описывается, как отслеживать и завершать гибкое обновление в приложении.
Как отслеживать статус гибкого обновления
После начала скачивания гибкого обновления приложение должно отслеживать его статус, чтобы определить, когда можно будет начать установку, и показывать ход выполнения в интерфейсе.
Вы можете отслеживать состояние обновления, зарегистрировав прослушиватель для статусов установки. Вы также можете добавить в интерфейс приложения индикатор выполнения, чтобы информировать пользователей о ходе скачивания.
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);
Как установить гибкое обновление
При обнаружении состояния InstallStatus.DOWNLOADED необходимо перезапустить приложение, чтобы установить обновление.
В отличие от срочных обновлений, Google Play не перезапускает приложение автоматически для гибкого обновления. Это связано с тем, что при гибком обновлении пользователь ожидает, что сможет продолжить работу с приложением, пока не решит установить обновление.
Рекомендуем уведомить пользователя о том, что обновление готово к установке, и запросить подтверждение на перезапуск приложения.
В примере ниже показано, как реализовать всплывающее уведомление Material Design, которое запрашивает у пользователя подтверждение на перезапуск приложения.
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(); }
Если вы вызываете appUpdateManager.completeUpdate() в активном режиме, платформа показывает полноэкранный интерфейс, который перезапускает приложение в фоновом режиме.
После установки обновления приложение перезапускается и открывается его главное окно.
Если вы вызовете completeUpdate(), когда приложение работает в фоновом режиме, обновление будет установлено без уведомления пользователя и не повлияет на работу устройства.
Каждый раз, когда пользователь открывает приложение, проверяйте, есть ли для него обновление. Если у приложения есть обновление в состоянии DOWNLOADED, предложите пользователю установить его. В противном случае данные обновления будут занимать место в хранилище устройства.
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(); } }); }
Как обработать немедленное обновление
Если вы запускаете немедленное обновление и пользователь соглашается на него, Google Play показывает ход обновления поверх интерфейса приложения в течение всего процесса. Если пользователь закроет или завершит работу приложения во время обновления, оно должно продолжить скачиваться и устанавливаться в фоновом режиме без дополнительного подтверждения.
Однако когда приложение возвращается на передний план, необходимо убедиться, что обновление не зависло в состоянии UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Если обновление зависло в этом состоянии, возобновите его:
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()); } }); }
Процесс обновления возвращает результат, как описано в справочной документации для startUpdateFlowForResult(). В частности, приложение должно корректно обрабатывать ситуации, когда пользователь отказывается от обновления или отменяет скачивание. После этого интерфейс Google Play закроется. Приложение должно определить, как лучше поступить.
По возможности разрешите пользователю продолжить работу без обновления и напомните о нем позже. Если приложение не может работать без обновления, рекомендуем показать информационное сообщение, прежде чем перезапускать процесс обновления или предлагать пользователю закрыть приложение. Так пользователь поймет, что он может перезапустить приложение, когда будет готов установить необходимое обновление.
Дальнейшие действия
Протестируйте обновления приложения, чтобы убедиться, что интеграция работает правильно.