코루틴 스코프 (1)

KSang·2024년 3월 11일
0

TIL

목록 보기
86/101

오늘은 코루틴 스코프에 대해 정리 해보려 한다.

코루틴 스코프는 코루틴이 언제 어디서 실행 될지, 그리고 어떻게 생명주기를 관리할지를 결정하는 데 사용된다.

실행 범위와 생명주기를 제어하는건데, 다양한 종류의 스코프가 있다.

GlobalScope

애플리케이션의 생명주기와 연결되어 있다.

코루틴은 앱이 종료될 떄까지 계속 존재할 수 있는데, 메모리 누수의 위험이 있으니 함부로 쓰지 않는걸 권장한다.

주로 앱의 백그라운드에서 지속적으로 데이터를 수집하거나 업데이트하는 작업,
예를 들면 사용자의 위치를 주기적으로 추적하고 데이터베이스에 저장하는 서비스등을 구현할때 사용된다.

GlobalScope.launch {
    while(isActive) {
        updateLocationData()
        delay(1000L)  // 1초마다 반복
    }
}

CoroutuneScope

가장 기본적인 코루틴 스코프다.

사용자가 커스텀해서 사용할 수 있는 장점이 있다.

선언부를 들어가보면 주석으로 다음과 같이 설명되어 있다.

지정된 코루틴 콘텍스트를 감싸는 코루틴 범위를 만듭니다. 지정된 콘텍스트에 Job 요소가 없으면 기본 Job()이 생성됩니다. 이렇게 하면 이 범위에서 하위 코루틴이 실패하거나 범위 자체를 취소하면 coroutineScope 블록 내와 마찬가지로 모든 스코프의 하위 코루트가 취소됩니다.

  CoroutineScope(Dispatchers.IO).launch {
              //code
          }

다중 job설정 또한 가능하며 Job을 설정하면서 스코프를 제어할 수 있다.

class DownloadManager {
    private var job: Job = Job()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    fun startDownload(url: String) {
        scope.launch {
            downloadFile(url)
        }
        scope.launch {
            downloadImage(url)
        }
    }

    fun cancelDownload() {
        job.cancel()
    }
}

다음과 같이 선언한경우 cancelDowload함수를 이용해서 downloadFile과 downloadImage를 같이 취소 할 수 있다.

cancel이외에도

job.isActive 를 통해 코루틴이 실행 중인지 확인할 수 있다.
job.isCancelled 를 통해 코루틴이 취소되었는지 확인할 수 있다.
job.isCompleted 를 통해 코루틴이 완료되었는지 확인할 수 있다.

LifecycleScope

안드로이드 컴포넌트의 생명주기에 맞춰 비동기 작업을 실행 할 때 사용한다.

컴포넌트의 생명주기에 따라 코루틴을 취소해 메모리 누수를 방지한다.

액티비티나 프래그먼트에서 인터페이스를 업데이트 할 때 주로 사용된다.

코드에서 선언부를 보면 다음과 같이 정의 되어 있다.

coroutineScope는 이 라이프사이클 소유자의 라이프사이클과 연계되어 있습니다.
라이프사이클이 destoryed 되면 이 스코프가 취소됩니다.
이 스코프는 Dispatchers.Main.immediate에 바인딩되어있습니다

소유자는 선언되는 액티비티나 프래그먼트를 말한다.

라이프사이클이 destoryed 되면 이 스코프가 취소됩니다 라고 되어 있는대

lifecycleScope.launch(Dispatchers.IO){
	repeat(MAX_VALUE) {
    	Log.D("", "on")
        }
}

이렇게 선언하고 앱을 끄면 어떨까?

로그를 보면 앱을 꺼도 계속 찍힐 것 이다.

이미 반복문이 수행되고 나면 작업이 완료될때까지 수행하기 때문에 반복되는 중간에 끊기진 않는다.

	lifecycleScope.launch(Dispatchers.IO){
    	flow{
        	list.forEach { item ->
            	emit(item)
                delay(1000)
            }
        }.collect { user ->
        	Log.d("",user)
        }
    }

Flow를 collect하는 경우엔 앱이 종료되는 순간 멈추게 된다.

Flow와 lifecycleScope를 사용할 땐 주의가 필요하다.

위에서도 설명 했듯 onDestroy시 Job이 cancel된다고 한다.

그말은 즉 onStop 되었을때고 Job이 계속 수행된다고 볼 수 있다.

그게 왜 문제가 될까?

class MainActivity: Activity() {
	private val exFlow = flow {
    	for (i in 0..100) {
        emit("$i")
        delay(1000)
        }
    }
    ...
	override fun onCreate(savedInstanceState: Bundle?) {
    	super.onCreate(savedInstanceState)
        lifecycleScope.launch {
        exFlow.collect {
        	Log.d("",it)

1초에 1씩 증가하는 로그를 찍을때, 앱을 종료하지 않고 홈키를 눌러서 바탕화면으로 이동하면 어떻게 될까

그러면 Activity는 Destroyed가 되지 않고 onStop상태가 되어 계속 로직을 수행할 것 이다.

저게 만약 화면의 Ui를 구현하는 데이터 스트림이라면 리소스가 낭비되고 앱이 비정상 종료 될 수 있다.

이를 해결하기 위해 뷰모델의 생명주기에 맞춰 onStart에 Job을 실행하고 onStop되었을때 멈추게 할 수 도 있다.

	override fun onStart() {
    	super.onStart()
        job = lifecycleScope.launch {
        exFlow.collect {
        ...

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

이렇게 구현하면 홈키를 눌러 화면을 이동할때 데이터 스트림이 발생하지 않는다.

이런 상황을 위해 API를 안드로이드 스튜디오에서 제공해주는 repeatOnLifecycle함수를 사용한다.

repeatOnLifecycle

class MyFragment : Fragment() {

    val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.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.
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // This happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                viewModel.someDataFlow.collect {
                    // Process item
                }
            }
        }
    }
}

디벨로퍼의 가이드에서 제공하는 코드이다

보면 lifecycleScope안에 repeatOnLifecycle을 사용해 코루틴 스코프를 관리하는걸 볼 수 있다.

선언부는 어떻게 구현되어 있을까?

            // Runs the block of code in a coroutine when the lifecycle is at least STARTED.
            // The coroutine will be cancelled when the ON_STOP event happens and will
            // restart executing if the lifecycle receives the ON_START event again.
            
public suspend fun LifecycleOwner.repeatOnLifecycle(
    state: Lifecycle.State,
    block: suspend CoroutineScope.() -> Unit
): Unit = lifecycle.repeatOnLifecycle(state, block)

소유자의 Lifecycle이 최소 onStart가 되었을 때 블록에 있는 메서드들이 수행되며, onStop 되었을땐 Job이 취소 된다.

간단히 말하면 생명주기가 포그라운드에 있을때만 진행된다고 보면된다.

0개의 댓글