State Restoration은 애플리케이션 재시작 또는 최소화 후 사용자 인터페이스를 저장하고 복원할 수 있는 모바일 운영 체제의 메커니즘입니다. 시스템은 UI 상태를 메모리 또는 영구 저장소에 저장하고 다시 열 때 복원합니다. Android Developers (2025)에 따르면, State Restoration은 고품질 사용자 경험을 추구하는 애플리케이션에 필수적입니다. State Restoration은 애플리케이션이 예기치 않게 종료될 때 데이터 손실을 방지하는 데 중요합니다.
주요 내용
State Restoration(상태 복원)은 애플리케이션 사용자 인터페이스의 현재 상태를 저장하고 종료 또는 재시작 후 복원할 수 있는 시스템 메커니즘입니다. 사용자가 애플리케이션을 최소화하거나 시스템이 리소스를 확보하기 위해 앱을 종료하면, State Restoration이 주요 UI 매개변수를 캡처하여 암호화된 저장소에 저장합니다.
State Restoration이 없으면 사용자는 앱 간 전환 시 저장되지 않은 모든 데이터를 잃게 됩니다. 예를 들어, 작성된 피드백 양식, 긴 검색어 또는 부분적으로 본 뉴스 목록 — 재시작 시 모두 사라집니다. State Restoration은 최소화하는 순간 ViewController 또는 Activity의 상태를 자동으로 캡처하여 이 문제를 해결합니다.
이 메커니즘은 시스템 수준에서 작동하며 두 주요 모바일 플랫폼에서 모두 지원됩니다. iOS는 NSUserActivity 및 UIStateRestoring 프로토콜을 통해 State Restoration을 제공하고, Android는 Jetpack 아키텍처 구성 요소 및 ViewModel의 SavedStateHandle을 통해 제공합니다. 구현 방식은 다르지만 개념은 동일합니다.
State Restoration 프로세스는 저장과 복원의 두 단계로 나뉩니다. 저장 단계에서 시스템은 해당 수명 주기 메서드를 호출하며, 애플리케이션은 현재 UI 상태를 간결한 표현으로 직렬화해야 합니다. 복원 단계에서 시스템은 저장된 데이터를 다시 전달하고, 애플리케이션은 이를 역직렬화하여 UI를 복원합니다.
저장은 애플리케이션이 백그라운드로 이동하거나 종료 임박 신호를 수신할 때 시스템에 의해 시작됩니다. iOS에서는 UIViewController의 encodeRestorableState 메서드가 호출되고, Android에서는 Activity의 onSaveInstanceState 또는 SavedStateHandle을 통한 저장이 수행됩니다. 데이터는 기본 유형(문자열, 숫자, 바이트 배열, Parcelable 객체)을 지원하는 형식으로 직렬화됩니다.
저장되는 데이터 양은 최소화해야 합니다. 시스템은 저장된 상태 번들의 크기에 제한을 둡니다. Android에서는 제한이 프로세스당 약 50KB입니다. 제한을 초과하면 TransactionTooLargeException 예외가 발생합니다. 따라서 아키텍트는 식별자와 키만 저장하고, 복원 시 영구 저장소에서 전체 데이터를 로드할 것을 권장합니다.
복원 시 시스템은 시작 시 저장된 데이터 번들을 애플리케이션에 전달합니다. iOS에서는 decodeRestorableState 메서드가 호출되고, Android에서는 onRestoreInstanceState 또는 SavedStateHandle에서 읽기가 수행됩니다. 애플리케이션은 번들에서 식별자와 키를 추출하고 UI(스크롤 위치, 선택된 항목, 입력된 데이터)를 복원합니다.
복원이 새 프로세스에서 발생할 수 있다는 점에 유의해야 합니다. 애플리케이션이 메모리에서 완전히 언로드된 경우 프로세스가 새로 생성되고 메모리의 모든 객체가 없습니다. 따라서 상태는 직렬화 가능해야 하며 이전 세션의 런타임 컨텍스트와 독립적이어야 합니다. 이는 여러 입력 필드가 있는 대규모 양식과 긴 다중 페이지 인터페이스에서 특히 중요합니다.
class MainActivity : AppCompatActivity() {
private var searchQuery: String = ""
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("search_query", searchQuery)
}
override fun onRestoreInstanceState(savedState: Bundle) {
super.onRestoreInstanceState(savedState)
searchQuery = savedState.getString("search_query", "")!!
restoreSearchUI(searchQuery)
}
}
State Restoration의 구현은 플랫폼 간에 크게 다릅니다. iOS는 스토리보드와 UIKit 프로토콜을 통한 선언적 접근 방식을 사용하는 반면, Android는 Activity 수명 주기 메서드와 Jetpack 아키텍처 구성 요소를 통한 명령적 접근 방식을 사용합니다. 접근 방식 선택은 대상 플랫폼과 애플리케이션 아키텍처에 따라 달라집니다.
iOS에서 State Restoration은 세 가지 구성 요소로 구축됩니다. UIApplication이 전체 프로세스를 관리하고, UIViewController가 UIStateRestoring 프로토콜을 구현하며, NSUserActivity가 탐색 복원을 위한 데이터를 저장합니다. 활성화하려면 UIViewController에 restorationIdentifier를 설정하고 encodeRestorableState 및 decodeRestorableState를 구현해야 합니다.
iOS는 restorationIdentifier가 설정된 경우 탐색 컨트롤러(UINavigationController)와 모든 중첩된 ViewController의 상태를 자동으로 저장합니다. 시스템은 탐색 스택을 관리하고 원래 상태로 복원합니다. 그러나 컨트롤러 내부의 데이터(입력된 텍스트, 스크롤 위치)는 개발자가 명시적으로 저장해야 합니다.
Android에서 State Restoration의 최신 접근 방식은 SavedStateHandle(AndroidX Lifecycle 라이브러리의 구성 요소)을 기반으로 합니다. SavedStateHandle은 ViewModel 내에서 액세스 가능하며, 구성 변경(화면 회전) 및 프로세스 재시작 시 데이터를 자동으로 저장하고 복원합니다. 데이터는 Bundle에 저장되고 자동으로 직렬화됩니다.
SavedStateHandle은 LiveData를 지원하는 키-값 저장소처럼 작동합니다. 구성 변경 시 데이터가 자동으로 저장되고 복원됩니다. 프로세스 재시작을 지원하려면 ViewModel을 SavedStateViewModelFactory를 통해 생성해야 합니다. 이를 통해 ViewModel이 완전한 앱 종료 후에도 유지될 수 있습니다.
class SearchViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
private val KEY_QUERY = string("search_query")
}
fun getSearchQuery(): String? = savedStateHandle[KEY_QUERY]
fun saveSearchQuery(query: String) {
savedStateHandle[KEY_QUERY] = query
}
}
State Restoration의 실용적인 구현에는 적절한 저장소 선택, 저장할 데이터 양 결정 및 다양한 종료 시나리오 테스트 등 여러 측면을 고려해야 합니다. Flutter 애플리케이션에서 state_restoration 패키지를 사용한 단계별 구현을 살펴보겠습니다.
class RestorableSearchField extends RestorableProperty<String> {
String _value = '';
@override
String get value => _value;
@override
void set value(String newValue) {
if (_value != newValue) {
_value = newValue;
notifyListeners();
}
}
@override
String? toPrimitives() => _value;
@override
void fromPrimitives(String? data) {
_value = data ?? '';
}
}
구현 시 저장 경계를 기억하는 것이 중요합니다. 모든 UI 필드를 복원할 필요는 없습니다. 긴 목록의 스크롤 위치 — 예. 일시적인 애니메이션 상태 — 아니요. 개발자는 어떤 데이터가 사용자 경험에 중요한지, 어떤 데이터가 편의성 손실 없이 안전하게 재설정될 수 있는지 의식적으로 선택해야 합니다.
State Restoration 테스트는 프로세스 종료 시뮬레이션이 필요한 별도의 작업입니다. Android에서는 adb shell am kill 명령을 통해 수행할 수 있고, iOS에서는 Xcode에서 종료 시뮬레이션을 통해 수행할 수 있습니다. Espresso 및 XCTest와 같은 UI 테스트 프레임워크는 상태 복원을 확인하기 위한 특수 메서드를 제공합니다.
첫 번째 규칙 — 데이터가 아닌 식별자를 저장하세요. 백 개의 필드가 있는 전체 객체를 저장하는 대신 고유 식별자를 저장하고, 복원 시 데이터베이스 또는 API에서 실제 데이터를 로드합니다. 이렇게 하면 Bundle의 공간을 절약하고 복원 시 데이터의 신선함을 보장합니다.
두 번째 규칙 — 모든 시나리오를 테스트하세요. 화면 회전 후, 최소화 후 한 시간 후에 돌아왔을 때, 메모리 부족으로 시스템이 앱을 종료한 후의 복원을 확인하세요. 각 시나리오는 운영 체제 상태와 사용 가능한 리소스에 따라 다르게 동작할 수 있습니다.
세 번째 규칙 — 사용자 정의가 아닌 시스템 메커니즘을 사용하세요. iOS와 Android는 각 플랫폼에 최적화된 State Restoration용 내장 API를 제공합니다. SharedPreferences 또는 UserDefaults를 통한 사용자 정의 구현은 동기화 문제와 복원 시 예기치 않은 동작을 초래할 수 있습니다.
네 번째 규칙 — 상태 부재 처리를 하세요. 첫 번째 실행 또는 데이터 삭제 후 상태가 없을 수 있습니다. UI는 예외를 발생시키지 않고 초기 상태에서 올바르게 작동해야 합니다. 사용 전에 모든 저장된 데이터를 null 확인하고 기본값을 제공하세요.
다섯 번째 규칙 — 저장된 키를 문서화하세요. 프로젝트에 수십 개의 화면이 있고 각 화면이 여러 필드를 저장하는 경우, 중앙 집중식 키 관리 없이는 혼란이 발생합니다. 각 모듈에 State Restoration용 키 상수가 있는 단일 클래스 또는 파일을 만드세요. 이렇게 하면 유지 관리가 간소화되고 리팩토링 시 우발적인 데이터 덮어쓰기를 방지할 수 있습니다.
자주 묻는 질문
State Restoration은 애플리케이션 재시작 또는 최소화 후 사용자 인터페이스를 저장하고 복원하여 데이터 및 사용자 컨텍스트 손실을 방지하는 메커니즘입니다.
State Restoration은 임시 UI 상태(스크롤 위치, 양식 데이터)를 저장하는 반면, 데이터베이스는 영구 사용자 데이터를 저장합니다. State Restoration은 볼륨 제한이 있는 시스템 메커니즘(Bundle, NSData)을 사용합니다.
AndroidX Lifecycle의 ViewModel에서 SavedStateHandle을 사용하세요. 최소화 시 자동으로 데이터를 저장하고 돌아올 때 복원합니다. 전체 재시작 지원을 위해 SavedStateViewModelFactory를 사용하세요.
UIViewController에 restorationIdentifier를 설정하고 encodeRestorableState 및 decodeRestorableState 메서드를 구현하세요. 탐색을 위해 컨트롤러 스택의 경로를 유지하는 NSUserActivity를 사용하세요.
전체 데이터가 아닌 식별자를 저장하세요: 선택된 항목의 ID, 검색어, 스크롤 위치, 토글 상태. 큰 객체와 이미지 저장은 피하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.