백그라운드에 있던 앱 프로세스를 OS가 메모리 회수를 위해 통째로 종료하는 것.
회전과 무엇이 다른가
-
회전 — Activity 만 죽는다. ViewModel 은 산다
-
프로세스 죽음 — 프로세스가 죽는다. ViewModel 도 메모리에 있었으니 함께 사라진다
-
그런데 사용자 입장에선 "내 앱으로 돌아왔을 뿐" 이다
- OS 는 마지막 화면을 다시 그려 줘야 한다
- 프로세스를 새로 띄우고 Activity 를 재생성한다
- ViewModel 은 비어 있고, 사용자는 "방금 보던 그 화면" 을 기대한다
SavedStateHandle이 간극을 메운다
@HiltViewModel
class SearchViewModel @Inject constructor(
private val handle: SavedStateHandle
) : ViewModel() {
val query: StateFlow<String> = handle.getStateFlow("query", "")
fun onQueryChange(q: String) { handle["query"] = q }
}
프로세스가 죽기 직전 OS 가 작은 Bundle 을 디스크에 저장한다 SavedStateHandle 은 그 Bundle 에 묶여 복원 시 값을 들고 돌아온다
정리
회전 → ViewModel
프로세스 죽음 → SavedStateHandle
큰 데이터 → 어느 쪽에도 담지 않고 DB 에서 다시 로드
Bundle 크기 제한
Bundle 은 Binder 트랜잭션으로 전달된다 → 버퍼가 약 1MB 넘으면 TransactionTooLargeException 으로 앱이 죽는다
- 담아도 되는 것 — 검색어 · 선택된 탭 · 스크롤 위치 · 화면 인자 id 담으면 안 되는 것 목록 데이터 · 비트맵 · 큰 JSON
어떻게 재현하나
개발자 옵션 → "활동 유지 안 함" 을 켠다
- 화면을 벗어나는 순간 Activity 가 파괴돼 복원 경로를 매번 탄다
또는 앱을 백그라운드로 보낸 뒤 adb shell am kill <package> (force-stop 이 아니다 — force-stop 은 상태 저장까지 지운다)
회전만 시켜 보고 "복원 잘 된다" 고 결론 내리는 것이 가장 흔한 검증 누락이다
면접 함정
- ❌ "프로세스가 죽으면 앱을 처음부터 다시 시작한다" → OS는 마지막 화면부터 복원하려 한다. 그래서 상태 저장이 필요하다.
- ❌ "onSaveInstanceState는 백그라운드로 갈 때만 불린다" → 구성 변경 때도 불린다. 두 경로가 같은 Bundle을 쓴다.
앱이 죽는 우선순위
OS 는 메모리가 부족하면 낮은 순위부터 죽인다
- 포그라운드 프로세스 — 지금 보이는 화면 — 거의 안 죽는다
- 가시 프로세스 — 일부 보임 (다이얼로그 뒤)
- 서비스 프로세스 — 포그라운드 서비스
- 백그라운드 프로세스 — 안 보이는 Activity — LRU 로 오래된 것부터 죽는다
- 빈 프로세스 캐시만 — 가장 먼저 죽는다
"내 앱은 안 죽겠지" 는 저사양 기기·게임을 오가는 사용자 앞에서 틀린다
복원 흐름
사용자가 앱 아이콘/최근앱을 누른다
- OS 가 프로세스를 새로 만든다
- Application.onCreate
- 마지막 Activity 를 저장된 Bundle 과 함께 재생성
- onCreate(savedInstanceState != null)
앱 입장에서는 '콜드 스타트인데 savedInstanceState 가 있는' 상태다
무엇이 복원되고 무엇이 안 되나
자동 복원
- View 의 id 가 있는 기본 상태 (EditText 텍스트 · 스크롤 위치)
- rememberSaveable · SavedStateHandle
- Navigation 백스택과 경로 인자
복원 안 됨
- ViewModel 의 필드
- 싱글턴에 담아 둔 메모리 캐시
- 정적 변수 (전부 초기화된다)
"싱글턴에 로그인 정보를 들고 있다" 는 앱이 프로세스 죽음 후 로그아웃되는 원인이다