In order to make it clear to the reader, this article analyzes Retrofit from the perspective of a person who has not read the source code. The amount of code is very large. If you feel uncomfortable while reading, please take a break.
Retrofit is simple to use
If you’re familiar with Retrofit, it’s pretty easy to use. Here’s how to use Retrofit:
val retrofit = Retrofit.Builder()
.baseUrl("https://www.github.com")
.build()
val userService = retrofit.create(UserService::class.java)
val call = userService.getUserInfo("1")
call.enqueue(object : Callback<ResponseBody> {
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
}
})
Copy the code
Yes, but what do these pieces of code actually do? Next I will analyze it from the source point of view.
Retrofit source code analysis
Here’s retrofit’s approach to create:
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() {
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);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
Copy the code
The first line checks the class passed in as follows:
static <T> void validateServiceInterface(Class<T> service) {
if(! service.isInterface()) { throw new IllegalArgumentException("API declarations must be interfaces.");
}
// Prevent API interfaces from extending other interfaces. This not only avoids a bug in
// Android (http://b.android.com/58753) but it forces composition of API declarations which is
// the recommended pattern.
if (service.getInterfaces().length > 0) {
throw new IllegalArgumentException("API interfaces must not extend other interfaces."); }}Copy the code
If the class is an interface, and if the interface inherits from another interface, we’ll see why this is done later. while
if (validateEagerly) {
eagerlyValidateMethods(service);
}
Copy the code
Mainly for the stability of the program, we won’t say much here, mainly look at the following. Careful readers have already noticed
Proxy.newProxyInstance
Copy the code
From this we can see that the create method mainly uses dynamic proxy, so it is easy to go back to the verification of the first code. What’s going on in the dynamic proxy?
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
Copy the code
This code group depends on whether the method is in Object, if it is, we don’t deal with it.
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
Copy the code
This code is mainly Platform adapted, we can take a brief look at the Platform,
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if(Build.VERSION.SDK_INT ! = 0) {return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
Copy the code
The key is the following code
returnloadServiceMethod(method).invoke(args ! = null ? args : emptyArgs);Copy the code
LoadServiceMethod (method) loadServiceMethod(method
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;
}
Copy the code
It returns a ServiceMethod from the serviceMethodCache. If it does, it returns it. If it does not, it creates a cache and adds it to the cache. ServiceMethod is an abstract class with two main methods,
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);
}
abstract @Nullable T invoke(Object[] args);
Copy the code
See ServiceMethod parseAnnotations method will find eventually return is HttpServiceMethod parseAnnotations, So if we open up HttpServiceMethod we’ll see that it’s an implementation class for the ServiceMethod method, so let’s see what parseAnnotations do, it’s a little bit longer, I’ll hide the handling of the kotlin coroutine related aspects (this code is only called when coroutines are used)
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
......
} else {
adapterType = method.getGenericReturnType();
}
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
throw methodError(method, "'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
throw methodError(method, "HEAD method must use Void as response type.");
}
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if(! isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return(HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code
If we look at the code, we’ll see that a callAdapter and a responseConverter will be created in the middle, and the responseConverter will be called
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
Copy the code
Let’s start by looking at the callAdapter and tracing the code to find the ones that end up in Retrofit
public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, TypereturnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null");
int start = callAdapterFactories.indexOf(skipPast) + 1;
for(int i = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if(adapter ! = null) {returnadapter; }}... Omit a number of... }Copy the code
And then finally from the callAdapterFactories, so what callAdapterFactories are and how do you put them in, and then eventually trace them into Retrofit’s Build method:
/**
* Create the {@link Retrofit} instance using the configured values.
* <p>
* Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link
* OkHttpClient} will be created and used.
*/
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Copy the code
It’s not hard to see from this that if you didn’t add one when you created Retrofit you would eventually add a default
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
Copy the code
The callbackExecutor, created by Platform to run in the Android environment, will eventually return a MainThreadExecutor. Here’s the code
@Override public Executor defaultCallbackExecutor() {
returnnew MainThreadExecutor(); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); }}Copy the code
MainThreadExecutor has a handler that passes looper.getMainLooper (), which is used to communicate with the android main thread. Back to the front DefaultCallAdapterFactory, found a get method, our callAdapter is created through the get method in the end
@Override public @Nullable CallAdapter<? ,? > get( TypereturnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) ! = Call.class) {return null;
}
if(! (returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
returnnew CallAdapter<Object, Call<? >>() { @Override public TyperesponseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
returnexecutor == null ? call : new ExecutorCallbackCall<>(executor, call); }}; }Copy the code
ResponseConverte is also the last call to Retrofit’s Build(), so we’re going to look at the final call to HttpServiceMethod’s parseAnnotations.
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
Copy the code
Return a CallStuck object, which is derived from HttpServiceMethod. When you go to Retrofit, the create method is executed in the first half of the call, feel tired 😫, have a cup of tea.
loadServiceMethod(method).invoke(args ! = null ? args : emptyArgs)Copy the code
Invoke (HttpServiceMethod) : invoke (HttpServiceMethod) : Invoke (HttpServiceMethod
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
Copy the code
We found that we first created an OkHttpCall and then called the Adapt method, which is an abstract class in HttpServiceMethod
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
Copy the code
This seems to be a dead end, but don’t be too concerned if we know from our code that a CallAdapted method is an implementation class of HttpServiceMethod, and ultimately adapt is the adapt method of CallAdapted
@Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
Copy the code
Finally, the callAdapter.adapt(call) is called, and the callAdapter is created in the loadServiceMethod above, and bingo is all right.
@Override public @Nullable CallAdapter<? ,? > get( TypereturnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) ! = Call.class) {return null;
}
if(! (returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
returnnew CallAdapter<Object, Call<? >>() { @Override public TyperesponseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
returnexecutor == null ? call : new ExecutorCallbackCall<>(executor, call); }};Copy the code
What is an ExecutorCallbackCall that the discovery of code creation from the CallAdapter above returns? Let’s move on to the code
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation. callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled")); } else { callback.onResponse(ExecutorCallbackCall.this, response); }}}); } @Override public void onFailure(Call
call, final Throwable t) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); } @Override public boolean isExecuted() { return delegate.isExecuted(); } @Override public Response
execute() throws IOException { return delegate.execute(); } @Override public void cancel() { delegate.cancel(); } @Override public boolean isCanceled() { return delegate.isCanceled(); } @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone. @Override public Call
clone() { return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone()); } @Override public Request request() { return delegate.request(); }}
Copy the code
So there are a couple of methods that we’re familiar with here, enqueue, execute, and I’m sure you all know what’s going on here, when we’re using it
val call = userService.getUserInfo("1")
call.enqueue(object : Callback<ResponseBody> {
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
}
})
Copy the code
The enqueue method that’s called is the enqueue method here, and the callbackExecutor is Retrofit Build the incoming MainThreadExecutor, the callback is front HttpServiceMethod OkhttpCallback in the invoke method, ExecutorCallbackCall the enqueue and execute Method will eventually call the related method in delegate, which is an OkhttpCall object, so let’s look at the Enqueue in OkhttpCall
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; . Omit a number of...if(failure ! = null) { callback.onFailure(this, failure);return;
}
if (canceled) {
call.cancel();
}
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}
});
}
Copy the code
It’s not hard to see from this that it ends up calling the network connection method in OKhttp, which ends up in the following method
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along. rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); int code = rawResponse.code(); if (code < 200 || code >= 300) { try { // Buffer the entire body to avoid future I/O. ResponseBody bufferedBody = Utils.buffer(rawBody); return Response.error(bufferedBody, rawResponse); } finally { rawBody.close(); } } if (code == 204 || code == 205) { rawBody.close(); return Response.success(null, rawResponse); } ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); try { T body = responseConverter.convert(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { // If the underlying source threw an exception, propagate that rather than indicating it was // a runtime exception. catchingBody.throwIfCaught(); throw e; }}Copy the code
Through callback callback, we got the final result, and after half a day of tracking, we believed that people were a little confused, but finally got some understanding of how Retrofit worked.
PS: Like the students can scan code to pay attention to my public number yo