안드로이드 프레임워크·시스템 학습 노트 목차

코루틴 on Android — lifecycleScope·viewModelScope·repeatOnLifecycle

[andkt 07·08]에서 코루틴·Flow의 언어 기능을 봤다면, 이 장은 그것을 안드로이드 생명주기에 안전하게 묶는 법이다. 핵심은 "코루틴을 올바른 스코프에 시작해, 화면이 사라지면 자동으로 취소되게"생명주기를 벗어난 코루틴이 죽은 화면을 건드려 누수·크래시가 나는 걸 막는다. (출처: Android — Coroutines best practices / Lifecycle-aware.)

생명주기 스코프 — 화면 수명에 묶인 코루틴

안드로이드는 생명주기에 묶인 CoroutineScope를 제공한다 — 그 생명주기가 끝나면 안의 코루틴을 전부 자동 취소([andkt 07] 구조적 동시성).

스코프취소 시점용도
viewModelScopeViewModel onCleared화면 상태 로직(권장)
lifecycleScopeLifecycleOwner 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.)

Navigation — 화면 이동·백스택·타입 안전 경로권한·보안 — 런타임 권한·저장소·암호화·네트워크