코루틴 on Android — lifecycleScope·viewModelScope·repeatOnLifecycle
[andkt 07·08]에서 코루틴·Flow의 언어 기능을 봤다면, 이 장은 그것을 안드로이드 생명주기에 안전하게 묶는 법이다. 핵심은 "코루틴을 올바른 스코프에 시작해, 화면이 사라지면 자동으로 취소되게" — 생명주기를 벗어난 코루틴이 죽은 화면을 건드려 누수·크래시가 나는 걸 막는다. (출처: Android — Coroutines best practices / Lifecycle-aware.)
생명주기 스코프 — 화면 수명에 묶인 코루틴
안드로이드는 생명주기에 묶인 CoroutineScope를 제공한다 — 그 생명주기가 끝나면 안의 코루틴을 전부 자동 취소([andkt 07] 구조적 동시성).
| 스코프 | 취소 시점 | 용도 |
|---|---|---|
viewModelScope | ViewModel onCleared | 화면 상태 로직(권장) |
lifecycleScope | LifecycleOwner DESTROYED | 화면 한정 작업 |
rememberCoroutineScope | 컴포지션 이탈 | Compose 콜백 |
// ViewModel — 회전에도 살아남고, 화면이 끝나면 취소 (가장 권장)
viewModelScope.launch { repo.refresh() }
// Activity/Fragment — 화면 수명 한정
lifecycleScope.launch { /* ... */ }
*비동기 작업은 대개 viewModelScope*에 둔다 — 회전에 살아남고(ViewModel이 생존)·화면 종료 시 취소되어 누수가 없다.
repeatOnLifecycle — Flow를 안전하게 수집
가장 흔한 함정 — Flow를 그냥 lifecycleScope.launch { flow.collect() }로 수집하면, 화면이 백그라운드(STOPPED)로 가도 수집이 계속된다(자원 낭비·UI 갱신 시도 크래시). **repeatOnLifecycle(STARTED)**는 STARTED일 때 수집을 시작하고 STOPPED면 취소, 다시 STARTED면 재시작한다.
class MyFragment : Fragment() {
override fun onViewCreated(v: View, s: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch { // 뷰 생명주기 ([01])
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { render(it) } // 보일 때만 수집
}
}
}
}
Compose에선 **collectAsStateWithLifecycle()**가 이 일을 한 줄로 해준다([05]).
Dispatcher와 메인 스레드 — Main.immediate
UI 갱신은 Dispatchers.Main에서. Main.immediate는 이미 메인 스레드면 즉시 실행(불필요한 재디스패치 회피)이라 상태 갱신에 자주 쓰인다. *무거운 작업은 withContext(IO/Default)*로 옮기고([andkt 07]), 돌아와 UI 갱신하는 패턴은 동일하다.
stateIn — cold Flow를 화면 상태로
Repository의 cold Flow를 화면이 구독할 hot StateFlow로 바꿀 때 stateIn을 쓰고, 구독자가 있을 때만 활성화하는 SharingStarted.WhileSubscribed(5000)이 표준이다(5초는 회전 같은 짧은 단절에 재구독 비용 회피).
val uiState: StateFlow<UiState> = repo.observe()
.map { UiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UiState.Loading)
모범 사례
- 디스패처를 주입하라 —
Dispatchers.IO를 하드코딩하지 말고 생성자로 주입(테스트에서TestDispatcher교체). - suspend 함수는 메인 안전(main-safe)하게 — 내부에서 필요하면
withContext로 스레드 전환해, 호출자가 스레드를 신경 안 쓰게. viewModelScope를 기본으로, 화면 한정 UI 작업만lifecycleScope.GlobalScope금지 — 구조적 동시성을 벗어나 누수가 난다.
정리 — 스코프가 생명주기를 따르게
코루틴 on Android의 핵심 — viewModelScope(기본)·lifecycleScope(화면 한정)로 시작해 생명주기 종료 시 자동 취소, Flow 수집은 repeatOnLifecycle(STARTED)/collectAsStateWithLifecycle로 안전하게, 디스패처는 주입·suspend는 메인 안전, cold→hot은 stateIn(WhileSubscribed), GlobalScope 금지. 코루틴을 화면 수명에 묶는 것이 누수 없는 안드로이드 비동기의 전부다.
한 줄 요약 — 생명주기 스코프:
viewModelScope(기본·회전 생존)·lifecycleScope(화면 한정)·rememberCoroutineScope(Compose). Flow 수집은repeatOnLifecycle(STARTED)/collectAsStateWithLifecycle(STOPPED면 취소). 디스패처 주입·suspend는 main-safe·stateIn(WhileSubscribed(5000))·GlobalScope금지.
(출처: Android — Coroutines best practices / Lifecycle-aware components / A safer way to collect flows.)