Activity 생명주기와 상태 보존
Activity 생명주기와 상태 보존
모든 안드로이드 Activity는 프레임워크의 ActivityManager 서비스와 ActivityThread 클래스가 관리하는 엄격한 생명주기(lifecycle)를 따릅니다. 시스템이 Activity를 포그라운드로 가져오거나, 전화 수신으로 일시 중지하거나, 메모리 회수를 위해 소멸시킬 때마다 각 전환은 특정 콜백을 트리거하며, 개발자는 이 콜백을 통해 리소스를 확보하거나, 상태를 저장하거나, 참조를 해제할 수 있습니다. 이러한 콜백의 순서와 보장 사항을 잘못 이해하면 프로덕션 앱에서 데이터 손실이나 화면 깨짐 현상이 빈번하게 발생합니다. 면접에서도 생명주기 관련 질문은 거의 빠지지 않으므로, 단순히 콜백 이름을 외우는 수준이 아니라 프레임워크 내부에서 어떻게 디스패치되는지까지 이해하고 있어야 합니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- 전화 수신으로
Activity가 중단되었다가 복귀할 때의 정확한 생명주기 콜백 순서를 추적할 수 있습니다. ActivityThread와Instrumentation이 내부적으로 생명주기 이벤트를 디스패치하는 역할을 설명할 수 있습니다.- 일시적 UI 상태(transient UI state)와 영구 데이터(persistent data)를 구분하고, 각각에 적합한 저장 콜백을 식별할 수 있습니다.
onSaveInstanceState가Bundle메커니즘 및 최신 Jetpack 컴포넌트의SavedStateRegistry와 어떻게 상호작용하는지 설명할 수 있습니다.onSaveInstanceState이후Fragment트랜잭션 커밋과 같은 흔한 함정을 피하는 방법을 적용할 수 있습니다.
프레임워크의 생명주기 콜백 디스패치 과정
Activity의 생명주기는 Activity 자체가 관리하지 않습니다. 시스템 서버 프로세스(system_server)에 상주하는 ActivityManagerService가 모든 프로세스에 걸쳐 각 Activity를 추적합니다. 상태 전환이 필요한 시점이 되면 ActivityManagerService는 Binder IPC를 통해 앱 프로세스로 트랜잭션을 전송하고, 앱 측에서는 메인 스레드의 진입점(entry point)인 ActivityThread가 이 트랜잭션을 수신하여 디스패치합니다.
내부적으로 생명주기 전환은 ClientTransactionItem 객체로 모델링됩니다. 가령, 시스템이 Activity를 일시 중지하기로 결정하면 PauseActivityItem 트랜잭션을 전송합니다. ActivityThread는 이 트랜잭션을 처리하면서 Instrumentation.callActivityOnPause()를 호출하고, 이 메서드는 다시 Activity.performPause()를 호출합니다. performPause()는 내부 상태 플래그를 갱신한 뒤, 개발자에게 노출되는 onPause() 콜백을 실행합니다.
// 프레임워크 디스패치 체인을 간략화한 코드
// ActivityThread.java (프레임워크 소스)
private fun handlePauseActivity(token: IBinder, finishing: Boolean) {
val r = mActivities[token] // ActivityClientRecord
performPauseActivity(r, finishing)
}
private fun performPauseActivity(r: ActivityClientRecord, finishing: Boolean) {
mInstrumentation.callActivityOnPause(r.activity)
r.paused = true
}
핵심은 프레임워크가 콜백 순서를 절대적으로 통제한다는 점입니다. 개발자가 콜백 순서를 바꾸거나 건너뛸 수는 없습니다. 또한 프레임워크는 사용자가 명시적으로 Activity를 종료하지 않았는데 소멸될 가능성이 있을 때만 onSaveInstanceState를 호출할지 여부를 결정합니다. 면접에서 "콜백 순서를 개발자가 제어할 수 있나요?"라는 질문이 나오면, 프레임워크가 전적으로 관리하며 개발자는 각 콜백 안에서 적절한 작업을 수행하는 것만 가능하다고 답하면 됩니다.
전화 수신 시 생명주기 전환 과정
사용자가 앱을 사용하는 중에 전화가 걸려 오면 전화 앱의 Activity가 포그라운드로 올라오면서 현재 Activity를 완전히 가립니다. 이때 시스템은 정확한 순서로 전환을 트리거합니다.
포그라운드를 떠날 때
-
onPause():Activity가 포그라운드 포커스를 잃습니다. 이 시점에서 윈도우는 아직 연결되어 있지만 더 이상 최상위 인터랙티브 표면이 아닙니다. 프레임워크는 다음Activity의onResume()이 실행되기 전에 이 콜백이 완료되는 것을 보장하므로,onPause()안에서는 빠르게 실행할 수 있는 작업만 수행해야 합니다. -
onStop():Activity가 더 이상 보이지 않습니다. 전화 UI가 완전히 덮고 있기 때문입니다. 이 시점에서 윈도우 매니저가 GPU 메모리를 절약하기 위해 윈도우 서피스(surface)를 해제할 수 있습니다. -
onSaveInstanceState(outState: Bundle): 사용자가 명시적으로 종료하지 않았으므로 프로세스 종료로 이어질 수 있는 상태이기 때문에 프레임워크가 이 콜백을 호출합니다. API 28(Android P) 이상을 타겟팅하는 앱에서는onSaveInstanceState가onStop()이후에 호출되는 것이 보장됩니다. 기본 구현은 뷰 계층 구조를 순회하면서 ID가 지정된 모든View의 상태(EditText내용, 스크롤 위치, 체크 상태 등)를 저장합니다.
시스템이 메모리 회수를 위해 프로세스를 종료하지 않는 한, Activity 객체는 통화 중 내내 정지 상태(stopped state)로 메모리에 남아 있습니다.
포그라운드로 복귀할 때
-
onRestart():Activity가 정지 상태에서 시작 상태로 전환될 때만 호출됩니다. 최초 생성 시에는 호출되지 않으므로, 재방문 시에만 실행해야 하는 로직(가령 오래된 데이터 갱신)에 유용합니다. -
onStart():Activity가 다시 보이기 시작합니다. 윈도우 서피스가 다시 연결되며, 등록된LifecycleObserver인스턴스가ON_START이벤트를 수신합니다. -
onResume():Activity가 액티비티 스택 최상단에 도달하여 인터랙티브 상태가 됩니다. 입력 이벤트가 다시 이Activity로 전달됩니다.