안드로이드에서의 구성 변경(Configuration Changes) 처리
안드로이드에서의 구성 변경(Configuration Changes) 처리
구성 변경(Configuration Changes)이란 디바이스의 상태가 변화하여 사용 가능한 리소스에 영향을 미치는 상황을 말합니다. 화면 회전, 로케일(언어) 변경, 다크 모드 전환, 글꼴 크기 조절 등이 모두 구성 변경에 해당합니다. 기본적으로 안드로이드 시스템은 구성 변경이 발생하면 현재 Activity를 소멸시키고 다시 생성합니다. 이 과정에서 일시적 UI 상태(transient UI state)가 유실될 수 있으므로, 앱에서 명시적으로 상태를 보존하는 전략을 갖추어야 합니다. 실제 면접에서도 구성 변경과 상태 보존 전략에 관한 질문은 매우 빈번하게 출제되므로, 각 메커니즘의 차이와 사용 시점을 명확하게 이해하는 것이 중요합니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- 구성 변경을 유발하는 요인과 시스템이
Activity를 재생성하는 이유를 설명할 수 있습니다. ViewModel을 활용하여 구성 변경 시에도 데이터를 유지하는 방법을 이해할 수 있습니다.onSaveInstanceState와rememberSaveable을 사용하여 경량 UI 상태를 보존하는 방법을 학습하실 수 있습니다.android:configChanges가 적절한 상황과 그에 따른 트레이드오프를 판단할 수 있습니다.SavedStateHandle이ViewModel과 저장된 인스턴스 상태(Saved Instance State)를 어떻게 연결하는지 설명할 수 있습니다.
구성 변경을 유발하는 요인
구성 변경에는 화면 회전, 로케일(언어) 변경, 다크 모드 전환, 글꼴 크기 조절, 키보드 연결 상태 변경, 화면 밀도(density) 변경 등이 포함됩니다. 폴더블 기기나 태블릿에서의 멀티 윈도우 전환 역시 사용 가능한 디스플레이 영역이 크게 달라지므로 구성 변경을 유발합니다. 면접에서 이 질문이 나왔을 때, 단순히 "화면 회전"만 언급하기보다는 다양한 유형을 함께 언급하면 더 깊이 있는 답변이 됩니다.
시스템이 Activity를 재생성하는 이유
구성 변경이 발생하면 시스템은 새로운 구성에 맞는 리소스를 다시 로드해야 합니다. 가령, 세로 모드에서 가로 모드로 회전하면 다른 레이아웃 파일, 문자열 리소스, 드로어블 한정자(drawable qualifier)가 선택될 수 있습니다. 시스템이 올바른 리소스를 보장하는 가장 확실한 방법은 Activity를 소멸시키고, 업데이트된 Configuration 객체를 기반으로 새 인스턴스를 생성하는 것입니다.
이로 인해 Activity는 onPause → onStop → onDestroy 순서로 소멸된 뒤, 새 인스턴스가 onCreate → onStart → onResume 순서로 재생성됩니다. Activity 필드에만 저장되어 있던 데이터는 이 과정에서 모두 유실됩니다. 이러한 생명주기 흐름을 정확히 이해해야 올바른 상태 보존 전략을 선택할 수 있습니다.
모든 구성 변경이 동일한 리소스 재로드를 유발하는 것은 아닙니다. 화면 회전은 레이아웃 한정자를 다시 로드하고, 로케일 변경은 문자열 리소스를 다시 로드합니다. 다크 모드 전환은 테마 속성과 색상 리소스를 재로드하며, 글꼴 크기 변경은 sp 단위로 스케일링되는 치수 리소스에 영향을 줍니다. 시스템은 매니페스트에서 명시적으로 제외하지 않는 한 이 모든 경우를 동일하게 Activity를 소멸하고 재생성하는 방식으로 처리합니다. 각 변경 유형은 Configuration 클래스의 상수로 식별되며, CONFIG_ORIENTATION, CONFIG_LOCALE, CONFIG_UI_MODE 등이 있고, 시스템은 업데이트된 Configuration 객체를 새 Activity에 전달합니다.
ViewModel을 활용한 구성 변경 대응
ViewModel은 Activity의 ViewModelStoreOwner에 스코프가 지정되어 있어, 구성 변경으로 인한 소멸에서도 살아남습니다. ViewModelStore는 프레임워크의 NonConfigurationInstances 메커니즘을 통해 유지되므로, 동일한 ViewModel 인스턴스가 새로운 Activity에서도 그대로 사용됩니다.
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value++
}
}