repeatOnLifecycle vs launchWhenStarted 완전 비교


1. 왜 두 API가 모두 존재하는가?

Android에서 Lifecycle을 인지하는 코루틴 수집 방식은 시간이 지나며 변화했습니다.

launchWhenStarted (구) → 메모리/리소스 낭비 문제 발견
       ↓
repeatOnLifecycle (신) → 문제 해결, 현재 공식 권장 방식

launchWhenStarted는 현재 deprecated 되었으며, repeatOnLifecycle이 표준입니다.


2. launchWhenStarted의 문제

동작 원리

lifecycleScope.launchWhenStarted {
    flow.collect { value ->
        updateUI(value)
    }
}

핵심 문제: 백그라운드에서도 업스트림 Flow는 계속 수집됨

lifecycleScope.launchWhenStarted {
    locationFlow.collect { location ->
        // Activity가 백그라운드로 가도 collect 코드 실행은 멈춤
        // 하지만 locationFlow 자체의 업스트림(GPS 콜백 등)은 계속 동작!
        updateMap(location)
    }
}
Activity STARTED  → collect 블록 실행 중
Activity STOPPED  → collect 블록 실행 일시정지 (suspend)
                     하지만 GPS 콜백, 네트워크 연결 등 업스트림은 계속 살아있음
                     → 배터리 소모, 메모리 낭비

3. repeatOnLifecycle로 해결

동작 원리

viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        locationFlow.collect { location ->
            updateMap(location)
        }
    }
}
Activity STARTED → 코루틴 A 시작 (collect 시작)
Activity STOPPED → 코루틴 A 취소 (collect 완전 종료, 업스트림도 취소됨)
Activity STARTED → 코루틴 B 시작 (collect 재시작)

업스트림 Flow까지 완전히 취소되므로 백그라운드에서 리소스를 사용하지 않습니다.


4. 핵심 차이 비교

구분 launchWhenStarted repeatOnLifecycle
상태 미달 시 동작 suspend (일시정지) 취소 (cancel)
업스트림 Flow 계속 활성 상태 유지 완전히 취소됨
상태 재도달 시 이어서 실행 처음부터 재시작
메모리/배터리 낭비 가능 효율적
현재 상태 Deprecated 공식 권장

5. 코드 비교 — 실제 동작 확인

// launchWhenStarted: 백그라운드에서도 Flow 생산자가 살아있음
lifecycleScope.launchWhenStarted {
    flow {
        while (true) {
            emit(System.currentTimeMillis())
            delay(1000)
            println("Flow 생산 중...")  // STOPPED 상태에도 출력 계속됨!
        }
    }.collect { time ->
        updateUI(time)  // 이 부분만 실행이 멈춤
    }
}
// repeatOnLifecycle: STOPPED 시 생산자까지 완전히 취소
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        flow {
            while (true) {
                emit(System.currentTimeMillis())
                delay(1000)
                println("Flow 생산 중...")  // STOPPED 시 더 이상 출력 안 됨
            }
        }.collect { time ->
            updateUI(time)
        }
    }
}

6. 사용할 때 주의사항

Fragment에서는 viewLifecycleOwner 사용

class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // ⚠️ this.lifecycleScope 사용 시 Fragment의 생명주기를 따름 (View보다 길게 유지됨)
        // ✅ viewLifecycleOwner.lifecycleScope 사용 권장
        viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    renderState(state)  // View가 살아있을 때만 접근
                }
            }
        }
    }
}

Fragment의 View는 onDestroyView에서 파괴되지만 Fragment 객체는 더 오래 살 수 있습니다.
lifecycleScope(Fragment 기준)를 쓰면 View가 없는데도 코루틴이 View에 접근해 크래시가 날 수 있습니다.

여러 Flow를 동시에 수집

viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        launch { viewModel.uiState.collect { renderState(it) } }
        launch { viewModel.events.collect { handleEvent(it) } }
        // 두 collect가 각각 독립적인 코루틴으로 동시 실행
    }
}

7. collectAsStateWithLifecycle (Compose)

Compose에서는 repeatOnLifecycle을 직접 작성할 필요 없이 라이브러리 함수로 대체됩니다.

@Composable
fun MyScreen(viewModel: MyViewModel) {
    // 내부적으로 repeatOnLifecycle(STARTED)와 동일하게 동작
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    Text(text = uiState.message)
}
// build.gradle.kts
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")

8. 어떤 State를 지정해야 할까

State 사용 시점
STARTED 가장 일반적, 화면이 보일 때만 데이터 갱신 (기본 권장)
RESUMED 화면이 완전히 포그라운드일 때만 (애니메이션, 카메라 등)
CREATED 화면이 살아있는 동안 항상 (드물게 사용)
// 화면이 완전히 활성화되어야 하는 카메라 프리뷰
repeatOnLifecycle(Lifecycle.State.RESUMED) {
    cameraFlow.collect { frame -> processFrame(frame) }
}

9. 정리



Related Posts