mobilegame-qainterruptionslifecycleqa

Тестирование прерываний в мобильной игре: звонок, свёрнутое приложение и потерянная сессия

У меня был баг, который не воспроизводился ни на одном ревью: игроки писали «пропал прогресс матча». Оказалось — если во время боя приходил звонок, игра сворачивалась, а при возврате начинала новую сессию: несохранённый ход терялся, а у части людей ещё и звук потом молчал. На моём тестовом девайсе всё было идеально — потому что я не звонил сам себе посреди теста.

Мобильная игра живёт не в вакууме: поверх неё в любой момент вылезает звонок, пуш, шторка, будильник, реклама. Прерывание — это не редкий 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