[Android] Channel vs SharedFlow

우발자·2025년 9월 5일
1

안드로이드 개발을 할 때 이벤트를 수집하고 소비하는 방식에는 크게 2가지가 있다. Channel과 SharedFlow이다. 이 두가지에 대해 차이점과 특징을 알아볼려고 한다.


Channel

// MainViewModel.kt
private val _channel = Channel<Int>()
val channel = _channel.receiveAsFlow()

요렇게 사용된다. channel은 기본적으로 Flow형태가 아님으로 수집을 할때는 Flow형태로 수집할 수 있도록 .receiveAsFlow()를 사용하여 coldFlow로 변형해준다.

우선 특징들을 말해보자면 크게 3가지가 있다.
1. 백그라운드에서 수집이 가능하다.
2. 단일 구독자인 경우에 주로 사용된다.
3. buffer가 꽉차면 수집을 suspend한다.

// MainViewModel.kt
    private val _channel = Channel<Int>(3)
    val channel = _channel.receiveAsFlow()

    init {
        viewModelScope.launch {
            repeat(100){
                Log.d("Channel", "send $it")
                _channel.send(it)
                delay(1000)
            }
        }
    }
    
// MainScreen.kt
   val lifecycleOwner = LocalLifecycleOwner.current
    LaunchedEffect(Unit) {
        delay(3000)
        lifecycleOwner.lifecycle.repeatOnLifecycle(state = Lifecycle.State.STARTED) {
            viewModel.channel.collect {
                Log.d("Channel", "MainScreen[1]: collect $it")
            }
        }
    }

이렇게 구성되어 있다고 가정해보자

실행하면 로그는 어떻게 찍히는지도 살펴보자

우선 첫 실행 했을 때다. 수집한 뒤 onStart가 되자마자 소비하는 걸 볼 수 있다. 그럼 백그라운드에서 동작도 한번 보자

onStop이 호출된 순간 소비를 멈추고 백그라운드로 진입를 했는데도 Channel에선 수집을 멈추지 않는다. buffer를 총 4개를 담는 걸 볼 수 있다. (아마도 4개인 이유는 디폴트인 0으로 설정하면 한개를 기본적으로 담아두는데 buffer + 1이라고 봐야될 것 같다.) 그리고 난 후 buffer가 꽉차면 수집을 suspend한 뒤 다시 포그라운드로 왔을 때 onStart가 호출되면 한번에 3,4,5,6을 소비하는 것을 볼 수 있다. 소비가 끝나자 다시 수집을 시작한다.

이번엔 구독자가 2명인 경우 어떻게 되나 살펴보자


사이좋게 하나씩 소비하는 걸 확인해볼수 있었다.

그럼 만약 백그라운드에서 버퍼를 4개 채우고 다시 포그라운드 왔을 때 2개씩 나눠서 소비를 할까?

정답은 독식(?)한다!. 그다음 소비할 녀석이 모두 가져가버린다.
그래서 만약 구독자가 2명 이상이라면 이벤트 처리가 매우 어려울 것 같다.

그래서 구독자가 2명이상일 때는 SharedFlow를 쓰는 것 같다.


SharedFlow

	// MainViewModel.kt
 	private val _sharedFlow = MutableSharedFlow<Int>()
    val sharedFlow = _sharedFlow.asSharedFlow()

    init {
        viewModelScope.launch {
            repeat(100){
                Log.d("sharedFlow", "send $it")
                _sharedFlow.emit(it)
                delay(1000)
            }
        }
    }
    
    
   // MainScreen.kt
    LaunchedEffect(Unit) {
        lifecycleOwner.lifecycle.repeatOnLifecycle(state = Lifecycle.State.STARTED) {
            viewModel.sharedFlow.collect {
                Log.d("sharedFlow", "MainScreen[1]: collect $it")
            }
        }
    }

이렇게 구성해보았다.

포그라운드에선 Channel하고 같은 동작을 기대할 수 있을 것이다.

하지만 백그라운드로 가는 순간 완전히 달라진다.

백그라운드로 가는 순간 수집은 하지만 구독자가 없으므로 그동안 수집했던 데이터가 유실된다. 이게 가장 큰 특징인 것 같다.

그럼 SharedFlow에서 extraBufferCapacity와 replay도 같이 알아보자

extraBufferCapacity

구독자가 있을 경우에 소비가 느릴 때는 extraBufferCapacity + 1만큼 버퍼에 쌓아둔다. 수집과 소비를 최적화를 시킬 때 주로 사용한다.

하지만 구독자가 없을 경우에는 적용되지않고 계속 수집하게된다.

replay

이건 구독할 때 replay에 설정값만큼 소비하게 된다.

로그를 보면 가장 최신값 3,4,5를 한번에 받을 수 있다.

구독자가 2명이면 어떻게 될까?


2명의 구독자가 같은 값을 받는 걸 볼 수 있다.


결론

그래서 뭘 쓰라고?

흠,, 나도 최근까지는 SharedFlow로 이벤트 처리를 해왔다.
하지만 테드박님에 글을 읽고 생각이 조금 달라졌다.Channel은 단일 구독자라는 단점이 있지만 안드로이드 개발을 하면서 UI에서 이벤트 처리가 굳이 2명 이상의 구독자가 필요할 까라는 생각이 들었다. 그리고 백그라운드에서 event를 사용자에게 안보여줘도 될까? 라는 생각도 들면서 앞으로의 개발을 할때는 Channel을 사용 할 것 같다.

profile
나는 안드로이드 개발자다.

0개의 댓글