Настройка In-App Update (принудительное обновление) в Android-приложении
Пользователи не обновляются сами по себе. По данным Play Console, медианное время между релизом и установкой обновления — около трёх недель. Если в новой версии критический фикс безопасности или сломалось API — это три недели с работающей дырой. In-App Update API решает эту проблему напрямую из приложения, без надежды на сознательность пользователя.
Два режима и когда выбирать каждый
Google Play In-App Update API, доступный через com.google.android.play:app-update-ktx, предоставляет два сценария.
Flexible update — фоновая загрузка, пользователь продолжает работу. Подходит для фичевых релизов. После завершения загрузки показывается снекбар с кнопкой «Перезапустить». Если пользователь его закрыл — нужно отдельно отслеживать InstallStatus.DOWNLOADED и показывать повторный запрос.
Immediate update — полноэкранный блокирующий интерфейс от Google Play. Приложение фактически недоступно до завершения обновления. Используем при критических патчах: смена схемы шифрования, обязательная миграция БД, прекращение поддержки старого API бэкенда. Важно: Immediate update не означает, что пользователь точно обновится. Он может закрыть приложение — onActivityResult вернёт RESULT_CANCELED, и нужно обрабатывать эту ветку корректно, иначе приложение будет полностью недоступно.
Что реально вызывает проблемы
Самая частая ошибка — инициализировать AppUpdateManager без проверки доступности обновления. Метод appUpdateManager.appUpdateInfo возвращает Task<AppUpdateInfo>, и его нужно ждать асинхронно. Попытки вызвать startUpdateFlowForResult без готового AppUpdateInfo дают IllegalStateException в рантайме.
Второй распространённый баг — не учитывать updateAvailability == UPDATE_NOT_AVAILABLE на сборках из Firebase App Distribution или при тестировании через FakeAppUpdateManager. В продакшене этот путь недостижим, но в CI падают тесты.
Третья проблема — версионирование. Сравнение идёт по versionCode, не по versionName. Если в нескольких флейворах одного приложения используется один versionCode — обновление не будет предлагаться, даже если бинарники разные.
Как мы реализуем
Интеграция начинается с аудита текущей схемы версионирования — проверяем, что versionCode монотонно возрастает и нет коллизий между флейворами. Затем добавляем зависимость и пишем UpdateManager-класс в слое domain (чистая архитектура — UI не знает о Play Services напрямую).
val appUpdateManager = AppUpdateManagerFactory.create(context)
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
) {
appUpdateManager.startUpdateFlowForResult(
info, AppUpdateType.FLEXIBLE, activity, REQUEST_CODE_UPDATE
)
}
}
Для Immediate-сценария добавляем staleDays — принудительный апдейт включается только если обновление доступно более N дней. Это снижает раздражение пользователей при мелких патчах.
Обработка onResume критична: если пользователь свернул приложение во время Flexible-загрузки и вернулся — нужно проверить installStatus и предложить рестарт. Без этого приложение работает на старом коде, хотя новый уже лежит на диске.
Тестирование
FakeAppUpdateManager из play-app-update позволяет эмулировать все состояния без публикации в Play Store. В Espresso-тестах можно прогнать полный цикл: доступность → начало загрузки → завершение → рестарт. Без этого покрытия баги в update flow уходят в продакшен незамеченными — сам Google Play показывает UI поверх приложения, и стандартные UI-тесты его не видят.
Процесс работы
Сначала анализируем текущую архитектуру приложения и схему версионирования. Определяем, какие релизы должны быть Immediate, а какие Flexible — обычно это закрепляется в release checklist. Реализуем и покрываем тестами. Финальная проверка — через Internal Testing track в Play Console с реальным устройством.
Сроки — от двух до пяти рабочих дней в зависимости от сложности архитектуры приложения.







