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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.