티스토리 뷰

들어가며
Compose로 개발하면서 "Activity 라이프사이클 아직도 알아야 해?" 라는 의문이 들었다. 결론부터 말하면 알아야 하는데, 생각보다 적게 알아도 된다.
React 개발자 관점에서 정리해봤다.
Activity 라이프사이클 기본
onCreate → onStart → onResume → [실행 중] → onPause → onStop → onDestroy
콜백 상태 메모리
| onCreate | 생성됨 | 할당 |
| onStart | 화면에 보임 | 유지 |
| onResume | 상호작용 가능 | 유지 |
| onPause | 일부 가려짐 | 유지 |
| onStop | 완전히 안 보임 | 유지 |
| onDestroy | 죽음 | 해제 |
상황별 호출 정리
상황 호출
| 다이얼로그 뜸 | onPause |
| 홈 버튼 / 다른 앱 전환 / 백버튼 | onPause → onStop |
| 화면 회전 / 다크모드 전환 | onDestroy → onCreate (재생성) |
| 최근 앱에서 스와이프 | onDestroy (진짜 종료) |
참고: Android 12부터 백버튼 동작 변경
예전: 백버튼 → 앱 종료
지금: 백버튼 → 백그라운드 전환 (onStop)
진짜 종료는 최근 앱에서 스와이프해야 한다.
React와 비교
Android React 의미
| onCreate | 마운트 | 최초 생성 |
| onDestroy | 언마운트 | 제거 |
| onPause/onResume | visibilitychange | 화면 전환 |
핵심 차이
브라우저: 탭 닫기 전까지 안 죽음 → 라이프사이클 단순
Android: 시스템이 언제든 앱 죽일 수 있음 → 라이프사이클 복잡
// React - 탭 전환 감지 (선택적)
useEffect(() => {
const handleVisibility = () => {
if (document.hidden) pause()
else play()
}
document.addEventListener('visibilitychange', handleVisibility)
return () => document.removeEventListener('visibilitychange', handleVisibility)
}, [])
// Android - 백그라운드 전환 감지 (필수에 가까움)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_STOP -> pause()
Lifecycle.Event.ON_START -> play()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
Compose에서 진짜 신경 써야 할 것
결론: 2가지만
- 백그라운드 리소스 관리 (onStop)
- 상태 복원 (Configuration Change)
신경 안 써도 되는 것
- onPause/onResume 구분: Compose Dialog는 같은 Activity 안이라 라이프사이클 변화 없음. 시스템 다이얼로그 떠도 Activity 살아있으니 특별히 할 게 없음.
1. 백그라운드 리소스 관리
사실 대부분 라이브러리가 해줌
// CameraX - lifecycleOwner 넘기면 끝
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview
)
// ExoPlayer - 자동 처리 옵션 있음
player.setPlayWhenReady(true)
직접 해야 하는 경우
Analytics 로깅 정도?
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> analytics.logScreenView("Home")
Lifecycle.Event.ON_PAUSE -> analytics.logScreenExit("Home")
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
2. 상태 복원 (Configuration Change)
화면 회전, 다크모드 전환 시 Activity가 죽었다 다시 생성된다.
상황 remember rememberSaveable ViewModel
| Recomposition | ✅ | ✅ | ✅ |
| 화면 회전 | ❌ | ✅ | ✅ |
| 프로세스 킬 | ❌ | ✅ | ❌ |
// ❌ 화면 회전하면 날아감
var text by remember { mutableStateOf("") }
// ✅ 화면 회전해도 유지
var text by rememberSaveable { mutableStateOf("") }
판단 기준
- 임시 UI 상태 (애니메이션 등) → remember
- 사용자 입력값 (폼 데이터) → rememberSaveable
- 비즈니스 로직 상태 → ViewModel
LocalLifecycleOwner 사용법
기본 패턴
@Composable
fun MyScreen() {
val lifecycleOwner = LocalLifecycleOwner.current
// 라이브러리에 넘김
cameraProvider.bindToLifecycle(lifecycleOwner, ...)
}
어디서 오는 거?
setContent 내부에서 CompositionLocal로 주입됨. React의 Context와 같은 개념.
// setContent 내부 (간략화)
CompositionLocalProvider(
LocalLifecycleOwner provides this, // Activity
LocalContext provides this,
) {
content()
}
Navigation 쓰면 달라짐
NavHost(...) {
composable("home") {
// LocalLifecycleOwner = NavBackStackEntry (화면별로 분리)
}
}
상황 LocalLifecycleOwner
| Activity + setContent | Activity |
| Fragment + ComposeView | Fragment |
| Navigation Compose | NavBackStackEntry |
Navigation 2 vs 3 (LifecycleOwner 관점)
Nav2: 자동
NavHost(...) {
composable("home") {
// 그냥 됨. NavBackStackEntry가 LifecycleOwner.
}
}
Nav3: 명시적
NavDisplay(
backStack = backStack,
entryDecorators = listOf(
rememberSceneSetupNavEntryDecorator(), // Lifecycle
rememberSavedStateNavEntryDecorator(), // 상태 저장
rememberViewModelStoreNavEntryDecorator() // ViewModel scoping
),
)
decorator 안 넣으면 LifecycleOwner도 없고, ViewModel scoping도 안 됨.
왜 바꿨나?
Nav2: 배터리 포함 (자동) → 커스텀 어려움
Nav3: 배터리 별매 (명시적) → 필요한 것만 조합
Compose 철학이 "암묵적 마법 X, 명시적 조합 O"라서.
NavEntry의 Lifecycle 상태
Navigation 사용 시 각 화면(NavEntry)은 Activity처럼 상태를 가짐.
상태 의미
| CREATED | 백스택에 있음 |
| STARTED | 화면 전환 애니메이션 중 |
| RESUMED | 현재 화면 |
| DESTROYED | 백스택에서 제거됨 |
Home → Detail 이동 (애니메이션 중)
├── Home: STARTED
└── Detail: STARTED
애니메이션 끝
├── Home: CREATED (백스택)
└── Detail: RESUMED (현재)
이게 왜 중요하냐면:
LaunchedEffect(Unit) {
// RESUMED일 때만 실행됨
// 백스택에 있으면 (CREATED) 중단됨
}
백스택 화면에서 불필요한 작업 안 돌아감.
정리
콜백 Compose에서 할 일
| onPause | 거의 없음 |
| onStop | 거의 없음 (라이브러리가 처리) |
| onDestroy (Configuration Change) | remember → rememberSaveable 판단 |
진짜 결론: Compose에서 라이프사이클 관련해서 신경 쓸 건 rememberSaveable 쓸지 말지 정도. 나머지는 라이브러리한테 lifecycleOwner 넘기면 끝.
댓글