시배's Android
Hi Jack Mocker | 개선기 (5) feat.SharedFlow 본문
Hi Jack Mocker란?
GitHub - koreatlwls/Hi-Jack-Mocker: Hi-Jack-Mocker is a project that leverages OkHttp3's interceptor to intercept and modify net
Hi-Jack-Mocker is a project that leverages OkHttp3's interceptor to intercept and modify network requests and responses, allowing you to verify the UI easily. - koreatlwls/Hi-Jack-Mocker
github.com
Hi Jack Mocker는 비개발자도 UI 엣지 케이스를 쉽게 테스트할 수 있도록 돕는 라이브러리입니다. 이 프로젝트는 OkHttp3 인터셉터를 활용하여 네트워크 요청과 응답을 가로채고 수정할 수 있게 합니다. 이를 통해 개발자뿐만 아니라 다양한 사용자들이 다양한 시나리오를 테스트할 수 있게 합니다.
소개
응답을 가로채고 수정한 후 다시 전달하는 과정에서 흥미로운 문제에 직면했고, 이를 해결하는 과정에서 코틀린의 Channel에서 SharedFlow로 전환하게 되었습니다. 이 포스트에서는 그 과정과 학습한 내용을 공유하고자 합니다.
초기 구현 : Channel 사용
처음에는 다음과 같은 방식으로 구현했습니다.
override fun intercept(chain: Interceptor.Chain): Response = runBlocking {
var response = chain.proceed(chain.request())
if (hjmDataStore.getHjmMode()) {
interceptorManager.sendWithInterceptorChannel(response)
startHjmActivityIfNeeded()
response = interceptorManager.receiveWithResultChannel()
}
return@runBlocking response
}
suspend fun receiveWithResultChannel(): Response {
if (resultChannel.isClosedForReceive) {
resultChannel = Channel(UNLIMITED)
}
return resultChannel.receive()
}
이 구현에서는 Channel을 사용하여 HjmActivity에서 수정된 응답을 다시 Interceptor로 전달했습니다.
문제점 인식
이 접근 방식은 단일 요청에 대해서는 잘 작동했지만, 여러 개의 동시 요청을 처리할 때 문제가 발생했습니다. Channel의 특성상 첫 번째로 도착한 응답이 첫 번째로 대기 중인 Interceptor에 전달되는 문제가 있었습니다.
해결책 : SharedFlow 도입
이 문제를 해결하기 위해 SharedFlow를 도입했습니다. SharedFlow는 여러 소비자가 동시에 값을 관찰할 수 있어, 우리의 사용 사례에 더 적합했습니다.
override fun intercept(chain: Interceptor.Chain): Response = runBlocking {
var response = chain.proceed(chain.request())
if (hjmDataStore.getHjmMode()) {
val uuid = UUID.randomUUID().toString()
interceptorManager.sendEventAtInterceptorEvent(uuid, response)
startHjmActivityIfNeeded()
response = interceptorManager.resultEvent.filter { it.first == uuid }.first().second
}
return@runBlocking response
}
class InterceptorManager {
val isHjmActivityRunning = AtomicBoolean(false)
private val _interceptorEvent = MutableSharedFlow<Pair<String, Response>>(replay = 1)
val interceptorEvent = _interceptorEvent.asSharedFlow()
private val _resultEvent = MutableSharedFlow<Pair<String, Response>>()
val resultEvent = _resultEvent.asSharedFlow()
suspend fun sendEventAtInterceptorEvent(uuid: String, response: Response) {
_interceptorEvent.emit(Pair(uuid, response))
}
suspend fun sendEventAtResultEvent(uuid: String, response: Response) {
_resultEvent.emit(Pair(uuid, response))
}
}
Channel과 SharedFlow의 주요 차이점
1. 데이터 공유 방식:
- Channel: point-to-point 통신 방식으로, 하나의 sender와 하나의 receiver가 연결되어 데이터를 주고받습니다. 즉, 각각의 collector는 독립적인 데이터 스트림을 받습니다.
SharedFlow: broadcast 통신 방식으로, 하나의 sender가 여러 receiver에게 동일한 데이터를 전송합니다. 즉, 모든 collector가 동일한 데이터 스트림을 공유합니다.
2. 데이터 버퍼링:
- Channel: 버퍼 크기를 지정할 수 있으며, 버퍼가 가득 차면 sender는 receiver가 데이터를 가져갈 때까지 대기합니다. (blocking)
- SharedFlow: replay, extraBufferCapacity 설정을 통해 버퍼링 동작을 제어할 수 있습니다. 기본적으로 최신 데이터만 유지하며, 이전 데이터는 버려집니다. (non-blocking)
3. 사용 용도:
- Channel: 주로 coroutine 간의 직접적인 통신에 사용됩니다. 예를 들어, producer coroutine에서 생성한 데이터를 consumer coroutine으로 전달하는 데 사용할 수 있습니다.
- SharedFlow: 여러 coroutine 또는 컴포넌트가 동일한 데이터를 관찰해야 하는 경우에 유용합니다. 예를 들어, 앱의 상태 변경을 여러 화면에서 관찰해야 하는 경우에 사용할 수 있습니다.
4. Cold vs. Hot:
- Channel: cold stream으로, collector가 collect를 호출할 때마다 새로운 스트림이 생성됩니다.
- SharedFlow: hot stream으로, collector의 존재 여부와 관계없이 데이터를 방출합니다
'Android > Hi Jack Mocker' 카테고리의 다른 글
Hi Jack Mocker | 개선기 (4) feat.out of order (0) | 2024.07.13 |
---|---|
Hi Jack Mocker | 개선기 (3) feat.channel data loss (0) | 2024.07.13 |
Hi Jack Mocker | 개선기 (2) feat.JSON 변환 이슈 (0) | 2024.07.12 |
Hi Jack Mocker | 개선기 (1) feat.UninitializedPropertyAccessException (0) | 2024.07.12 |
Hi Jack Mocker | Android 오픈 소스 배포기 (4) (0) | 2024.07.11 |