OTA-обновления: как выпускать исправления без релиза в сторе
Продвинутое3 минРедакция Innovialabs
Коротко
OTA-обновления позволяют доставить новый JavaScript- или Dart-код без проверки в сторе, но не меняют нативную часть. Для React Native основной вариант — Expo Updates, для Flutter — Shorebird; обязательно нужны совместимость версий, поэтапный выпуск, откат и экран принудительного обновления.
Что можно обновить «по воздуху»
Кроссплатформенное приложение состоит из двух слоёв:
- нативная оболочка — скомпилированный код, SDK, разрешения, иконка,
Info.plistиAndroidManifest.xml; - код приложения — JavaScript-бандл в React Native или Dart-код во Flutter, плюс ассеты.
OTA-обновление заменяет только второй слой. Исправить ошибку в логике, текст, вёрстку экрана — можно. Добавить новый нативный SDK, разрешение камеры или обновить версию React Native — нельзя, для этого нужен обычный релиз через стор.
Правила сторов
Apple. Правила App Review (раздел 2.5.2) запрещают загружать исполняемый код, меняющий функциональность. Лицензионное соглашение Apple Developer Program делает исключение для интерпретируемого кода, например JavaScript, если обновление не меняет основное назначение приложения, не создаёт витрину для другого кода и не обходит проверку. На этом исключении и работают OTA-решения.
Google Play. Политика запрещает скачивать исполняемый код (dex, JAR, .so) не из Google Play, но не распространяется на код, выполняемый в виртуальной машине или интерпретаторе с ограниченным доступом к API Android.
Практический вывод: исправления и небольшие улучшения — да, новые крупные функции в обход проверки — нет. Существенные изменения выпускайте через стор.
Сравнение инструментов
| Expo Updates (EAS Update) | Альтернативы CodePush | Shorebird | |
|---|---|---|---|
| Платформа | React Native (Expo и bare-проекты с expo-updates) | React Native | Flutter |
| Что обновляет | JS-бандл и ассеты | JS-бандл и ассеты | Dart-код |
| Совместимость | runtimeVersion | Целевая версия бинарника | Патч привязан к конкретному релизу |
| Хостинг | Облако Expo или свой сервер по протоколу | Свой сервер или сторонний сервис | Облако Shorebird |
CodePush долго был стандартом для React Native, но работал внутри Microsoft App Center, а App Center закрыт. Сейчас варианты такие: перейти на EAS Update, поднять совместимый self-hosted сервер или выбрать стороннее решение. Для новых проектов чаще выбирают EAS Update.
Shorebird — code push для Flutter. Вы выпускаете релиз через shorebird release, а исправления — через shorebird patch:
shorebird release android
shorebird patch android
Для Expo публикация обновления — одна команда:
eas update --channel production --message "Fix checkout crash"
Совместимость версий — главное правило
Самая опасная ошибка — отправить JS-бандл, который вызывает нативный модуль, отсутствующий в установленной у пользователя версии. Результат — краш на старте.
В Expo это решает runtimeVersion: обновление получают только сборки с совпадающей версией среды выполнения.
{
"expo": {
"runtimeVersion": { "policy": "appVersion" }
}
}
Правило простое: изменился нативный слой — меняется runtimeVersion и выходит новый релиз в стор.
Стратегии отката
- Поэтапный выпуск. Отдайте обновление части пользователей, посмотрите на краши и ошибки, затем расширяйте.
- Быстрый откат. Держите возможность переопубликовать предыдущее стабильное обновление или откатить патч в консоли. Проверьте процесс заранее, а не во время инцидента.
- Встроенная защита. OTA-библиотеки умеют возвращаться к встроенному в сборку бандлу, если обновление не запустилось. Не отключайте этот механизм.
- Мониторинг. Отмечайте в системе сбора крашей идентификатор обновления — так видно, какое именно обновление сломало приложение.
- Отдельные каналы.
stagingдля тестировщиков,productionдля всех.
Экран принудительного обновления
Если исправление требует нативных изменений, OTA не поможет — нужно заставить пользователей обновиться через стор.
- На сервере или в remote config хранится минимальная поддерживаемая версия.
- При запуске приложение сравнивает её со своей версией.
- Если версия ниже — показывается экран с кнопкой перехода в стор. Мягкий вариант можно закрыть, жёсткий — нет.
На Android есть In-App Updates API с режимами immediate и flexible. На iOS аналогичного API нет — ведите пользователя на страницу приложения в App Store.
Жёсткую блокировку используйте только при реальной необходимости: несовместимость API, уязвимость, юридические требования.