Compose에서 생명주기 다루기

권민주·2025년 10월 12일

안드로이드

목록 보기
19/23
post-thumbnail

1. 생명주기

1)lifecycle

Compose에서는 Activity에 직접 의존하지 않도록 설계되어 있기 때문에 Lifecycle을 통해 생명주기를 다룹니다. androidx.lifecycle 패키지는 수명 주기 인식 구성요소를 빌드할 수 있는 클래스 및 인터페이스를 제공합니다. 이때 수명 주기 인식 구성 요소는 activity나 fragment의 현재 수명 주기 상태를 기반으로 동작을 자동 조정할 수 있는 구성요소를 말합니다. Lifecycle은 activity나 fragment와 같은 구성요소의 수명 주기 상태 관련 정보를 포함하며 해당 상태를 다른 객체가 관찰할 수 있게 하는 클래스입니다. Lifecycle은 두 가지 주요 열거를 사용하여 연결된 구성요소의 수명 주기 상태를 추적합니다.

  • 이벤트: 프레임워크 및 Lifecycle 클래스에서 전달되는 수명 주기 이벤트. 이러한 이벤트는 activity와 fragment의 콜백 이벤트에 매핑
  • 상태: Lifecycle 객체가 추적한 구성요소의 현재 상태

Lifecycle에 직접 접근하여 사용하지는 않습니다. LifecycleOwner를 통해 사용됩니다. LifecycleOwner이 Lifecycle을 보유하고 있고 getLifecycle()을 통해 외부에서 접근합니다. 생명주기 상태가 변경되면 이를 관찰하다가 해당 변화를 인식할 수 있는 LifecycleObserver 인터페이스가 있습니다. 관찰자를 등록하면 생명주기 이벤트가 발생할 때마다 콜백을 받게 됩니다. LifecycleOwner 내부에는 LifecycleRegistry가 있는데 생명주기 이벤트가 발생할 때 LifecycleRegistry가 현재 상태를 변경하고 등록된 LifecycleObserver들에게 해당 이벤트를 전달하게 됩니다.

안드로이드에서 제공하는 DefaultLifecycleObserver에서 onCreate, onStart 등의 상응하는 메서드를 재정의하여 구성요소의 수명 주기 상태를 모니터링할 수 있습니다. 다만 Compose는 상태 기반 UI 프레임워크이기 때문에 굳이 모든 생명 주기 단계를 정의하는 DefaultLifecycleObserver는 적절하지 않습니다. 대신 LifecycleEventObserver 인터페이스를 사용할 수 있습니다. 단일 메서드으로 onStateChanged()만 있기에 직접 필요한 이벤트만 분기 처리할 수 있습니다.

2) LifecycleOwner

LifecycleOwner는 클래스에 Lifecycle이 있음을 나타내는 단일 메서드 인터페이스입니다. 이 인터페이스에는 클래스에서 구현해야 하는 getLifecycle() 메서드 하나만 있습니다. 만약 전체 애플리케이션 프로세스의 수명 주기를 관리하려는 경우 ProcessLifecycleOwner를 사용할 수 있습니다.

이 인터페이스는 Fragment 및 AppCompatActivity와 같은 개별 클래스에서 Lifecycle의 소유권을 추출하여 함께 작동하는 구성요소를 작성할 수 있게 합니다. 이때 Lifecycle은 현재 Lifecycle.State를 StateFlow 형태의 currentStateFlow로 노출합니다. 이 Flow는 State로 수집할 수 있습니다. 이렇게 하면 앱이 컴포지션 중에 Lifecycle의 변경사항을 읽을 수 있습니다.

val lifecycleOwner = LocalLifecycleOwner.current
val stateFlow = lifecycleOwner.lifecycle.currentStateFlow
…
val currentLifecycleState by stateFlow.collectAsState()

앞의 예시는 lifecycle-common 모듈을 사용하여 접근할 수 있습니다. currentStateAsState() 메서드는 lifecycle-runtime-compose 모듈에서 사용할 수 있으며 한 줄로 현재 Lifecycle 상태를 간단하게 읽을 수 있습니다.

val lifecycleOwner = LocalLifecycleOwner.current
val currentLifecycleState = lifecycleOwner.lifecycle.currentStateAsState()

모든 사용자 정의 애플리케이션 클래스는 LifecycleOwner 인터페이스를 구현할 수 있습니다.

2. 생명주기를 다루는 메소드들

1)LocalLifecycleOwner

  • 정의
    • Compose에서 현재 문맥(Activity, Fragment, NavBackStackEntry 등)에 매핑된 LifecycleOwner를 제공하는 CompositionLocal
  • 동작 방식
    • val lifecycleOwner = LocalLifecycleOwner.current
    • 이를 통해 lifecycleOwner.lifecycle로 lifecycle에 접근
  • 주의점
    • NavHost/Navigation을 사용할 경우 LocalLifecycleOwner가 NavBackStackEntry에 의해 바뀔 수 있음. 즉 activity의 생명주기를 못 받음.
    • DisposableEffect/LaunchedEffect 등의 키로 lifecycleOwner를 사용해 재등록 조건을 제어하는 것이 좋음

2)LifecycleEventObserver

  • 정의
    • LifecycleObserver의 한 구현 형태로, onStateChanged(owner, event)lifecycle 이벤트를 직접 전달 받음
  • 동작 방식
    • lifecycle.addObserver(observer)로 등록하고 removeObserver로 해제
    • 이벤트 기반 처리에 적절.
  • 사용
    • 특정 lifecycle 이벤트에 대해 정확히 한 번 반응해야 할 때
    • 이벤트 별 세밀한 제어가 필요할 때
  • 장점
    • 이벤트 단위로 쉽고 명확하게 동작 정의 가능
  • 주의점
    • suspend 작업을 직접 할 수 없어 내부에서 코루틴을 launch 해야 함
    • 등록/제거를 잊으면 메모리 누수 가능성이 있어 DisposableEffect와 조합 권장

3)LaunchedEffect

  • 정의
    • Compose의 side-effect API. Composable의 생명주기나 composition에 묶여 코루틴을 시작하고, 키가 바뀌거나 Composable이 사라지면 코루틴을 자동 취소/재시작
  • 동작 방식
    • LaunchedEffect(key1, key2) 내부는 suspend 가능한 코루틴 블록
    • 키가 변경되면 기존 코루틴이 취소되고 새로운 코루틴이 시작
    • Composable이 composition에서 제거되면 코루틴이 취소
  • 사용
    • Composable이 존재하는 동안 유지되어야 하는 coroutine 작업(예: UI와 연동되는 Flow 수집, 초기화용 suspend 호출 등)
    • 키 변경 시 재시작이 필요할 때
  • 장점
    • 구성 생명주기와 구조적 동시성이 보장
    • suspend 함수 직접 호출 가능
  • 주의점
    • 키 관리를 잘못하면 불필요한 재시작(재실행)이 발생
    • 재구성과 별개로 키가 바뀌면 재시작되어 의도치 않은 재시작에 주의 필요

4)DisposableEffect

  • 정의
    • Composable이 등록/해제해야 하는 비코루틴의 자원(리스너, Observer, 콜백 등)을 관리하기 위한 API
    • 생성 시점에 설정하고 onDispose에서 해제
  • 동작 방식
    • DisposableEffect(key) 블록은 즉시 실행되고 onDispose { ... }로 정리 코드를 제공
    • 키 변경 또는 composition에서 제거될 때 onDispose가 호출
  • 사용
    • Android 리스너, Lifecycle.addObserver 같은 등록/해제 패턴
    • 비동기 suspend 코드 없이 단순한 자원 등록과 해제가 필요한 경우
  • 장점
    • 명시적 등록/해제 제공하여 메모리 누수 방지 가능
    • 재구성으로 인한 불필요한 재등록을 키로 제어 가능
  • 주의점
    • 직접 suspend 호출 불가. suspend 호출이 필요하면 내부에서 코루틴을 직접 launch 해야 함.
    • 등록/해제를 수동으로 관리해야 함. 물론 onDispose로 안전하게 처리 가능.

5)LifecycleEventEffect

  • 정의
    • Compose에서 Lifecycle.Event를 감지하기 위한 간결한 API
    • 내부적으로 DisposableEffect + LifecycleEventObserver를 결합한 축약 형태
    • 기존처럼 observer 등록/해제를 수동으로 관리하지 않아도 됨
    • 특정 Lifecycle 이벤트에 맞춰 UI나 로직을 갱신할 때 사용
  • 동작 방식
    • Composable이 composition에 들어올 때, LifecycleEventObserver를 자동 등록
    • Composable이 composition에서 제거될 때, observer 자동 해제
    • 이벤트 발생 시 LifecycleOwner, Lifecycle.Event 콜백을 호출
    • suspend 함수는 직접 호출할 수 없음. 즉시 처리만 가능
  • 사용
    • Lifecycle 이벤트에 맞춰 UI를 갱신하거나, 특정 동작을 실행해야 할 때
    • suspend 함수 호출이 필요 없고, 단순히 이벤트 기반 로직만 실행할 때
    • 기존 DisposableEffect + LifecycleEventObserver 코드가 길어서 단순화하고 싶을 때
  • 장점
    • 코드 간결. observer 등록/해제를 자동으로 관리
    • Composable 생명주기와 자동 연동. Composable이 사라지면 observer도 자동 해제
    • 이벤트 단위 처리에 특화. Lifecycle 이벤트를 직접 받아 처리 가능
    • 보일러플레이트 제거. DisposableEffect 블록 작성 불필요
  • 주의점
    • suspend 함수 호출 불가. 코루틴 기반 처리(delay, Flow.collect)는 지원되지 않아 필요하면 LaunchedEffect나 repeatOnLifecycle과 조합해야 함
    • 이벤트 기반 한정. 상태 기반 반복 작업에는 부적합
    • Observer 동작 커스터마이징 불가. 내부적으로 observer 생성/해제가 고정되어 있음

6) repeatOnLifecycle

  • 정의
    • Lifecycle의 특정 상태 동안 suspend 블록을 실행하고 상태가 벗어나면 해당 블록을 취소했다가 다시 상태가 되면 재시작하는 편리한 확장 함수. 코루틴 기반
  • 동작 방식
    • lifecycle.repeatOnLifecycle(state) { /* suspend block */ }는 호출된 코루틴 내에서 동작(launch {}나 LaunchedEffect 내부 등)
    • Lifecycle이 목표 상태에 들어오면 블록을 실행하기 위해 새 코루틴을 시작하고, 상태가 벗어나면 해당 코루틴을 취소. 상태가 다시 오면 새 코루틴으로 재시작
  • 사용
    • Flow를 수집하거나 suspend 작업을 Lifecycle 상태에 맞춰 안전하게 실행할 때(예: 화면 보이는 동안만 collect).
  • 장점
    • Flow 수집과 같은 반복적 suspend 작업을 Lifecycle 상태에 자동으로 맞춰 관리해 줌
    • ON_STOP 등으로 내려가면 자동으로 취소되어 리소스 사용 최소화
  • 주의점
    • repeatOnLifecycle 자체는 suspend 함수이므로 반드시 코루틴 컨텍스트에서 호출해야 함
    • 진입/탈출마다 블록이 새 코루틴으로 다시 실행되므로 블록 내부에서 상태를 잘 관리해야 함(예: 중복 시작 방지, 초기화 비용 고려).

7)rememberUpdatedState

  • 정의
    • 재구성으로 값, 특히 람다가 바뀌어도 장기 보유 코드에서 최신 값을 안전하게 참조하도록 하는 유틸.
  • 동작 방식
    • val current = rememberUpdatedState(value), current.value는 최신 값을 가리키도록 업데이트
    • Observer/콜백 같은 장기간 살아있는 객체에 최신 람다/값을 전달할 때 사용
  • 사용
    • DisposableEffect에서 등록한 observer가 콜백을 호출할 때, 콜백 내부에서 최신 Composable 상태/람다를 사용해야 할 경우
  • 장점
    • Observer를 재등록하지 않고도 최신 상태를 안전하게 사용 가능하여 불필요한 등록/해제 회피
  • 주의점
    • rememberUpdatedState는 값의 동시성 보장을 단순화하지만, 상태 변경과 observer 호출 시점 간의 경쟁 상태를 완전히 제거해 주지 않음. 상황에 따라 동기화 필요.

3. 사용 가능 조합

1)LaunchedEffect + repeatOnLifecycle

  • 목적
    • Composable에 묶인 코루틴으로, Lifecycle 상태 동안 안전하게 Flow를 수집하거나 suspend 작업을 실행하려 할 때.
  • 역할 분리
    • LaunchedEffect는 composition 연결된 코루틴 스코프, repeatOnLifecycle는 lifecycle 상태 기반 반복 실행·자동 취소.

2)DisposableEffect + LifecycleEventObserver

  • 목적
    • 이벤트 단위를 감지해 간단한 작업을 하고, observer 등록/해제를 안전하게 처리하려는 경우.
  • 역할 분리
    • DisposableEffect는 등록/해제 생명주기 관리, LifecycleEventObserver는 이벤트 감지.

3)LaunchedEffect + LifecycleEventObserver

  • 목적
    • 이벤트 기반으로 동작하면서도 동시에 suspend 작업(예: delay, Flow collect)이 필요할 때.
  • 주의
    • observer 제거를 try/finally 또는 onDispose처럼 확실히 처리해야 메모리 누수 방지.

4)rememberUpdatedState + DisposableEffect

  • 목적
    • observer를 재등록하지 않고도 콜백이 최신 Composable 상태/람다를 사용하게 하려는 경우.
  • 역할
    • rememberUpdatedState는 최신 값 참조를 보장, DisposableEffect는 observer의 수명 관리.

5)LifecycleEventEffect

  • 목적
    • 이벤트 감지 전용과 suspend 작업 병행이 필요하는 경우

4. 주의해야할 실수

  • Observer 등록을 컴포지션 내부에서 직접 하면 재구성마다 중복 등록 발생하기에 DisposableEffect(lifecycleOwner)로 관리
  • suspend 작업은 DisposableEffect 본문에서 직접 호출 불가하기에 내부에서 코루틴 launch 하거나 LaunchedEffect/repeatOnLifecycle 사용
  • Observer 내부에서 콜백 직접 캡처하면, 재구성 후에는 해당 콜백이 최신 상태를 가리키지 않게 되기에 rememberUpdatedState 사용하여 항상 최신 값을 참조 필요
  • 반복 폴링(while + delay)로 lifecycle 상태 체크는 비효율적이기에 repeatOnLifecycle 또는 observer 사용 권장
  • LaunchedEffect의 키 관리 부실로 불필요한 재시작 발생할 수 있기에 키를 최소화하거나 의도적으로 관리

참고

profile
안드로이드 개발자:D

0개의 댓글