본문 바로가기
개발/안드로이드

[안드로이드] Retrofit은 어떻게 동작할까?

by 창이2 2023. 2. 12.

안녕하세요.

이번 포스팅은 안드로이드의 Retrofit Library의 동작 방식에 대한 분석을 이야기해 보려고 합니다.

Retrofit을 사용할때 가끔씩

어떻게 인터페이스를 정의한것만으로 네트워크 통신이 가능하지?

라는 생각을 했습니다.

 

그래서 라이브러리를 분석하는 중

InvocationHandler와 newProxyInstance 를 보고 Dynamic Proxy 패턴에 대해 알게 되고 

이것이 Retrofit을 동작하는데 중요한 역할을 한다는 것을 알게 되었습니다.

일단 Retrofit의 동작원리를 이해하기 전 Dynamic Proxy 패턴에 대해 이해가 필요합니다.

잘 이해가 안가시면 아래 글을 참고해주세여!

 

https://dog-footprint.tistory.com/94

 

[자바 디자인패턴] 5. Dynamic Proxy 패턴

안녕하십니까아. 이번 포스팅은 Dynamic Proxy 패턴에 대해서 써보려고 합니다. Dynamic Proxy는 Proxy를 동적으로 생성하여 사용하는 패턴입니다. Dynamic Proxy를 사용하는 경우는 대게 제네릭한 행동을 할

dog-footprint.tistory.com

일단 Retrofit이 어떻게 생성되는지 부터 보겠습니다.

제너릭 타입 T 를 받고 있는데 이 T는 우리가 설정한 API Interface 입니다. 

Proxy.newProxyInstance

를 사용하여 Dynamic Proxy 패턴을 구현한 것을 알 수 있습니다.

  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);
              }
            });
  }

 

첫번째로 create 대상이 인터페이스인지 아닌지를 체크합니다.

validateServiceInterface(service);

만약 인터페이스라면 프록시를 생성하고 아니라면 에러를 일으킵니다. 

 

 

Platform

이라는 객체가 있는데 내부 구현을 보니 Platform에 따라 메소드 호출을 다르게 하는 것을 알 수 있습니다.

Platform 을 구할때 아래와 같이 달빅머신이면 Android를 아니면 Platform 을 구하도록 되어있습니다.  

내부에 hasJava8Types를 갖고 있는데 이 여부에 따라 ConverFactory를 다르게 가져가는것으로 보입니다.

 

  private static Platform findPlatform() {
    return "Dalvik".equals(System.getProperty("java.vm.name"))
        ? new Android() //
        : new Platform(true);
  }

 

api를 호출하면 serviceMethodCache 에 해당 메소드정보를 가져오는데 캐싱된게 없다면 메소드를 파싱하여 캐시정보를 저장합니다.

 

Retrofit.java

ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

 

이때 parseAnnotations 함수에서

private void parseMethodAnnotation(Annotation annotation)
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody)
private Headers parseHeaders(String[] headers)

 

등등 수행하여 해당 메소드 정보를 캐싱하게 됩니다.

 

이후

HttpServiceMethod의 invoke를 통해 호출을 하여 결과를 받습니다.

ServiceMethod.java

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

 

HttpServiceMethod의 invoke 메소드에 실질적으로 okhttpCall이 들어가는 것을 볼 수 있습니다.

HttpServiceMethod.java

@Override
final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}

 

부족한 부분에 대해 지적해주시면 감사하겠습니다!

댓글