Kotlin

[Kotlin Coroutine] 생명주기를 인식하여 안전하게 코루틴 사용하기

반응형

 

Kotlin 코루틴은 비동기 코드를 작성할 수 있게 하는 API를 제공한다. Kotlin 코루틴을 사용하면 코루틴이 실행되어야 하는 시기를 관리하는 데 도움이 되는 CoroutineScope 를 정의할 수 있다.

 

수명 주기 인식 코루틴 범위

ViewModelScope

ViewModelScope 는 앱의 각 ViewModel 을 대상으로 정의한다. 이 범위에서 시작된 모든 코루틴은 ViewModel 이 삭제되면 자동으로 취소된다. ViewModel 이 활성 상태인 경우엠나 실행해야 할 작업이 있을 때 유용하다.

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

 

 

LifecycleScope

LifecycleScope 는 각 Lifecycle 객체에서 정의된다. 이 범위에서 실행된 코루틴은 Lifecycle 이 끝날 때 제거된다. lifecycle.coroutineScope 또는 lifecycleOwner.lifecycleScope 속성을 통해 CoroutineScope 에 접근 할 수 있다.

 

 

lifecycleScope는 LifecycleCoroutineScope를 리턴하며

launchWhenXXX 자체가 LifecycleCoroutineScope 들어있다.

CoroutineScope tied to this LifecycleOwner’s Lifecycle. This scope will be cancelled when the Lifecycle is destroyed. This scope is bound to Dispatchers.Main.immediate

lifecycleScope의 CoroutineScope는 LifecycleOwner의 생명주기에 따르며, 이 스코프는 Lifecycle이 destoryed되면 소멸된다. 그리고 lifecycleScope는 DIspatchers.Main.immediate 에 묶여져 함께 동작한다.

 

즉, lifecycleScope를 사용하면 Context Switching 하는 곳에서 순서가 보장이 된다는 것이다.

 

 

LifecycleCoroutineScope

CoroutineScope tied to a Lifecycle and Dispatchers.Main.immediate This scope will be cancelled when the Lifecycle is destroyed. This scope provides specialised versions of launch: launchWhenCreated, launchWhenStarted, launchWhenResumed

 

수명 주기 인식 코루틴 정지

lifecycleScope.launchWhenCreated, Started, Resumed

CoroutineScope 을 사용하면 적절하게 실행 작업을 자동으로 취소할 수 있지만, Lifecycle 이 특정 상태에 있지 않다면 코드 블록의 실행을 중단해야 하는 경우가 있다.

ex) FragmentTransaction 을 실행하려면 Lifecycle이 적어도 STARTED 상태가 될 떄 까지 기다려야 하는데 이러한 상황을 위해 Lifecycle 은 lifecycle.launchWhenX 를 제공한다.

Lifecycle 및 Dispatchers.Main.immediate에 연결된 CoroutineScope로 수명주기가 소멸(destoryed)되면 이 스코프도 함께 취소된다.

이 스코프는 launchWhenCreated, launchWhenStarted, launchWhenResumed를 제공한다. 이 함수들은 when 이후 접미어에 해당하는 생명주기에 맞춰 실행이 되고, 생명주기의 상태가 충족되지 않으면 정지가 되는 함수이다.

 

class MainActivity : AppCompatActivity() {
    val TAG = "Daewon"

    val countingFlow: Flow<Int> = flow {
        var count = 0
        while(true) {
            delay(1000L)
            emit(count++)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launchWhenStarted {
            countingFlow.collect {
                Log.d(TAG, "$it")
            }
        }
    }
}

 

위 예제코드를 보면 중간에 홈버튼을 눌러 STOPPED 상태가 되어 생명주기의 상태를 충족시키지 못해 작업하던 코루틴이 정지(suspended) 상태가 되고, 다시 앱으로 진입하여 생명주기가 충족되면 재개된다.

위 메서드를 사용할 때 data 가 Lifecycle 범위 내에서 유효할 때만(미리 계산된 데이터) 사용해야 한다. 액티비티나 프래그먼트가 다시 시작되면 코루틴은 다시 시작되지 않기(중단됐던게 다시 재개되는 것) 때문이다.

 

재시작 가능한 수명 주기 인식 코루틴

repeatOnLifecycle

Lifecycle이 Destroyed 일 때, lifecycleScope 가 실행 작업을 자동으로 취소하는 방법도 있지만 Lifecycle 이 특정 상태에 있을 때 코드 블록의 실행을 시작하고 다른 상태에 있을 때 취소하려는 경우가 있다.

즉, launchWhenX 는 함수 호출을 통해 자동으로 생명주기에 맞추어 시작, 중단, 재개가 된다. 하지만 생명주기에 맞춰 코루틴을 시작, 취소, 재시작 하기 위해서는 repeatOnLifeCycle 확장함수를 사용할 수 있다.

 

class MainActivity : AppCompatActivity() {
    val TAG = "Daewon"

    val countingFlow: Flow<Int> = flow {
        var count = 0
        while(true) {
            delay(1000L)
            emit(count++)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                countingFlow.collect {
                    Log.d(TAG, "$it")
                }
            }
        }
    }
  }

 

앞선 예제와 똑같은 상황이지만 백그라운드로 나갔다가 다시 진입했을 때 코루틴이 취소가 되어 다시 처음부터 재시작한다는 것을 알 수 있다.

 

launchWhenX vs repeatOnLifecycle

결국 차이점은 주어진 생명주기의 조건에 부합하지 못했을 때 코루틴을 정지(suspended) 상태로 두냐, 취소(Cancelled)상태로 두냐의 차이이다.

상황에 따라 핫 스트림을 두어 코루틴을 취소하지 않고 백그라운드 혹은 일시정지 상태로 둘 수 있고, 메모리 누수를 방지하고자 repeatOnLifecycle을 사용하여 코루틴을 취소/ 재시작 할 수 있다.

 

또한, 중요한 것은 UI를 업데이트 해야 하는 경우 launch 또는 launchIn 확장 함수로 UI 에서 직접 흐름을 수집하면 안된다. 이러한 함수는 뷰가 표시 되지 않는 경우에는 이벤트를 처리하여 앱이 다운될 수 있다. 이를 방지하려면 repeatOnLifecycle API 를 사용해야 한다.

 

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

 

 

 

참조

https://www.charlezz.com/?p=46044 

 

생명주기에 맞춰 안전하게 코루틴 사용하기 | 찰스의 안드로이드

생명주기에 안전한 코루틴 lifecycle 컴포넌트를 사용한다면, 생명주기를 인식하는 코루틴을 만들 수 있다. LifecycleOwner로써 취급되는 AppCompatActivity(ComponentActivity) 또는 Fragment를 일반적으로 사용할

www.charlezz.com

https://developer.android.com/topic/libraries/architecture/coroutines?hl=ko 

 

수명 주기 인식 구성요소와 함께 Kotlin 코루틴 사용  |  Android 개발자  |  Android Developers

수명 주기 인식 구성요소와 함께 Kotlin 코루틴 사용 Kotlin 코루틴은 비동기 코드를 작성할 수 있게 하는 API를 제공합니다. Kotlin 코루틴을 사용하면 코루틴이 실행되어야 하는 시기를 관리하는 데

developer.android.com

 

반응형