OkHttp4.2.2 source code parsing
Retrofit2.6.2 source code parsing
The previous article briefly analyzed the OkHttp4.2.2 source code, and attached the corresponding flow chart. Using OkHttp directly wasn’t convenient enough, so Retrofit was born. As of the date of this writing, the latest version of Retrofit is 2.6.2, which itself uses OkHttp3.14.4 and coroutines1.3.2. Although Retrofit does not use the latest version of OkHttp, it does not affect the previously analyzed process.
I. Flow chart
Two, simple use
- Add the dependent
implementation("Com. Squareup. Retrofit2: retrofit: 2.6.2." ")
Copy the code
- Define the request interface
interface ApiService {
@GET("users/{user}/repos")
suspend fun listRepos(
@Path("user") user: String
): FoodResponse<List<Repo>>
}
Copy the code
- Build the Retrofit build Api instance
val baseUrl = "https://api.github.com/"
val okClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
......
.build()
Retrofit.Builder()
.baseUrl(baseUrl)
.client(okClient) // There is default, not required
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(ApiService::class.java)
Copy the code
- The initiating
//1. Callback mode
service.listRepos1("zx").enqueue(object : Callback<List<Repo>> {
override fun onFailure(call: Call<List<Repo>>, t: Throwable){}override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo> >){}})//2. Coroutine
val repos = service.listRepos("zx")
/ / 3. RxJava etc
})
Copy the code
Three, source code analysis
I’ve shown you how to initiate a web request through Retrofit in the four steps above, and then I’ll look at the source code using the process above.
Building Retrofit
In building Retrofit we focused on three methods: the Builder method, the baseUrl() method, and the Build () method
- Builder
public Builder(a) {
// Platform fetch, Platform we also see in OkHttp analysis
this(Platform.get());
}
Copy the code
- baseUrl
public Builder baseUrl(String baseUrl) {
Objects.requireNonNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
public Builder baseUrl(HttpUrl baseUrl) {
Objects.requireNonNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();
// Check if baseUrl ends with/and throw an exception if it does not
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
Copy the code
So instead of saying what baseUrl does, if we look at the comment line, baseUrl does not end with a slash, it throws an exception.
Note that the baseUrl type is HttpUrl, so it’s not the same as the String we used. Httpurl.get handles our Settings, so we don’t necessarily get an exception if our String baseUrl doesn’t end in /.
- build
public Retrofit build(a) {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
// If the callFactory is not set, OkHttpClient is used by default. Retrofit uses OkHttp by default
//OkHttpClient implements the okHttp3.call.factory interface
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
//callbackExecutor
// If no callback executor is set, the platform default callback executor is used
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
// Network request Adaptation Factory set = user set adaptation factory + platform default adaptation factory
// The user sets the adaptation factory
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
// The platform ADAPTS the factory by default, notice that the callback executor is passed in here
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// Make a defensive copy of the converters.
// Set of data transformation factories = built-in transformation factories + user-set transformation factories + platform default transformation factories
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.
// Built-in conversion factory
converterFactories.add(new BuiltInConverters());
// The user sets the transformation factory
converterFactories.addAll(this.converterFactories);
// Platform default conversion factory
converterFactories.addAll(platform.defaultConverterFactories());
/ / build Retrofit
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Copy the code
The Build method mainly handles parameters (null values and platform defaults) and builds Retrofit instances.
The word Platform appears several times. What does this class do?
class Platform {
private static final Platform PLATFORM = findPlatform();
static Platform get() {
returnPLATFORM; Private static Platform private static PlatformfindPlatform() {
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) {
}
returnnew Platform(); }... @IgnoreJRERequirement // Only classloaded and used on Java 8. static class Java8 extends Platform { ...... } static class Android extends Platform { @IgnoreJRERequirement // Guarded by API check. @Override boolean isDefaultMethod(Method method) {if (Build.VERSION.SDK_INT < 24) {
return false;
}
return method.isDefault();
}
@Override
public Executor defaultCallbackExecutor() {// Returns the default callback executorreturn new MainThreadExecutor();
}
@Override
List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
if(callbackExecutor == null) throw new AssertionError(); / / build the default factory, a note here to callback executives DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory (callbackExecutor); // Return the default adaptation factoryreturn Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
: singletonList(executorFactory);
}
@Override
int defaultCallAdapterFactoriesSize() {
returnBuild.VERSION.SDK_INT >= 24 ? 2:1; } @Override List<? extends Converter.Factory>defaultConverterFactories() {// Return the default conversion factoryreturn Build.VERSION.SDK_INT >= 24
? singletonList(OptionalConverterFactory.INSTANCE)
: Collections.<Converter.Factory>emptyList();
}
@Override
int defaultConverterFactoriesSize() {
returnBuild.VERSION.SDK_INT >= 24 ? 1:0; } 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
Three points:
- You can see from the findPlatform method that 2.6.2 supports Android and Java. (remember there was iOS in 2.0.1, and the latest GitHub code has removed Java8.)
- The platform default callback executor (Handler main thread), default adaptation factory, and default transformation factory are set
- Java8 and Android 24 is a cut-off point (specific can see CompletableFutureCallAdapterFactory and OptionalConverterFactory)
Creating an Api instance
The build process for Retrofit was analyzed above, followed by the section on creating an Api instance. Actually creating an Api instance seems simple, with just one line of code:
val service = retrofit.create(ApiService::class.java)
Copy the code
Is it really that simple? Read on:
public <T> T create(final Class<T> service) {
// Check whether the Service Interface meets the requirements
Utils.validateServiceInterface(service);
if (validateEagerly) {
// Whether the Service Interface method needs to be verified in advance,
// true: the method is added to the serviceMethodCache when create is called
// false: Do processing and caching when invocationHandler. invoke is invoked
// Both true and false end up calling loadServiceMethod
eagerlyValidateMethods(service);
}
// Use the dynamic proxy to get all the annotation configuration of the request interface and create an instance of the network request interface
return (T) Proxy.newProxyInstance(service.getClassLoader(), newClass<? >[]{service},new InvocationHandler() {
......
});
}
// Verify the Service Interface method ahead of time
private void eagerlyValidateMethods(Class
service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if(! platform.isDefaultMethod(method) && ! Modifier.isStatic (method.getModifiers())) {IsDefaultMethod Indicates whether the platform default method is fasle returned in 2.6.2
// isStatic whether to be modified by staticloadServiceMethod(method); }}}Copy the code
Sure enough, that looks easy. This step is not for further analysis of loadServiceMethod and its subsequent operations, but for the invoke invoke execution, as NORMALLY I have not set validatembit/s.
Making a Network request
public <T> T create(final Class<T> service) {...return (T) Proxy.newProxyInstance(service.getClassLoader(), newClass<? >[]{service},new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
// Execute the method in the instance of newProxyInstance
@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.
// Object Default method
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// Platform default method
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// Load the Service Interface method and invoke to create an OkHttpCall
returnloadServiceMethod(method).invoke(args ! =null? args : emptyArgs); }}); }ConcurrentHashMap supports multithreaded access and is thread-safe
private finalMap<Method, ServiceMethod<? >> serviceMethodCache =newConcurrentHashMap<>(); ServiceMethod<? > loadServiceMethod(Method method) {// Get the cacheServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
// Parse annotations to generate ServiceMethod
result = ServiceMethod.parseAnnotations(this, method);
// serviceMethodCache Adds to the cacheserviceMethodCache.put(method, result); }}return result;
}
Copy the code
Follow up ServiceMethod parseAnnotations
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// Parse the annotation parameters
BaseUrl relativeUrl, method annotation parameter type parameter annotation, etcRequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); .// HttpServiceMethod is a subclass of follow-on ServiceMethod
// Create a callAdapter and responseConverter in parseAnnotations
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
// Create an OkHttpCall to initiate a request from adapt
abstract @Nullable
T invoke(Object[] args);
}
Copy the code
To follow up HttpServiceMethod. ParseAnnotations look
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) {
// Is Kotlin Suspend method
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
Response
continuationWantsResponse = true;
} else {
// TODO figure out if type is nullable or not
// Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
// Find the entry for method
// Determine if return type is nullable or not
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
// Skip the callback executor annotation
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
// Create a CallAdapter from Retrofit, method, adapterType, Annotations
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.");
}
// Create Converter based on retrofit, method, adapterType
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if(! isKotlinSuspendFunction) {// Non-Kotlin Suspend method
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
// The Kotlin Suspend method is included in Response
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
// The Kotlin Suspend method is not included in Response
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) newSuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code
HttpServiceMethod parseAnnotations finally returned to the HttpServiceMethod, back to Retrofit. Execute invokede call the create method. HttpServiceMethod implements invoke as follows:
@Override final @Nullable ReturnT invoke(Object[] args) {
// Create a Call object
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
// Adapt is also an abstract method implemented in CallFranchise, SuspendForResponse, SuspendForBody
return adapt(call, args);
}
Copy the code
The createCallAdapter and createResponseConverter methods are also important. Here is how to create a CallAdapter. CreateCallAdapter -> retrofit.callAdapter -> nextCallAdapter:
publicCallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
......
int start = callAdapterFactories.indexOf(skipPast) + 1;
// Walk through the collection of callAdapterFactories built by Retrofit.build to find suitable adaptation factories
for (inti = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations,this);
if(adapter ! =null) {
returnadapter; }}...throw new IllegalArgumentException(builder.toString());
}
Copy the code
Finding the corresponding callAdapter is as simple as depending on the type (for example, if Call is returned by default and coroutine is decorated by suspend, see the CallAdapterFactory get method).
SuspendForBody implements HttpServiceMethod:
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT.Object> {
private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
private final booleanisNullable; .@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
/** As inside await and awaitNullable, calls to OkHttp call.enqueue () can sometimes invoke the provided callback before the exception returns, and Call stack frames can return. Coroutines intercept subsequent calls to continuations and throw exceptions synchronously. If the checked exception is not declared in the interface method, the Java proxy cannot raise the checked exception. In order to avoid abnormal will check the packing in UndeclaredThrowableException to intercept it and offered to help the program, the helper will enforce suspend operation, so that you can pass it to continue to operate to bypass this limitation. * * /
try {
// Call the specific upvote method
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
} catch (Exception e) {
returnKotlinExtensions.yieldAndThrow(e, continuation); }}}Copy the code
The next implementation of the extension method await is as follows:
suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine {continuation - > continuation. InvokeOnCancellation {the cancel ()} / / actual is call call the enqueue enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) {if (response.isSuccessful) {
val body = response.body()
if (body == null) {
val invocation = call.request().tag(Invocation::class.java)!!
val method = invocation.method()
val e = KotlinNullPointerException("Response from " +
method.declaringClass.name +
'. ' +
method.name +
" was null but response body type was declared as non-null")
continuation.resumeWithException(e)
} elseResume (body)}}else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
Copy the code
When performing the KotlinExtensions. Await/awaitNullable, executes the corresponding extension function. It is not hard to see that the Call. Enqueue is used to initiate the request, and the corresponding result is resumed by the corresponding resume function. This Call object is the OkHttpCall from invoke above, and it is created as follows. It is not hard to guess that the final originating request and data transformation are in it, so the enqueue method called in the above await extension is also implemented in it.
// Avoid rolling back to see OkHttpCall, Call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);Copy the code
Take a look at the enqueue method of OkHttpCall
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null"); .// After a series of judgment operations, the request is finally initiated
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
/ / parse the Response
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}}... }); }Parse the response data
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); . ExceptionCatchingResponseBody catchingBody =new ExceptionCatchingResponseBody(rawBody);
try {
//responseConverter finally parses data through Converter
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
......
}
Copy the code
Four,
Retrofit simplifies the configuration of network requests through interface annotations and uses dynamic proxy loading to parse annotation execution. The OkHttpCall is then used to initiate the request, and the response is converted to The CallAdapter and the final data is handed to the developer.
Using Retrofit and the source code, Retrofit does encapsulate OkHttp and makes extensive use of annotations and design patterns such as builder pattern, dynamic proxy pattern, factory pattern, adapter pattern, and so on.
Although this is based on 2.6.2 parsing, there are some changes in the latest commit, as mentioned earlier.
Five, the complement
thread
I didn’t see how to switch threads because I was using coroutines above, so now I’m going to see 1 see, I’m going to add comments. As follows:
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
private final @Nullable Executor callbackExecutor;
// The argument passed in is the callback executor, remember where it was passed in?
// Retrofit builds based on the relevant platform
DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
// This method is used for createCallAdapter
@Override public @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) {if(getRawType(returnType) ! = Call.class) {return null;
}
if(! (returnTypeinstanceof 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);
// Get executor (coroutines not required)
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return newCallAdapter<Object, Call<? > > () {@Override public Type responseType(a) {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
// Returns call, which is non-coroutine with the ExecutorCallbackCall package layer
return executor == null
? call
: newExecutorCallbackCall<>(executor, call); }}; }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");
// The delegate here is OkHttpCall
delegate.enqueue(new Callback<T>() {
@Override public void onResponse(Call<T> call, final Response<T> response) {
// The callback executor completes the thread switch
callbackExecutor.execute(new Runnable() {
@Override public void run(a) {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this.new IOException("Canceled"));
} else {
// The result is called back via callback
callback.onResponse(ExecutorCallbackCall.this, response); }}}); }@Override public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run(a) {
callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); }@Override public boolean isExecuted(a) {
return delegate.isExecuted();
}
@Override public Response<T> execute(a) throws IOException {
return delegate.execute();
}
@Override public void cancel(a) {
delegate.cancel();
}
@Override public boolean isCanceled(a) {
return delegate.isCanceled();
}
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
@Override public Call<T> clone(a) {
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
}
@Override public Request request(a) {
returndelegate.request(); }}}Copy the code