안녕하세요.
이번 포스팅은 안드로이드의 Retrofit Library의 동작 방식에 대한 분석을 이야기해 보려고 합니다.
Retrofit을 사용할때 가끔씩
어떻게 인터페이스를 정의한것만으로 네트워크 통신이 가능하지?
라는 생각을 했습니다.
그래서 라이브러리를 분석하는 중
InvocationHandler와 newProxyInstance 를 보고 Dynamic Proxy 패턴에 대해 알게 되고
이것이 Retrofit을 동작하는데 중요한 역할을 한다는 것을 알게 되었습니다.
일단 Retrofit의 동작원리를 이해하기 전 Dynamic Proxy 패턴에 대해 이해가 필요합니다.
잘 이해가 안가시면 아래 글을 참고해주세여!
https://dog-footprint.tistory.com/94
일단 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);
}
부족한 부분에 대해 지적해주시면 감사하겠습니다!
'개발 > 안드로이드' 카테고리의 다른 글
안드로이드 Annotation을 이용한 코드 생성(2) (0) | 2021.08.08 |
---|---|
안드로이드 Annotation을 이용한 코드 생성(1) (0) | 2021.08.02 |
안드로이드 include, merge, viewstub (0) | 2021.07.08 |
안드로이드 뷰가 그려지는 과정 (0) | 2021.06.09 |
안드로이드 LiveData setValue 와 postValue (0) | 2021.05.13 |
댓글