Inactive — переходное состояние жизненного цикла приложения между Active и Background, в котором приложение видимо на экране, но не получает события касания. Рассказываем, как возникает Inactive на iOS и Android, какие методы делегата за него отвечают и как корректно обрабатывать прерывания — звонки, уведомления и системные жесты.
Главное
Inactive — промежуточное состояние жизненного цикла мобильного приложения, которое возникает при переходе между Active и Background. В этом состоянии приложение всё ещё находится на переднем плане и видимо пользователю, но не получает события касания, нажатия клавиш и другие UI-события. Система блокирует передачу событий приложению, но при этом UI остаётся на экране и не сворачивается.
Природа Inactive — временная. Это состояние длится ровно столько, сколько длится системное прерывание: от 0.1 секунды при быстром закрытии Control Center до нескольких секунд при входящем звонке с экраном вызова. После завершения прерывания приложение либо возвращается в Active, либо переходит в Background, если пользователь переключился на другое приложение. Inactive — единственное состояние, из которого возможен переход как в обе стороны: обратно в Active или далее в Background.
На iOS Inactive управляется системой автоматически. Разработчик не может продлить или сократить время пребывания в Inactive — это полностью контролируется UIApplication. Единственное, что может сделать разработчик, — корректно обработать уход в Inactive через applicationWillResignActive и возврат через applicationDidBecomeActive. На Android аналогом является onPause, хотя семантика отличается: onPause вызывается даже при частичном перекрытии Activity другим компонентом.
На iOS Inactive — это отдельное состояние жизненного цикла приложения (одно из пяти: Not Running, Active, Inactive, Background, Suspended). На Android нет прямого аналога — onPause сигнализирует, что Activity теряет фокус ввода, но может оставаться видимым (например, при открытии диалога). Ключевое различие: iOS Inactive — состояние приложения в целом, Android onPause — состояние конкретной Activity. В multi-window на Android одно Activity может быть в onPause (без фокуса), а другое — в onResume (с фокусом).
| Характеристика | iOS Inactive | Android onPause |
|---|---|---|
| UI видим | Да | Да (частично или полностью) |
| Touch-события | Не принимает | Не принимает |
| Длительность | До завершения прерывания | До возврата фокуса или ухода в фон |
| Следующее состояние | Active или Background | onResume или onStop |
| Уровень | Приложение (UIApplication) | Activity |
| Multi-window | Одна сцена активна | Несколько Activity в onPause |
Inactive на iOS возникает в нескольких строго определённых сценариях. Пользователь вызывает Control Center (свайп вниз от правого верхнего угла на iPhone X+ или свайп вверх на старых моделях). Пользователь открывает Notification Center (свайп вниз от левого верхнего угла). Приходит входящий звонок — система показывает экран вызова поверх приложения. Запрашивается системное разрешение — геолокация, микрофон, камера, контакты. На iPad запускается Slide Over или Split View — активная сцена становится Inactive.
На Android onPause (аналог Inactive) возникает в ещё более широком спектре ситуаций. Открытие диалогового окна (AlertDialog, DialogFragment). Частичное перекрытие Activity другим Activity (например, прозрачная Activity для авторизации). Поворот экрана (Activity пересоздаётся, последовательность: onPause → onStop → onDestroy → onCreate → onStart → onResume). Multi-window режим — неактивное окно получает onPause. Каждое из этих событий требует приостановки ресурсоёмких операций для сохранения батареи и производительности.
import UIKit
extension Notification.Name {
static let systemInterruptionBegan = Notification.Name("systemInterruptionBegan")
static let systemInterruptionEnded = Notification.Name("systemInterruptionEnded")
}
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillResignActive(_ application: UIApplication) {
// Приложение переходит в Inactive — системное прерывание
print("Прерывание: Control Center, звонок или системный алерт")
// Приостановка чувствительных ко времени операций
pauseVideoPlayback()
stopContinuousDataCollection()
hideSensitiveInformation()
// Уведомление компонентов
NotificationCenter.default.post(name: .systemInterruptionBegan, object: nil)
}
// Возврат из Inactive в Active
func applicationDidBecomeActive(_ application: UIApplication) {
resumeVideoPlayback()
restartDataCollection()
NotificationCenter.default.post(name: .systemInterruptionEnded, object: nil)
}
private func pauseVideoPlayback() {
// Приостановка видео, чтобы звук не накладывался
}
private func hideSensitiveInformation() {
// Скрытие чувствительных данных при скриншоте экрана
// Control Center/App Switcher делают скриншот UI
}
}В коде показана обработка Inactive в UIKit. applicationWillResignActive приостанавливает видео, останавливает сбор данных и скрывает чувствительную информацию. Это важно, потому что при открытии Control Center или App Switcher система делает скриншот текущего UI — пользователь может увидеть конфиденциальные данные в превью. NotificationCenter позволяет компонентам приложения подписаться на события прерывания.
В iOS Inactive обрабатывается парой методов: applicationWillResignActive (уход в Inactive) и applicationDidBecomeActive (возврат из Inactive). Эти методы являются частью UIApplicationDelegate и вызываются для каждого перехода через Inactive. С iOS 13 и UISceneDelegate к ним добавились sceneWillResignActive и sceneDidBecomeActive для multi-window сценариев.
На iPad с iOS 13+ приложение может иметь несколько сцен (окон). Каждая сцена имеет собственный жизненный цикл. Одна сцена может стать Inactive (пользователь переключился на другую сцену), в то время как другая остаётся Active. Это важное отличие от iPhone, где Inactive — глобальное состояние для всего приложения. При разработке под iPad нужно обрабатывать Inactive для каждой сцены отдельно.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Сцена становится неактивной
func sceneWillResignActive(_ scene: UIScene) {
// На iPad эта сцена теряет фокус, но другие могут быть активны
print("Сцена теряет активность")
// Приостановка задач этой сцены
pauseSceneSpecificOperations()
}
// Сцена становится активной
func sceneDidBecomeActive(_ scene: UIScene) {
print("Сцена стала активной")
resumeSceneSpecificOperations()
}
private func pauseSceneSpecificOperations() {
// Приостановка операций, специфичных для данной сцены
}
private func resumeSceneSpecificOperations() {
// Возобновление операций при возврате фокуса
}
}
// AppDelegate остаётся точкой входа, делегирует сценам
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
}В коде показан SceneDelegate для обработки Inactive на уровне сцены. sceneWillResignActive вызывается, когда конкретное окно теряет фокус — это может произойти при переключении между окнами на iPad. AppDelegate настраивает UISceneConfiguration для поддержки multi-window. Каждая сцена имеет независимое состояние, и разработчик должен обрабатывать их отдельно.
В Android прямым аналогом iOS Inactive является метод onPause() жизненного цикла Activity. Он вызывается, когда Activity теряет фокус ввода, но может оставаться видимым. Типичные сценарии: открытие диалогового окна, запуск другого Activity в том же приложении, входящий звонок, нажатие кнопки Home или Recents. В onPause разработчик должен приостановить ресурсоёмкие операции — анимации, воспроизведение видео, работу с камерой.
Важное отличие Android — onPause всегда предшествует onStop, но не наоборот. Activity может получить onPause без onStop (например, при открытии прозрачного Activity). Также onPause может вызываться многократно в течение жизни Activity — при каждом переключении фокуса. Не размещайте в onPause одноразовую логику — используйте onStop для финальных операций и onPause только для приостановки интерактивных действий.
class VideoPlayerActivity : AppCompatActivity() {
private var exoPlayer: ExoPlayer? = null
private var currentPosition: Long = 0L
override fun onPause() {
super.onPause()
// Приложение теряет фокус — ставим видео на паузу
exoPlayer?.let { player ->
if (player.isPlaying) {
currentPosition = player.currentPosition
player.pause()
}
}
// Скрываем чувствительные данные (GDPR/банковские экраны)
if (window.decorView.systemUiVisibility and
View.SYSTEM_UI_FLAG_SECURE == 0
) {
hideSensitiveOverlay()
}
}
override fun onResume() {
super.onResume()
// Возврат фокуса — возобновляем воспроизведение
exoPlayer?.seekTo(currentPosition)
exoPlayer?.play()
showSensitiveOverlay()
}
private fun hideSensitiveOverlay() {
// Накладываем чёрный экран поверх финансовых данных
}
}В коде показана правильная обработка onPause для видеоплеера. ExoPlayer приостанавливается при потере фокуса, и позиция воспроизведения сохраняется. При возврате в onResume плеер возобновляет воспроизведение с сохранённой позиции. Дополнительно показан паттерн скрытия чувствительных данных — важный для финансовых и медицинских приложений, требующих защиты от скриншотов при переключении.
Первое правило — скрывайте конфиденциальные данные при переходе в Inactive. Когда пользователь открывает Control Center или App Switcher, iOS делает скриншот текущего экрана. На Android аналогично — система показывает превью последнего Activity в Recents. Используйте UIApplication.shouldSnapshotSecureApp (iOS 16+) или FLAG_SECURE (Android) для защиты конфиденциальных экранов.
Второе правило — приостанавливайте анимации и медиа. Inactive — не лучшее время для воспроизведения видео или анимаций, так как пользователь их не видит. Более того, воспроизведение в фоне может привести к наложению звуков на системные звуки (звонок, уведомление). Останавливайте AVPlayer, ExoPlayer и UIView.animate при уходе в Inactive и возобновляйте при возврате в Active.
Третье правило — блокируйте ввод данных. Если приложение содержит формы ввода или черновики, заблокируйте клавиатуру и поля ввода при уходе в Inactive. Это предотвращает случайный ввод при возврате и защищает от перехвата данных через системные оверлеи. На iOS отключайте first responder (view.endEditing(true)), на Android — очищайте фокус (currentFocus?.clearFocus()).
Четвёртое правило — не выполняйте долгие операции в applicationWillResignActive или onPause. Эти методы должны завершаться за доли секунды. Если нужно сохранить большой объём данных, начните сохранение в фоновом потоке и завершите его в applicationDidEnterBackground или onStop. iOS даёт 5 секунд на выполнение applicationWillResignActive, после чего система может принудительно завершить приложение.
import UIKit
final class SecureOverlayManager {
private var blurView: UIVisualEffectView?
func showBlurOverlay() {
guard let window = UIApplication.shared.keyWindow,
blurView == nil
else { return }
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blur.frame = window.bounds
blur.autoresizingMask = [.flexibleWidth, .flexibleHeight]
window.addSubview(blur)
blurView = blur
}
func removeBlurOverlay() {
blurView?.removeFromSuperview()
blurView = nil
}
}
// Использование в AppDelegate
func applicationWillResignActive(_ application: UIApplication) {
SecureOverlayManager().showBlurOverlay()
}
func applicationDidBecomeActive(_ application: UIApplication) {
SecureOverlayManager().removeBlurOverlay()
}В коде показана реализация безопасного оверлея для защиты данных при переходе в Inactive. UIVisualEffectView с blur-эффектом накладывается поверх всего UI при уходе в Inactive и удаляется при возврате в Active. Это гарантирует, что конфиденциальные данные не будут видны на скриншотах App Switcher и Control Center. Аналогично можно использовать UIImageView с логотипом для брендированного оверлея.
Часто задаваемые вопросы
Да. Inactive — обязательное промежуточное состояние перед переходом в Background на iOS. Приложение не может перейти из Active напрямую в Background — сначала оно становится Inactive, затем Background. На Android аналогично: onPause всегда вызывается перед onStop. Это даёт разработчику возможность подготовить данные к сохранению перед полным уходом в фон.
Да. На iPad при запуске Slide Over или Split View активная сцена становится Inactive, хотя никакого системного прерывания не происходит — пользователь просто взаимодействует с другой сценой. Это особенность multi-window iPadOS. На iPhone Inactive всегда вызывается системным прерыванием — звонком, уведомлением, Control Center или Notification Center.
Обычно от 0.1 до 2 секунд. При входящем звонке с экраном вызова — до 30 секунд (пока пользователь не ответит или не отклонит вызов). iOS не ограничивает принудительно время в Inactive, но система может завершить приложение, если оно не реагирует на события (watchdog). На Android onPause не имеет ограничения по времени, но рекомендуется завершать работу за 200 мс.
ScenePhase.inactive — значение enum ScenePhase, устанавливаемое, когда сцена находится на переднем плане, но не принимает события. В SwiftUI вы можете наблюдать его через @Environment(\.scenePhase) и реагировать через onChange. При переходе из .active в .inactive приостанавливайте таймеры и анимации. При возврате в .active — возобновляйте. При уходе в .background — сохраняйте состояние.
Нет, только для приложений, работающих с конфиденциальными данными: банковские, медицинские, корпоративные, мессенджеры с приватными чатами. Для игр и развлекательных приложений скрытие UI не требуется. Однако приостановка игрового процесса и звука при Inactive — хорошая практика, чтобы избежать наложения звуков на системные уведомления. Apple рекомендует скрывать чувствительные данные, но не требует этого.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также