RIEN😚
이상한 나라의 개발자
RIEN😚
전체 방문자
오늘
어제
  • 분류 전체보기 (125)
    • Algorithm (68)
      • 알고리즘 (0)
      • Baekjoon (8)
      • 프로그래머스 (55)
      • HackerRank (5)
    • Android (30)
      • Project (1)
      • Error (2)
      • Studio (1)
      • Android (26)
    • Kotlin (6)
    • CS (4)
      • 네트워크 (2)
      • 데이터베이스 (2)
    • Front End (5)
      • React (1)
      • VUE (3)
      • Project (0)
      • 기타 (1)
    • 기록 (11)
      • 회고록 (6)
      • TIL (5)

블로그 메뉴

  • Github🔥
  • 포트폴리오🌹

공지사항

인기 글

티스토리

250x250
반응형
hELLO · Designed By 정상우.
RIEN😚

이상한 나라의 개발자

Android/Android

[Android] Retrofit의 ThreadPool : 왜 Retrofit은 Dispatcher 변경을 하지 않아도 되는것일까..

2023. 3. 10. 14:56
728x90
반응형

요근래 Retrofit에서 ThreadPool 관리를 어떻게 하는가에 대한 질문 받았었습니다.

하지만 그 당시에는 대답을 잘 하지 못해가지고😭

해당 부분에 대한 내용을 다시 공부도 할겸 이렇게 게시글을 정리하게 되었습니다.

 

먼저 결론부터 말하자면!

Retrofit 내에서 ThreadPool을 직접 만들어 관리하는 것으로 확인하였습니다. 👍🏻

결국 Dispatcher.IO도 IO만을 위한 ThreadPool을 사용하고 있는 방식이기 때문에 굳이 withContext를 이용해 IO Thread로 바꿔주지 않아도 되는듯해요.🔥

 

아직 안드로이드 경력 0년인 초보자인 관계로 혹시 틀린 점이 있다면

꼭! 댓글로 말씀 부탁드립니다! 🙏

 

지금까지 분석해보았던 라이브러리들 중에서 가장 어려운 분석 난이도가 아니었을까 해요.ㅎㅎ

 

1. API Service 구현 클래스 생성

Retrofit과 Hilt를 같이 사용하다보면 Api Service를 모듈로 관리할 때가 많았는데,

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Singleton
    @Provides
    fun provideImageApiService(
        retrofit: Retrofit
    ): ImageApiService = retrofit.create(ImageApiService::class.java)
}

저는 위와 같이 선언하였습니다.

메서드 이름 그대로 Retrofit의 create 메서드들 통해 ImageApiService 구현 클래스를 생성하는 코드입니다.

 

📌 (Retrofit).create

  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

으아. 한눈에 봐도 뭔가 긴 코드가 호출이 되는데, 이곳에서 중요한 부분은 바로 저 InvocationHandler입니다.

이 Handler는 우리가 Api Service에 선언해둔 메서드를 호출할 때 실행이 되는 부분이라고 생각하시면 됩니다. 🤗

 

👇🏻 요런 느낌?

 

이제 하나의 API 메서드를 사용하였을 때 어느 코드가 호출되었는지 알게 되었습니다.

그리고 이 코드를 타고타고 들어가다보면 실질적으로 Network 요청을 처리하는 enque 메서드와 만나실 수 있습니다. 😌

 

2. enque

이 enque 메서드에서는 client의 dispatcher를 사용하게 되는데,

🌹 바로 이 dispatcher가 Retrofit Thread 관리의 핵심적인 인스턴스입니다.
여기서 Client는 OkHttpClient입니다.

 

처음에는 이름이 Dispatcher이길래 kotlin껀줄 알았는데 OkHttp의 Dispatcher 클래스였다는..

3. Dispatcher

OkHttp3 package 에 있는 Dispatcher 코드를 보시면

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

 

요렇게 ExecutorService 프로퍼티를 만들어 사용하고 있는 것을 알 수 있습니다.

이 executorService는 실질적으로 각 네트워크 요청을 threadpool에서 동작시키는데 사용됩니다.

 

추가적으로 executorService는 OkHttpClient를 정의할 때 Custom하게 변경할 수도 있습니다. 🤗

@Module
@InstallIn(SingletonComponent::class)
object HttpClientModule {
    private const val TIMEOUT = 3L

    @Singleton
    @Provides
    fun provideHttpClient(): OkHttpClient {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor.Level.BODY
        } else {
            HttpLoggingInterceptor.Level.NONE
        }

        return OkHttpClient.Builder()
            .dispatcher(
            	// Custom ExecutorService로 설정
                Dispatcher(
                    ThreadPoolExecutor(0,10,60,
                        TimeUnit.SECONDS,
                        SynchronousQueue(),
                        threadFactory("이건 테스트 Dispatcher", false)
                    )
                )
            )
            .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
            .build()
    }
}

 

4. 정리

이전에는 코드리뷰를 통해 Retrofit 자체에서 Dispatcher.IO를 지원해준다고만 들어 withContext를 사용하지 않기만 하다가

이런 질문을 듣게 되어 직접 한번 분석해보는 경험을 가지게 되었습니다. 😭

 

사실 이 질문을 받게된 계기가 Hilt를 사용할 때 ApiService에는 @Singleton을 사용하고 Retrofit에는 @Singleton을 사용하지 않았던 부분에 질문을 받은 것이 시작이었습니다.ㅎㅎ 왜 그 때는 대답을 잘 못했는지ㅠㅠ

 

4.1 결론

  • Api Service의 메서드를 호출할 때마다 새로운 HttpConnection이 연결된다.(물론 3 Hand-Shake도)
  • Api Service의 메서드가 호출될 때마다 하나의 thread를 점유해서 작업을 수행하며, 이는 Api Service 메서드가 suspend로 정의되어있어도 동일한 듯 합니다. 즉, Api Service 메서드를 호출하는 Thread만 코루틴으로 동작해서 block 되지 않고, 실질적으로 네트워크 연결이 수행되는 쪽은 하나의 thread 자체를 사용한다고 생각합니다.
  • Api Service 메서드의 반환값이 Response여도 내부에서는 결국 Call을 사용하는 것이며, onResponse / onFailure는 Api Service를 호출한 thread에서 수행됩니다.

 


덧붙이자면 

결국은 Api Service 인스턴스 자체가 아닌 Api Service의 메서드를 호출할 때마다 특정 작업이 이루어지므로

Api Service를 생성하는 쪽도 Retrofit을 생성하는 쪽도 Singleton을 붙여주었습니다. 🪴

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Singleton
    @Provides
    fun provideImageApiService(
        retrofit: Retrofit
    ): ImageApiService = retrofit.create(ImageApiService::class.java)
}

@Module
@InstallIn(SingletonComponent::class)
object RetrofitModule {
    @Singleton
    @Provides
    fun provideRetrofit(
        okHttpClient: OkHttpClient,
        converter: Converter.Factory
    ): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(converter)
            .baseUrl("https://picsum.photos")
            .build()
    }
}

 

감사합니다. 🌹

반응형

'Android > Android' 카테고리의 다른 글

[Android] Room을 이용해 만든 Database와 dao는 어떻게 생겼을까?  (0) 2023.04.01
[Android/Compose] 언제 Recomposition이 발생하는 것일까?  (0) 2023.03.26
[Android/Compose] Compose에서 Paging3 적용기  (0) 2023.03.03
[공식문서 열어보기] AlarmManger로 정확한 시간에 알림 띄워주자  (0) 2023.02.13
[Hilt] Component와 Scope 무엇이 다른지 디버깅으로 다 찍어보자!  (0) 2023.02.11
    'Android/Android' 카테고리의 다른 글
    • [Android] Room을 이용해 만든 Database와 dao는 어떻게 생겼을까?
    • [Android/Compose] 언제 Recomposition이 발생하는 것일까?
    • [Android/Compose] Compose에서 Paging3 적용기
    • [공식문서 열어보기] AlarmManger로 정확한 시간에 알림 띄워주자
    RIEN😚
    RIEN😚
    안드로이드 / 코틀린 독학으로 취업하자!

    티스토리툴바