Тестирование прерываний в мобильной игре: звонок, свёрнутое приложение и потерянная сессия
У меня был баг, который не воспроизводился ни на одном ревью: игроки писали «пропал прогресс матча». Оказалось — если во время боя приходил звонок, игра сворачивалась, а при возврате начинала новую сессию: несохранённый ход терялся, а у части людей ещё и звук потом молчал. На моём тестовом девайсе всё было идеально — потому что я не звонил сам себе посреди теста.
Мобильная игра живёт не в вакууме: поверх неё в любой момент вылезает звонок, пуш, шторка, будильник, реклама. Прерывание — это не редкий edge-case, это норма пользовательской сессии. И тестировать надо не «работает ли игра», а «переживает ли она, когда её грубо ставят на паузу и возвращают».
Почему прерывания — минное поле
Когда приложение уходит в фон, ОС гоняет его через жизненный цикл (onPause/onStop на Android, sceneWillResignActive/didEnterBackground на iOS, OnApplicationPause/OnApplicationFocus в Unity). На возврате всё разворачивается обратно. Баги живут ровно в этих переходах: что-то не сохранили при уходе, что-то не восстановили при возврате, что-то продолжило крутиться в фоне, хотя не должно.
И тут работает то же правило, что и с реальными девайсами: на твоём телефоне, где ты не прерываешь сам себя, этих багов нет. Их надо провоцировать намеренно.
Каталог прерываний (что провоцировать)
- Входящий звонок — классика. Игра сворачивается, звук должен приглушиться/встать на паузу.
- Системное уведомление / пуш — баннер сверху, шторка уведомлений.
- Сворачивание — Home, свайп в «недавние», переключение на другое приложение.
- Блокировка экрана — кнопка power, авто-блокировка по таймауту.
- Разрядка и энергосбережение — Low Power Mode / агрессивный вендорский киллинг фона (Xiaomi, Samsung, Oppo убивают процесс).
- Входящая реклама — interstitial/rewarded открывает свою активность поверх игры: это тоже прерывание, и самое коварное (см. ниже).
- Наушники / Bluetooth — подключил, выдернул, разрыв BT-соединения посреди звука.
- Split-screen / Picture-in-Picture / плавающее окно — игра теряет полный фокус, но остаётся видимой.
- Будильник, таймер, входящий второй звонок, календарь — системные оверлеи.
- Скриншот / запись экрана / шаринг — короткая потеря фокуса.
- Смена сети — Wi-Fi ↔ сотовая при сворачивании (частый спутник прерывания).
Что реально ломается при возврате
- Потеря несохранённого прогресса. Ход, незавершённый матч, введённый текст, позиция в туториале. Если состояние не сохраняется в
onPause, оно исчезает. - Звук не вернулся или не заглушился. После звонка музыка молчит; или наоборот — во время звонка игра орёт в трубку. Проверяй audio focus.
- Rewarded-реклама: награда потеряна или задвоена. Свернул приложение во время просмотра ролика, вернулся — награда не пришла (или пришла дважды). Это прямой ущерб и доверию, и деньгам.
- Оборвалась мультиплеерная сессия. Реконнект не отработал, игрок вылетел из матча, рейтинг просел; или наоборот — «завис» в комнате.
- Таймеры в фоне соврали. Внутриигровые таймеры (энергия, ежедневки, буст) либо замерли, либо, наоборот, продолжили тикать и открыли чит с переводом системных часов.
- Двойной
onResume/ чёрный экран при возврате. Игра дважды инициализируется, ловит гонку, показывает чёрный экран или вылетает. - Диалог/пауза-меню не появились. Игра не встала на паузу при сворачивании — бой продолжился без игрока.
iOS и Android — разное поведение
- Android агрессивнее убивает фон: система (или вендорский battery saver) может прибить процесс, и возврат = холодный старт с восстановлением состояния. Обязательно тестируй сценарий «процесс убит в фоне → возврат».
- iOS мягче с памятью, но строже с фоновым временем: приложению дают считанные секунды на завершение работы в фоне.
- Unity/движок добавляет свой слой:
OnApplicationPause(true/false)иOnApplicationFocusсрабатывают не всегда так, как ждёшь (особенно при рекламе и split-screen) — проверяй на реальном железе, а не в редакторе.
Как воспроизводить (а не надеяться)
- Настоящий входящий звонок с другого телефона в момент активного действия.
adb shell am start -a android.intent.action.CALL/ эмуляция звонка в эмуляторе; но финальную приёмку — на реальном девайсе.- Свернуть на Home и убить процесс из «недавних» (или
adb shell am kill <package>), затем вернуться — проверка «процесс убит в фоне». - Включить Low Power Mode и вендорский battery saver, оставить в фоне на минуты.
- Прерывать РОВНО в критические моменты: во время сохранения, в середине rewarded-ролика, на решающем ходу, в момент сетевого запроса.
- Каждый кейс — на разном железе (см. матрицу устройств), особенно на вендорах с агрессивным киллингом.
Чек-лист прерываний
- Звонок во время игры → корректная пауза, звук приглушён, после — восстановлен.
- Сворачивание (Home/недавние) и возврат → состояние на месте, игра на паузе.
- Процесс убит в фоне → возврат восстанавливает прогресс (не новая сессия).
- Блокировка экрана и разблокировка → без чёрного экрана и двойной инициализации.
- Low Power Mode + вендорский battery saver → игра переживает.
- Прерывание во время rewarded-рекламы → награда приходит ровно один раз (SSV-верификация).
- Наушники/BT подключены/выдернуты → звук не пропал и не заорал в трубку.
- Split-screen / PiP → UI и ввод не разъехались.
- Мультиплеер: прерывание → корректный реконнект или честный выход из матча.
- Таймеры/энергия при сворачивании и переводе часов → без замирания и без чита.
- Прерывание в момент сохранения/сетевого запроса → без потери данных и задвоения.
Коротко — что забрать с собой
- Прерывание — это норма сессии, а не редкий edge-case. Провоцируй его намеренно.
- Баги живут в переходах жизненного цикла: не сохранил при уходе, не восстановил при возврате.
- Три самых дорогих кейса: потерянный прогресс, потерянная/задвоенная награда rewarded, оборванный мультиплеер.
- Обязательно тестируй «процесс убит в фоне» (особенно Android + вендорский киллинг) и прерывание РОВНО в критический момент.
- Реальные девайсы и настоящий звонок — эмулятор половину этого не покажет.
Почитать: Android — жизненный цикл Activity · Apple — управление жизненным циклом приложения · Unity — OnApplicationPause