Inactive — Active와 Background 사이의 애플리케이션 라이프사이클에서 전환 상태로, 앱이 화면에 표시되지만 터치 이벤트를 받지 않습니다. iOS 및 Android에서 Inactive가 어떻게 발생하는지, 어떤 델리게이트 메서드가 이를 처리하는지, 중단(전화, 알림, 시스템 제스처)을 올바르게 처리하는 방법을 설명합니다.
주요 포인트
Inactive는 Active와 Background 사이의 전환 중에 발생하는 모바일 앱 라이프사이클의 중간 상태입니다. 이 상태에서 앱은 여전히 포그라운드에 있으며 사용자에게 표시되지만 터치 이벤트, 키 입력 또는 기타 UI 이벤트를 받지 않습니다. 시스템은 앱으로의 이벤트 전달을 차단하지만 UI는 화면에 남아 있으며 최소화되지 않습니다.
Inactive의 특성은 일시적입니다. 이 상태는 시스템 중단이 지속되는 동안만 지속됩니다: Control Center를 빠르게 닫을 때 0.1초에서 통화 화면이 있는 수신 전화 중 몇 초까지. 중단이 끝나면 앱은 Active로 돌아가거나 사용자가 다른 앱으로 전환한 경우 Background로 전환됩니다. Inactive는 양방향으로 전환할 수 있는 유일한 상태입니다: Active로 돌아가거나 Background로 진행.
iOS에서 Inactive는 시스템에 의해 자동으로 관리됩니다. 개발자는 Inactive 시간을 연장하거나 단축할 수 없습니다 — UIApplication에 의해 완전히 제어됩니다. 개발자가 할 수 있는 유일한 일은 applicationWillResignActive를 통한 Inactive로의 전환과 applicationDidBecomeActive를 통한 복귀를 올바르게 처리하는 것입니다. Android에서는 onPause가 이에 해당합니다. 의미는 다르지만: onPause는 Activity가 다른 구성 요소에 의해 부분적으로 가려진 경우에도 호출됩니다.
iOS에서 Inactive는 애플리케이션 라이프사이클의 개별 상태입니다(다섯 가지 중 하나: Not Running, Active, Inactive, Background, Suspended). Android에는 직접적인 동등물이 없습니다 — onPause는 Activity가 입력 포커스를 잃고 있지만 표시된 상태로 남을 수 있음을 알립니다(예: 대화상자가 열릴 때). 주요 차이점: iOS Inactive는 앱 전체 상태인 반면, Android onPause는 Activity별 상태입니다. Android 멀티 윈도우에서 하나의 Activity는 onPause(포커스 없음) 상태이고 다른 Activity는 onResume(포커스 있음) 상태일 수 있습니다.
| 특성 | iOS Inactive | Android onPause |
|---|---|---|
| UI 표시 | 예 | 예 (부분적 또는 완전히) |
| 터치 이벤트 | 받지 않음 | 받지 않음 |
| 지속 시간 | 중단이 끝날 때까지 | 포커스가 돌아오거나 백그라운드로 이동할 때까지 |
| 다음 상태 | Active 또는 Background | onResume 또는 onStop |
| 수준 | 앱 (UIApplication) | Activity |
| 멀티 윈도우 | 하나의 씬만 활성 | 여러 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). 멀티 윈도우 모드 — 비활성 창이 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 스크린샷을 찍음
}
}코드는 UIKit에서 Inactive 처리를 보여줍니다. applicationWillResignActive는 비디오를 일시 중단하고, 데이터 수집을 중지하며, 민감한 정보를 숨깁니다. Control Center 또는 App Switcher가 열릴 때 시스템이 현재 UI의 스크린샷을 찍기 때문에 이는 중요합니다 — 사용자가 미리보기에서 기밀 데이터를 볼 수 있습니다. NotificationCenter를 통해 앱 구성 요소가 중단 이벤트를 구독할 수 있습니다.
iOS에서 Inactive는 한 쌍의 메서드로 처리됩니다: applicationWillResignActive(Inactive로 전환)와 applicationDidBecomeActive(Inactive에서 복귀). 이 메서드는 UIApplicationDelegate의 일부이며 Inactive를 통한 모든 전환에서 호출됩니다. iOS 13 및 UISceneDelegate 이후 멀티 윈도우 시나리오를 위해 sceneWillResignActive와 sceneDidBecomeActive가 추가되었습니다.
iOS 13+의 iPad에서 앱은 여러 씬(윈도우)을 가질 수 있습니다. 각 씬은 고유한 라이프사이클을 가지고 있습니다. 하나의 씬이 Inactive가 될 수 있고(사용자가 다른 씬으로 전환) 다른 씬은 Active로 남을 수 있습니다. 이는 iPhone과의 중요한 차이점입니다. 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
)
}
}코드는 씬 수준에서 Inactive를 처리하는 SceneDelegate를 보여줍니다. sceneWillResignActive는 특정 윈도우가 포커스를 잃을 때 호출됩니다 — 이는 iPad에서 윈도우 간 전환 시 발생할 수 있습니다. AppDelegate는 멀티 윈도우를 지원하기 위해 UISceneConfiguration을 구성합니다. 각 씬은 독립적인 상태를 가지며 개발자는 이를 개별적으로 처리해야 합니다.
Android에서 iOS Inactive에 직접 해당하는 것은 Activity 라이프사이클의 onPause() 메서드입니다. 이는 Activity가 입력 포커스를 잃었지만 표시된 상태로 남아 있을 수 있을 때 호출됩니다. 일반적인 시나리오: 대화상자 열기, 동일한 앱에서 다른 Activity 실행, 수신 전화, Home 또는 Recents 버튼 누르기. onPause에서 개발자는 리소스 집약적 작업(애니메이션, 비디오 재생, 카메라 작업)을 일시 중단해야 합니다.
Android의 중요한 차이점은 onPause가 항상 onStop보다 먼저 발생하지만 그 반대는 아니라는 것입니다. Activity는 onStop 없이 onPause를 받을 수 있습니다(예: 투명 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에서도 유사하게 시스템이 Recents의 마지막 Activity 미리보기를 표시합니다. 기밀 화면을 보호하려면 UIApplication.shouldSnapshotSecureApp(iOS 16+) 또는 FLAG_SECURE(Android)를 사용하십시오.
두 번째 규칙 — 애니메이션과 미디어를 일시 중단합니다. Inactive는 비디오나 애니메이션을 재생하기에 적합한 시간이 아닙니다. 사용자가 이를 볼 수 없기 때문입니다. 또한 백그라운드 재생은 시스템 사운드(벨소리, 알림)와 오디오가 겹칠 수 있습니다. Inactive로 전환할 때 AVPlayer, ExoPlayer 및 UIView.animate를 중지하고 Active로 돌아올 때 재개하십시오.
세 번째 규칙 — 데이터 입력을 차단합니다. 앱에 입력 양식이나 초안이 포함된 경우 Inactive로 전환할 때 키보드와 입력 필드를 잠급니다. 이는 복귀 시 실수로 입력하는 것을 방지하고 시스템 오버레이를 통한 데이터 가로채기를 방지합니다. iOS에서는 퍼스트 리스폰더를 해제하고(view.endEditing(true)), Android에서는 포커스를 지웁니다(currentFocus?.clearFocus()).
네 번째 규칙 — applicationWillResignActive 또는 onPause에서 긴 작업을 수행하지 마십시오. 이 메서드는 0.1초 이내에 완료되어야 합니다. 대량의 데이터를 저장해야 하는 경우 백그라운드 스레드에서 저장을 시작하고 applicationDidEnterBackground 또는 onStop에서 완료하십시오. iOS는 applicationWillResignActive 실행에 5초를 제공하며, 이후 시스템이 앱을 강제 종료할 수 있습니다.
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가 Inactive로 전환할 때 전체 UI 위에 배치되고 Active로 돌아올 때 제거됩니다. 이렇게 하면 기밀 데이터가 App Switcher 및 Control Center 스크린샷에 표시되지 않습니다. 유사하게 브랜드 오버레이를 위해 로고가 있는 UIImageView를 사용할 수 있습니다.
자주 묻는 질문
네. Inactive는 iOS에서 Background로 전환하기 전의 필수 중간 상태입니다. 앱은 Active에서 직접 Background로 전환할 수 없습니다 — 먼저 Inactive가 된 다음 Background가 됩니다. Android에서도 유사하게 onPause가 항상 onStop보다 먼저 호출됩니다. 이는 개발자가 완전히 백그라운드로 이동하기 전에 데이터를 저장할 준비를 할 기회를 제공합니다.
네. iPad에서 Slide Over 또는 Split View를 시작하면 활성 씬이 Inactive가 됩니다. 시스템 중단이 발생하지 않더라도 — 사용자가 단순히 다른 씬과 상호 작용하고 있는 것입니다. 이는 멀티 윈도우 iPadOS의 기능입니다. iPhone에서 Inactive는 항상 시스템 중단(전화, 알림, Control Center 또는 Notification Center)에 의해 트리거됩니다.
일반적으로 0.1초에서 2초입니다. 통화 화면이 있는 수신 전화 중에는 최대 30초(사용자가 응답하거나 거절할 때까지). iOS는 Inactive 시간을 강제로 제한하지 않지만 앱이 이벤트에 응답하지 않는 경우(watchdog) 시스템이 앱을 종료할 수 있습니다. Android에서 onPause에는 시간 제한이 없지만 200ms 이내에 작업을 완료하는 것이 좋습니다.
ScenePhase.inactive — 씬이 포그라운드에 있지만 이벤트를 받지 않을 때 설정되는 ScenePhase 열거형 값입니다. SwiftUI에서는 @Environment(\.scenePhase)를 통해 관찰하고 onChange를 통해 반응할 수 있습니다. .active에서 .inactive로 전환할 때 타이머와 애니메이션을 일시 중단합니다. .active로 돌아올 때 재개합니다. .background로 전환할 때 상태를 저장합니다.
아니요, 기밀 데이터를 처리하는 앱(은행, 의료, 기업 및 개인 채팅 메시징 앱)에만 해당됩니다. 게임 및 엔터테인먼트 앱에서는 UI를 숨길 필요가 없습니다. 그러나 Inactive 중 게임플레이와 사운드를 일시 중단하는 것은 시스템 알림과 오디오가 겹치는 것을 방지하는 좋은 방법입니다. Apple은 민감한 데이터를 숨길 것을 권장하지만 요구하지는 않습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.