Retrofitソースの詳細
12454 ワード
Retrofit
の使用は古くから行われていたが、なぜRetrofit
を使用するかは深く研究されておらず、1つのインタフェースを定義するとともに、インタフェースの方法と方法のパラメータに注釈を加えることでHttp
の要求を完了することができ、パラメータと要求結果をどのようにパッケージ化するかも研究されていない.したがって、Retrofit
を使用することは常に一知半解の状態であり、その内部の原理が分からないため、Retrofit
のソースコードを見るのに少し時間がかかり、Retrofit
の要求フロー全体に対して一定の理解が得られた.Retrofitコアソース解読
1.
Retrofit
オブジェクトの作成Retrofit
オブジェクトを作成するときに使用するのはBuilder
モードで、Retrofit
オブジェクトを作成するときにRetrofit
のbaseURL
を設定して、自分のconverterFactory
を追加することができます例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GITHUB_API)
.addConverterFactory(GsonConverterFactory.create())
.build();
注意点:
baseUrl
が設定されている場合とRetrofit 1.x
のバージョンが異なるのは、url
が'/'
でソースコード:
// Retrofit.java#baseUrl method
public Builder baseUrl(HttpUrl baseUrl) {
....
// pathSegments() url List( '/' , split("/") )
List pathSegments = baseUrl.pathSegments();
// List (""), '/'
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
....
}
ResponseBody
タイプにしかカプセル化できないか、戻り値のソースコードがない:// Retrofit.java#Builder
Builder(Platform platform) {
this.platform = platform;
// BuiltInConverter
converterFactories.add(new BuiltInConverters());
}
// BuiltInConverters.java#responseBodyConverter method
public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
// ResponseBody
if (type == ResponseBody.class) {
return Utils.isAnnotationPresent(annotations, Streaming.class)
? StreamingResponseBodyConverter.INSTANCE
: BufferingResponseBodyConverter.INSTANCE;
}
// Void
if (type == Void.class) {
return VoidResponseBodyConverter.INSTANCE;
}
//
return null;
}
Retrofit
オブジェクトを作成するときにBuilder
モードを使用しますが、Builder
モードを使用するメリットは、作成したオブジェクトが可変オブジェクトであることです(属性はprivate
であり、setter
メソッドがないため)、これにより、オブジェクト内の属性が変更されたか、またはオブジェクト内の属性が誤って変更されたかを心配する必要がなくなり、他の要求と同じRetrofit
オブジェクトを共有できない要求がある場合、ほとんどの属性は同じで一部の属性が一致しないだけで、Retrofit
オブジェクトをテンプレートとして使用して、Retrofit
オブジェクトの新しいソースコードを作成することができる:
// Retrofit.java#Builder
Builder(Retrofit retrofit) {
// retrofit Retrofit
this.platform = Platform.get();
callFactory = retrofit.callFactory;
baseUrl = retrofit.baseUrl;
converterFactories.addAll(retrofit.converterFactories);
adapterFactories.addAll(retrofit.adapterFactories);
// Remove the default, platform-aware call adapter added by build().
adapterFactories.remove(adapterFactories.size() - 1);
callbackExecutor = retrofit.callbackExecutor;
validateEagerly = retrofit.validateEagerly;
}
Retrofit
は複数のプラットフォーム(jvm, android, ios)
をサポートしているので、Retrofit
は現在参照されているプラットフォームをどのように見分けているのでしょうか.実際にRetrofit
で使用する方法は、現在のclasspath
に対応するプラットフォーム特有のclass
が存在するかどうかを確認して、現在どのプラットフォームにあるかを判断することである.これとSpringの@ConditionalOnClass(ClassName.class)
は、注釈修飾beanが異曲同工の味を持つキーコードを初期化するかどうかを決定する.2.送信要求のインタフェースの作成
送信要求メソッドを含むインタフェースは、他のインタフェースを継承できないインタフェースでなければなりません.
例:
// Platform.java#findPlatform method
//
private static Platform findPlatform() {
try {
// "android.os.Build" android
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
// "java.util.Optional" java8
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
// "org.robovm.apple.foundation.NSObject" ios
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
//
return new Platform();
}
注意点:
public interface GitApi {
@GET("/users/{user}")
Call getFeed(@Path("user") String user);
}
Retrofit
はHttp
リクエストを送信することができるのでしょうか.Retrofit
はエージェントを使用しているため、インタフェースのオブジェクトを作成するときに返されるのはエージェントオブジェクトであり、このエージェントもRetrofit
のコアである.ソース:static void validateServiceInterface(Class service) {
// class
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
//
if (service.getInterfaces().length > 0) {
throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
}
}
Retrofit
が実際にjdk
のProxy
動的エージェント技術を使用していることが明らかになり、1つのインタフェースを定義し、注釈を加えてhttp
の要求を完了することができる.Proxy
を使用しているため、抽象クラスではなく1つのインタフェースとして定義しなければならない理由も明らかになった.jdk
のproxy
はインタフェースのエージェントオブジェクトしか生成できないため、ここでcglib
ライブラリを使用すると抽象クラス3.
Http
リクエストの送信http
リクエストを送信するには、ServiceMethod
クラスとOkHttpCall
クラスが欠かせません.彼らはHttp
リクエストを送信するコアクラスです.まず、Map
から現在のリクエストメソッドに関連付けられたServiceMethod
を取得し、見つかったら(説明前にこのメソッドが呼び出された)見つかったServiceMethod
を直接使用します.見つからなかったら、そして、新たなServiceMethod
が作成する、現在の要求方法に関連付けられたput
からMap
へと、最終的には、OkHttpCall.execute
を呼び出して要求を送信する.ソース:
// , GitApi
GitApi git = retrofit.create(GitApi.class);
// : create
public T create(final Class service) {
// service
Utils.validateServiceInterface(service);
// validateEagerly==true, serviceMethodCache ,
// loadServiceMethod , , ,
// ,
if (validateEagerly) {
eagerlyValidateMethods(service);
}
// ,
// , , , , retrofit
// service: , proxy: , method: , args:
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// Object
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// ServiceMethod
ServiceMethod
ServiceMethod
もBuild
モードを用いる、buildメソッドにおいて要求されたメソッド上の注釈、メソッドパラメータ、戻り値を解析するソース:
// ServiceMethod
ServiceMethod, ?> loadServiceMethod(Method method) {
//
ServiceMethod, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
// ( )
synchronized (serviceMethodCache) {
// , , key
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
Retrofit.java#create
のproxy
において呼び出し要求メソッドが最終的に返すのがreturn serviceMethod.callAdapter.adapt(okHttpCall);
であり、デフォルトではOkHttpCall
を返すので、最終的に呼び出すのはOkHttpCall.execute()
メソッドが要求を送信し、結果を解析するキーコード:
public ServiceMethod build() {
// callAdapter ( DefaultCallAdapterFactory), callAdapter .
// callAdapter CallAdapter okhttp (DefaultCallAdapterFactory OkHttpCall.execute ).
// Call, CallAdapter.Factory
callAdapter = createCallAdapter();
...
other code
...
// ( BuiltInConverters), Retrofit addConverterFactory converters
responseConverter = createResponseConverter();
//
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
...
other code
...
//
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
...
...
return new ServiceMethod<>(this);
}
まとめ
これで、
Retrofit
が要求を送信するプロセス全体が説明され、実際にはプロセス全体のキーはいくつかしかありません.例えば、Retrofit.java#create
メソッド、このメソッドの役割はProxyを使用してユーザー定義のインタフェースの実装を作成することであり、使用時にインタフェースを作成し、メソッドを作成し、関連する注釈を追加する3つのステップでhttpリクエストのキーServiceMethod.java#build
メソッド、このメソッドの役割はリクエストメソッド上の注釈、メソッドのパラメータ、メソッドの戻り値を解析するとともにcallAdapterオブジェクトを作成することであり、これはRetrofitが残した「ソケット」であり、CallAdapter.Factory
を実現すれば、カスタムメソッド戻り値タイプを処理することができ、メソッドはCallタイプOkHttpCall.java#execute
メソッド.このメソッドの役割は、最下位のokhttp
を呼び出して真のhttp
リクエストを送信し、戻り結果を解析することです.戻り結果を解析する方法は、Retrofit
のconverterFactories
を巡り、戻り結果を解析できるオブジェクトを見つけることです.このRetrofit
が残した別の「ソケット」です.Converter.Factory
を実装すれば、Call
タイプのまた、
Retrofit
は @Override public Response execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
...
call = rawCall;
if (call == null) {
try {
// Okhttp call
call = rawCall = createRawCall();
} catch (IOException | RuntimeException e) {
...
}
}
}
....
// call.execute: okhttp
// parseResponse: , retrofit converterFactory, converter
return parseResponse(call.execute());
}
この書き込みコンバータは、日常開発のニーズを満たすことができるはずだ.
以上から分かるように、
Retrofit
は私たちに帰還タイプをカスタマイズすることを許可することができて、帰還結果は同時に下層のOkHttp
の複雑さを遮断して、私たちに1つのインタフェースを定義するだけでHttp
の要求の送信を完成させて、これは使用者にとってとても友好的です.