[Coroutine] repeatOnLifecycle에 대해 알아보자!

 

Flow는 스스로 라이프사이클을 알지 못하기 때문에 CoroutineScope에 따라 생명주기에 따라 맞추어 사용하며 불필요한 메모리나 리소스가 낭비되지 않도록 하는 것이 중요합니다!

이번에는 LifecycleOwner.launchWhenStarted, Lifecycle.repeatOnLifecycle 등이 어떻게 다른지 알아보고

어떻게 불필요한 리소스 낭비를 방지해야 할지 알아보려고 합니다. 


lifecycleScope.launch

 

private val _stateFlow = MutableStateFlow(0)
val stateFlow get() = _stateFlow

private fun testCount() {
	viewModelScope.launch(Dispatchers.IO) {
    	repeat(10) {
        	_stateFlow.value = it
            delay(3000)
		 }
	 }
 }
    
    
 lifecycleScope.launch {
 	viewModel.stateFlow.collect {
    		Log.d("MainActivity","number : $it")
        }
 }

간단한 숫자를 출력하는 flow를 만들어 알아보도록 하겠습니다. flow를 이용하여 데이터를 collect 하는 job이 존재합니다. lifecycleScope 안에서 flow에 대한 collect이 호출됩니다.

 

해당 코드를 실행하고 홈 버튼을 눌러 백그라운드로 가거나 앱을 종료하게 되면 어떻게 될까요?

 

먼저, 백버튼을 통해 Activity가 finish()되어 onDestroy가 호출되면 데이터 수집이 중단됩니다.

위 사진은 홈버튼을 누른 후 백그라운드에 진입했을 때 입니다. 홈버튼을 눌러 백그라운드 상태가 되면 Activity의 생명주기는 onStop이 됩니다. onStop이 된 경우에도, 여전히 데이터 스트림에 대한 collect가 일어나는 것을 확인할 수 있습니다. 사용자가 앱을 작동하지 않는 상태에서 collect가 발생하는 것은 불필요한 메모리나 리소스가 낭비될 수 있습니다!

 

 

그럼 어떻게 해결해야 할까요?

 

private var job : Job? = null

job = lifecycleScope.launch {
            viewModel.stateFlow().collect {
                Log.d("MainActivity","number : $it")
            }
        }



override fun onStop() {
	super.onStop()
	job?.cancel()
}

다음과 같이 onStop에서 job을 cancel 해주어야 백그라운드에서 데이터가 수집되는 것을 방지할 수 있습니다.

하지만, Job을 생성하고 해제해주는 코드는 보일러 플레이트 코드를 생성하며, 까먹고 cancel를 해주지 않은 경우 메모리 사용량이 증가할 가능성이 있습니다.

 

이제, 이를 방지할 수 있는 lifecycleScope.launchWhenStarted와 repeatOnLifeCycler에 대해 알아보도록 하겠습니다.

 

 

launchWhenStarted

 

lifecycleScope.launchWhenCreated, Started, Resumed 스코프가 있으며 해당 스코프는 when 이후 접미어에 해당하는 생명주기에 맞춰 실행이 되고, 생명주기의 상태가 충족되지 않으면 정지가 되는 함수입니다.

lifecycleScope.launchWhenStarted {
	viewModel.stateFlow.collect {
    	Log.d("MainActivity","number : $it")
    }
}

다음 코드를 실행한 결과를 확인해봅시다.

 홈버튼을 눌러 onStop() 상태가 된다면 생명주기의 상태를 충족시키지 못해 코루틴이 suspend 상태가 됩니다. 그 후 앱을 다시 진입하면 조건을 다시 충족하기 때문에 데이터 수집을 재개합니다. 즉, Lifecycle이 destroyed 될 때 job이 cancel 될 수 있습니다.

 

 

repeatOnLifeCycler

repeatOnLifeCycle은 앞서 소개한 스코프와 달리 재구성이 가능합니다. launchWhenstarted는 백그라운드에서 포그라운드로 진입할 때 일시 중지된 코루틴을 재개한다면 repeatOnLifeCycle은 다시 돌아오면 처음부터 재구성이 가능합니다. 또한, launchWhen 함수는 자동으로 생명주기에 맞추어지지만 생명주기에 맞춰 코루틴을 시작, 취소, 재시작하기 위해서는 repeatOnLifeCycle 확장함수를 사용할 수 있습니다. 즉, repeatOnLifecycle 은 호출하는 coroutine을 suspend 시키고, lifecycle 이 target state 에서 벗어나면 재시작 합니다. 그리고 Lifecycle 이 destroyed 되면 호출하는 coroutine 을 resume 시킵니다.

lifecycleScope.launch {
  	repeatOnLifecycle(Lifecycle.State.STARTED) {
    	viewModel.stateFlow.collect {
        	Log.d("MainActivity", "number : $it")
        }
    }
}

repeatOnLifeCycle의 State를 STARTED로 두고 실행 결과를 살펴보도록 하겠습니다.

먼저 왼쪽 결과는 홈버튼을 눌러 백그라운드로 진입 후 다시 포그라운드로 돌아온 상태입니다. onStop()인 상태에서 잠시 일시중지 하다가 onStart()가 되면 재개합니다. 오른쪽은 백버튼을 클릭하면 activity를 destoryed 한 경우입니다. 이 경우 다시 앱이 시작되면 처음부터 재구성하는 결과를 얻을 수 있습니다.

 

 

마무리

상황에 따라 필요한 생명주기에 맞추어 사용한다면 메모리 누수나 불필요한 리소스 방지를 할 수 있으니 알아두면 좋을 것 같습니다!

 

 

참고

https://codedaeng.tistory.com/32

https://hongbeomi.medium.com/%EB%B2%88%EC%97%AD-android-ui%EC%97%90%EC%84%9C-flow%EB%A5%BC-%EC%88%98%EC%A7%91%ED%95%98%EB%8A%94-%EC%95%88%EC%A0%84%ED%95%9C-%EA%B8%B8-bd8449e67ec3