Inactive — перехідний стан між Active та Background

Автор: IT Sectr Опубліковано: 2026-03-03 Час читання: 10 хв

Inactive — перехідний стан життєвого циклу застосунку між Active та Background, в якому застосунок видимий на екрані, але не отримує події дотику. Пояснюємо, як виникає Inactive на iOS та Android, які методи делегата за нього відповідають та як коректно обробляти переривання — дзвінки, сповіщення та системні жести.

Головне

  • Inactive — застосунок видимий на екрані, але не приймає touch-події через системне переривання
  • applicationWillResignActive — основний метод, що сигналізує про перехід в Inactive на iOS
  • Тимчасовий характер — Inactive триває від часток секунди до кількох секунд, потім слідує перехід в Active або Background
  • Причини — вхідний дзвінок, Control Center, Notification Center, системний алерт, Apple Pay, Siri
  • SwiftUI — scenePhase .inactive еквівалентний Inactive в UIKit

Inactive — суть перехідного стану

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 іншим компонентом.

Порівняння Inactive на iOS та Android

На iOS Inactive — це окремий стан життєвого циклу застосунку (один з п'яти: Not Running, Active, Inactive, Background, Suspended). На Android немає прямого аналога — onPause сигналізує, що Activity втрачає фокус введення, але може залишатися видимим (наприклад, при відкритті діалогу). Ключова відмінність: iOS Inactive — стан застосунку в цілому, Android onPause — стан конкретної Activity. В multi-window на Android одна Activity може бути в onPause (без фокусу), а інша — в onResume (з фокусом).

ХарактеристикаiOS InactiveAndroid onPause
UI видимийТакТак (частково або повністю)
Touch-подіїНе приймаєНе приймає
ТривалістьДо завершення перериванняДо повернення фокусу або переходу в фон
Наступний станActive або BackgroundonResume або onStop
РівеньЗастосунок (UIApplication)Activity
Multi-windowОдна сцена активнаКілька Activity в onPause

Коли виникає Inactive

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. Кожна з цих подій вимагає призупинення ресурсоємних операцій для збереження батареї та продуктивності.

swift
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 дозволяє компонентам застосунку підписатися на події переривання.

Inactive в iOS: Swift та UIKit

На iOS Inactive обробляється парою методів: applicationWillResignActive (перехід в Inactive) та applicationDidBecomeActive (повернення з Inactive). Ці методи є частиною UIApplicationDelegate та викликаються для кожного переходу через Inactive. З iOS 13 та UISceneDelegate до них додалися sceneWillResignActive та sceneDidBecomeActive для multi-window сценаріїв.

SceneDelegate та multi-window Inactive

На iPad з iOS 13+ застосунок може мати кілька сцен (вікон). Кожна сцена має власний життєвий цикл. Одна сцена може стати Inactive (користувач переключився на іншу сцену), в той час як інша залишається Active. Це важлива відмінність від iPhone, де Inactive — глобальний стан для всього застосунку. При розробці під iPad потрібно обробляти Inactive для кожної сцени окремо.

swift
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. Кожна сцена має незалежний стан, і розробник повинен обробляти їх окремо.

Inactive в Android: onPause та видимість

На Android прямим аналогом iOS Inactive є метод onPause() життєвого циклу Activity. Він викликається, коли Activity втрачає фокус введення, але може залишатися видимим. Типові сценарії: відкриття діалогового вікна, запуск іншого Activity в тому ж застосунку, вхідний дзвінок, натискання кнопки Home або Recents. В onPause розробник повинен призупинити ресурсоємні операції — анімації, відтворення відео, роботу з камерою.

Важлива відмінність Android — onPause завжди передує onStop, але не навпаки. Activity може отримати onPause без onStop (наприклад, при відкритті прозорого Activity). Також onPause може викликатися багаторазово протягом життя Activity — при кожному перемиканні фокусу. Не розміщуйте одноразову логіку в onPause — використовуйте onStop для фінальних операцій та onPause тільки для призупинення інтерактивних дій.

kotlin
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

Перше правило — приховуйте конфіденційні дані при переході в 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, після чого система може примусово завершити застосунок.

swift
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?

Так. Inactive — обов'язковий проміжний стан перед переходом в Background на iOS. Застосунок не може перейти з Active напряму в Background — спочатку він стає Inactive, потім Background. На Android аналогічно: onPause завжди викликається перед onStop. Це дає розробнику можливість підготувати дані до збереження перед повним переходом у фон.

Чи може застосунок отримати Inactive без переривання?

Так. На iPad при запуску Slide Over або Split View активна сцена стає Inactive, хоча жодного системного переривання не відбувається — користувач просто взаємодіє з іншою сценою. Це особливість multi-window iPadOS. На iPhone Inactive завжди викликається системним перериванням — дзвінком, сповіщенням, Control Center або Notification Center.

Як довго застосунок може перебувати в Inactive?

Зазвичай від 0.1 до 2 секунд. При вхідному дзвінку з екраном виклику — до 30 секунд (поки користувач не відповість або не відхилить виклик). iOS не обмежує примусово час в Inactive, але система може завершити застосунок, якщо він не реагує на події (watchdog). На Android onPause не має обмеження за часом, але рекомендується завершувати роботу за 200 мс.

Що таке scenePhase в SwiftUI при Inactive?

ScenePhase.inactive — значення enum ScenePhase, що встановлюється, коли сцена знаходиться на передньому плані, але не приймає події. В SwiftUI ви можете спостерігати його через @Environment(\.scenePhase) та реагувати через onChange. При переході з .active в .inactive призупиняйте таймери та анімації. При поверненні в .active — відновлюйте. При переході в .background — зберігайте стан.

Чи потрібно приховувати UI при Inactive для всіх застосунків?

Ні, тільки для застосунків, що працюють з конфіденційними даними: банківські, медичні, корпоративні, месенджери з приватними чатами. Для ігор та розважальних застосунків приховування UI не потрібне. Однак призупинення ігрового процесу та звуку при Inactive — хороша практика, щоб уникнути накладання звуків на системні сповіщення. Apple рекомендує приховувати чутливі дані, але не вимагає цього.

Підсумки

  • Inactive — перехідний стан між Active та Background, застосунок видимий але не інтерактивний
  • Причини — Control Center, Notification Center, вхідний дзвінок, системні дозволи, Siri, Apple Pay
  • iOS обробка — applicationWillResignActive для призупинення анімацій, медіа та приховування чутливих даних
  • SwiftUI — scenePhase .inactive через Environment, onChange для виконання побічних ефектів
  • Android — onPause як аналог, з підтримкою multi-window та часткового перекриття Activity
  • Безпека — blur-оверлей або FLAG_SECURE для захисту від скріншотів App Switcher та Control Center
  • Тимчасовий характер — Inactive триває до завершення переривання, потім повернення в Active або перехід в Background

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також