This source code parsing is based on 2.9.0, if there is any description error, please comment.
1. The role of Retrofit
Retrofit is based on OKHTTP, simplifies the operation of the OKHTTP request interface, and is compatible with Rxjava and Kotlin’s coroutines, but it is not yet compatible with Kotlin’s flows, and if it is, it can be packaged itself.
Take a look at the early days of using okHTTP requests directly Construct request + parse response + execute using okHTTP thread pool (of course okHTTP also has synchronous calls), a bunch of operations is very cumbersome, if loading show/hide, thread switch code is more complicated, retrofit+ RxJava classic combination to adapt to the trend.
The return value of retrofit Suspend is not required for Retrofit, because Java does not suspend at all. Java does not suspend when compiled. Suspend seems to Kotlin to be nothing more than a suspend function flag, with a surfeit of interface continuations for callbacks after being coded into Java bytecode. Let’s start with retrofit
// Create our Retrofit client
public class HttpManager {
private Retrofit mRetrofit;
privateMap<Class<? >, Object> mMap =new HashMap<>();
private static class SingletonInstance {
private static final HttpManager INSTANCE = new HttpManager();
}
public static HttpManager getInstance(a) {
return SingletonInstance.INSTANCE;
}
private HttpManager(a) {
mRetrofit = newRetrofit.builder ().client(custom okhttpClient)// If not, Retrofit will be created by default
.baseUrl("https://xxxx.xxxx.cn")
.addConverterFactory(ScalarsConverterFactory.create())// Convert to String
.addConverterFactory(GsonConverterFactory.create())// Convert to a Gson object
// Interface return value match
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
public <T> T create(Class<T> cls) {
if(! mMap.containsKey(cls)) { T t = mRetrofit.create(cls); mMap.put(cls, t); }return(T) mMap.get(cls); }}Copy the code
Q: That Call can be queued directly, that observeable can be subscribed, and that coroutine can be suspended and recovered.
For simple Observables, we create an Observable using the Create method, and then need to manage the data emission by ourselves. The Retrofit observable probably needs to handle the data emission by itself. Is that right? Explain later.
Q: Methods have annotations, request parameters have annotations, backarguments and generics. How do you deal with this? There are annotations on the method, there are annotations on the Request parameters, so if you get the method and parse the annotations, it’s not hard, so once you get the annotations, you build the Request, if it’s a POST, you build the RequestBody, you specify MediaType, Whether the return value is Call, Observable, etc., determines whether to go with the default, RXJava, or coroutine. Generics on the return value are also critical.
Request post interface from Retrofit+Observable to get familiar with the process
-
2.1. Post request writing
There is no for coroutines specializes in what CallAdapterFactory oh, because the default DefaultCallAdapterFactory coroutines. This default was added when the Retrofit object was created.
-
2.2 RetroFIT creation –>Retrofit build method
public Retrofit build(a) {... okhttp3.Call.Factory callFactory =this.callFactory;
if (callFactory == null) { // Create OkhttpClient for you by default
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) { // Call back the thread pool. Android defaults to the main thread switch
callbackExecutor = new MainThreadExecutor();
}
/ / add the default new DefaultCallAdapterFactory (callbackExecutor)
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(new DefaultCallAdapterFactory(callbackExecutor)
List<Converter.Factory> converterFactories = new ArrayList<>( 1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add some default converters
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(new OptionalConverterFactory());
return newRetrofit(....) ; }// The main thread pool of android callbacks
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) { handler.post(r); }}Copy the code
If you haven’t looked at retrofit’s build method and found that we only added two converters and one RxjavaCallAdapter when you debug, there are some more converters that you don’t know because Retrofit secretly added some default converters when it was created. That’s so sweet. Remember: callFactory is OkHttpClient.
-
2.3. Classic Retrofit dynamic proxy method —->create
The create methodThe return value of “is our custom Api interface object, so we can call Api methods directly – nonsense.
The InvocationHandlerInvoke methodThe return value of the Api interface class method can be Call, Observable, or Object. Object can be used as the return value.
Let’s see loadServiceMethod
private final Map<Method, ServiceMethod<? >> serviceMethodCache =new ConcurrentHashMap<>();
// Use a Map caching Method that supports concurrent securityServiceMethod<? >loadServiceMethod(Method method){ ServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) { // For the first request, annotations will be parsed first
result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); }}return result;
}
Copy the code
The return value is ServiceMethod, which has several subclasses.Both Rxjava and Call requests are returned CallCastors.
The coroutine returns SuspendForBody or SuspendForResponse.
Let’s see parseAnnotations
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// Create RequestFactory, then HttpServiceMethodRequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); . The return value is Void and the return value type that cannot be parsed is.....return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
Copy the code
-
2.4 RequestFactory creates —-> parse method annotations and method parameters
A critical RequestFactory. ParseAnnotations (…). Return RequestFactory, which contains too much information
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
// Method parameters processor arrayParameterHandler<? > []parameterHandlers
RequestFactory build(a) {
for (Annotation annotation : methodAnnotations) {
// Parse annotations on methods
parseMethodAnnotation(annotation);
}
//hasBody isFormEncoded relativeUrl isFormEncoded isMultipart gotPart
// The post must have a body, not a form submission, not a very important detail.int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = newParameterHandler<? >[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
// Parse the parametersparseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); }...return new RequestFactory(this);
}
Copy the code
Take a look at the annotation on the parse method parseMethodAnnotation(annotation). For example @POST(“app/courses/behaviour”), the annotation on the parse method gets the request and the short path
private void parseMethodAnnotation(Annotation annotation) {
DELETE GET HEAD PATCH PUT OPTIONS HTTP POST RetroFIT2.http. Headers
//Multipart FormUrlEncoded)
// Only POST will be kept here
if (annotation instanceof DELETE) {
....
} else if (annotation instanceof POST) {
@post ("app/courses/behaviour")
this.httpMethod ="POST";
this.hasBody = true;
String value=((POST) annotation).value();
if (value.isEmpty()) {
return; }...// Save the short path
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value); }... }Copy the code
Let’s look at parseParameter
Let’s start with our Body class
Fun getCourse(@body Info: CourseInfo): Observable with the @body annotation, we designed the ParameterHandler classSelect xxxRequestBodyConverter to build RequestBody.
private @NullableParameterHandler<? > parseParameter(....) { ParameterHandler<? > result =null;
if(annotations ! =null) {
for(Annotation annotation : annotations) { ParameterHandler<? > annotationAction =// Annotation of parsed method parametersparseParameterAnnotation(p, parameterType, annotations, annotation); . result = annotationAction; }}if (result == null) {
if (allowContinuation) {
try { // To determine whether to go coroutine, use Continuation. Class
// This judgment is a bit rough
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null; }}catch (NoClassDefFoundError ignored) {
}
}
.....
}
return result;
}
Copy the code
👆 explains how Retrofit works by determining if there is a Continuation type in a parameter, and if so, by using a coroutine. I added a Continuation to the argument below, but I wanted it to go to Rxjava, but unfortunately it decided to go to coroutines and crashed. Explain later.
So the key thing is, this parseParameterAnnotation method has 400+ lines, so a method has 400+ lines. But let’s just look at what we need so let’s say the annotations are Field and Body.
@Nullable private ParameterHandler<? >parseParameterAnnotation(
int p, Type type, Annotation[] annotations, Annotation annotation) {
/ / the annotation instanceof Url/Path/Query/QueryName/QueryMap/headers/HeaderMap too much, such as delete
// Use the Field and Body classes to explain
if (annotation instanceof Url) {
.....
} else if (annotation instanceof Field) {
.....
// omit some logicConverter<? .String> converter = retrofit.stringConverter(type, annotations);
return new ParameterHandler.Field<>(name, converter, encoded);
} else if (annotation instanceof Body) {
// Body is definitely not a form type. Converter<? , RequestBody> converter; converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations);// The requestBody converter exists in the request parameter handler
return new ParameterHandler.Body<>(method, p, converter);
}
return null; // Not a Retrofit annotation.
}
Copy the code
The Field comments: Look at the retrofit.stringConverter(Type, Annotations) method Its type is Int, no suitable RequestBody converter can be found, and all it ends up doing is forcing toString. Are all numeric types like Int float short made into strings by Retrofit? I see it has an addFormField method, value is a uniform String.
void addFormField(String name, String value, boolean encoded)
Copy the code
Body annotations Let’s start with three parser factories: each containing a parser for RequestBody building and a parser that converts ResponseBody to a generic type on the return value
- BuiltInConverters: The request parameter is Okhttp3’s RequestBody generic type + the response type is Okhttp3’s ResponseBody, Void or Kotlin’s Unit type, etc
- ScalarsConverterFactory: The MediaType of the request body is (text/plain; Charset =UTF-8) and the request body type is the wrapper type of the base data type or String + the response body is the wrapper type of the base data type /String.
- GsonConverterFactory: The MediaType of the request body is (” Application /json; Charset =UTF-8″) and can be serialized/deserialized by Gson, or the response body must be converted to a generic type on the return value.
The requestBodyConverter methods of the three converter factories try one by one and return converter as soon as they work properly.
public <T> Converter<T, RequestBody> requestBodyConverter( Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
for (int i = 0, count = converterFactories.size(); i < count; i++) { Converter.Factory factory = converterFactories.get(i); Converter<? , RequestBody> converter = factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations,this);
if(converter ! =null) {
return(Converter<T, RequestBody>) converter; }}throw newIllegalArgumentException(....) ; }/ / GsonConverterFactory requestBodyConverter
@Override
publicConverter<? , RequestBody> requestBodyConverter(....) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));return new GsonRequestBodyConverter<>(gson, adapter);
}
Copy the code
The Body here is a data class, which is definitely something that GsonConverterFactory can handle, so it saves the GsonRequestBodyConverter in the RequestFactory. Pay attention to the request and the response of the parser can be different, depending on the specific circumstances, the interface parameter is the data classes, the return value is an Integer type, the response of the parser is ScalarResponseBodyConverters IntegerResponseBodyConverter.
-
2.5, HttpServiceMethod create – > HttpServiceMethod. ParseAnnotations
An HttpServiceMethod object requires a requestFactory, callFactory, responseConverter, and callAdapter. The front was just created RequestFactory, callFactory. Remember our interface method first, so that we can look at the following code.
@POST("app/courses/behaviour")
fun getCourse(@Body info: CourseInfo): Observable<Int>
Copy the code
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(...). {
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
// The coroutine gets the return value type. }else {
Observable
, the type is integer.class
adapterType = method.getGenericReturnType();
}
// Create CallAdapter to distinguish between default and RxjavaCallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); .// Determine the response type
// The Response type cannot be okhttp3's Response
// If the Response type is Retrofit's Response, it must be generic like Response
.
// The responseType here is of type Integer.// Create a converter for the response body
Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if(! isKotlinSuspendFunction) {// is a CallAdapter, a subclass of HttpServiceMethod
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
.......
// Coroutine correlation}}/ / get CallAdapter think it is DefaultCallAdapterFactory RxJava2CallAdapterFactory
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
for (int i = 0, count = callAdapterFactories.size(); i < count; i++) {
/ / the return value is observables strategized Signle Flowable, go create RxJava2CallAdapter RxJava2CallAdapterFactory
/ / the return value is the Call went to create CallAdapter DefaultCallAdapterFactoryCallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations,this);
if(adapter ! =null) {
returnadapter; }}}Copy the code
callFactory: is OkHttpClient, which can be customized or created directly by default using Retrofit.
Judgment of callAdapter:
The return values of interface methods are Observable, Maybe, Signle, Flowable, etc. (RxJava2CallAdapter).
The return value on the interface method is Call type, which is a CallAdapter
So the getCourse method is Observable so it’s definitely an RxJava2CallAdapter.
ResponseConverter judgment:
The return value is Observable, and the generic type is Kotlin’s Int, which is Java’s Integer. What kind of converter can handle, ScalarsResponseBodyConverters of course.
The parser for the response body is ready before the request is sent. Awesome.
The parsers for the request body and the response body can be used arbitrarily, depending on the user’s needs.
-
The BodyObservable () function calls the Invoke method
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory,responseConverter);
return callAdapter.adapt(call);
}
Copy the code
Create a Retrofit Call and then Call the ADAPT method of the RxJava2CallAdapter
@Override
public Object adapt(Call<R> call) {
/ / we had been added to the retrofit addCallAdapterFactory (RxJava2CallAdapterFactory. The create () is Async
Observable<Response<R>> responseObservable =
isAsync ? new CallEnqueueObservable<>(call) : newCallExecuteObservable<>(call); Observable<? > observable;if (isResult) {
......
} else if (isBody) {
// Most requests are for Body, so it must be this one
observable = new BodyObservable<>(responseObservable);
} else{... }// Match Flowable Single Maybe Completable logic.return RxJavaPlugins.onAssembly(observable);
}
Copy the code
BodyObservable (CallExecuteObservable): Decorator design mode, two cooperate to complete return. A CallExecuteObservable by name is a synchronous call to a call to get a Response. A BodyObservable is an observable that emits T. The single responsibility principle uses 6. For Rxjava2CallAdapterFactory type, invoke () method returns the observables is BodyObservable against DefaultCallAdapterFactory type, The ExecutorCallbackCall returned by the Invoke method (Retrofit’s Call request method) or Retrofit’s Call (Retrofit’s coroutine request method)
-
2.7 Interface request –>BodyObservable data launching and response body processing
1. When we use Retrofit +Rxjava to request the interface, we will write a subscribeOn(Schedulers.io()), which means that we need to process the network request in the child thread, so we need to use okHTTP synchronous method to request the interface in RetroFIT. 2. After the BodyObservable (CallExecuteObservable) is subscribed, it calls its subscribeActual method, initiates the call synchronization request (call.execute()), waits for the response, and parses the response body. Distribute the observer callback methods and cancel the request in case of disponsed.
final class CallExecuteObservable<T> extends Observable<Response<T>> {
private final Call<T> originalCall;
CallExecuteObservable(Call<T> originalCall) {
this.originalCall = originalCall;
}
@Override
protected void subscribeActual(Observer<? super Response<T>> observer) {
// Clone is the new OkhttpCall
Call<T> call = originalCall.clone();
CallDisposable disposable = new CallDisposable(call);
// Handle the onSubscribe callback
observer.onSubscribe(disposable);
if (disposable.isDisposed()) {
return;
}
boolean terminated = false;
try {
// Call is called synchronously
Response<T> response = call.execute();
if(! disposable.isDisposed()) {// Handle the observer onNext method
observer.onNext(response);
}
if(! disposable.isDisposed()) { terminated =true;
// Handle the observer onComplete methodobserver.onComplete(); }}catch (Throwable t) {
Exceptions.throwIfFatal(t);
if (terminated) {
RxJavaPlugins.onError(t);
} else if(! disposable.isDisposed()) {try {
// Handle the observer onError method
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(newCompositeException(t, inner)); }}}}private static final class CallDisposable implements Disposable {
private finalCall<? > call;private volatile booleandisposed; CallDisposable(Call<? > call) {this.call = call;
}
@Override
public void dispose(a) {
disposed = true;
// Cancel the request
call.cancel();
}
@Override
public boolean isDisposed(a) {
returndisposed; }}}Copy the code
After a Request is created, a synchronization Request is initiated
@Override
public Response<T> execute(a) throws IOException {
// Use callFactory (OkhttpClient) to create an okHTTP Call
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
//requestFactory Create creates the request
okhttp3.Request create(Object[] args) throws IOException {
// All parameters are stored in the array of parameter handlers
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args.length;
if(argumentCount ! = handlers.length) {throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")");
}
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart);
// Kotlin must have a Continuation argument that cannot be used for request body creation.
if (isKotlinSuspendFunction) {
// The Continuation is the last parameter and the handlers array contains null at that index.
argumentCount--;
}
List<Object> argumentList = new ArrayList<>(argumentCount);
for (int p = 0; p < argumentCount; p++) {
argumentList.add(args[p]);
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
}
Copy the code
The Apply method for Body, a subclass of ParameterHandler, starts building the request Body Request url (baseUrl+relativeUrl concatenation), header field processing
Request.Builder get(a) {
HttpUrl url;
HttpUrl.Builder urlBuilder = this.urlBuilder;
if(urlBuilder ! =null) {
url = urlBuilder.build();
} else {
url = baseUrl.resolve(relativeUrl);
}
RequestBody body = this.body; . MediaType contentType =this.contentType;
if(contentType ! =null) {
if(body ! =null) {
body = new ContentTypeOverridingRequestBody(body, contentType);
} else {
headersBuilder.add("Content-Type", contentType.toString()); }}return requestBuilder.url(url).headers(headersBuilder.build()).method(method, body);
}
Copy the code
Parse okhttp3’s rawResponse to Retrofit’s Response
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// read with okio
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 {
// the previously saved ResponseBody converter converts the ResponseBody
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
catchingBody.throwIfCaught();
throwe; }}Copy the code
Response handling: Two Observables work together to return data to the user BodyObservable emits onNext by reponse.body()
You can look at the onNext call order.
👆 basically runs through Retrofit+Rxjava’s request interface.
Retrofit+Call request interface process
Look from use, compared to the Retrofit + Rxjava, this principle is more simple, but with less people, it is the return value of the Call, the default DefaultCallAdapterFactory, The MainThreadExecutor mentioned above is used to switch threads in this scenario.
The enqueue method of the OkhttpCall class in Retrofit is the enqueue method of the okHTTP call, directly requesting the interface using the OKHTTP thread pool.
ExecutorCallbackCall is only used to handle thread switching
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor; //MainThreadExecutor
final Call<T> delegate; / / Retrofit of the Call.@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
// Async
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
callback.onFailure(ExecutorCallbackCall.this.new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response); }}); }@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }// Here is the enqueue method for Retrofit calls
@Override
public void enqueue(final Callback<T> callback) { okhttp3.Call call = createRawCall(); .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); }}@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); }}}); }Copy the code
Create the request and parse the response, much the same as above for RXJava. But it can only return Retrofit’s Response, not the type the user wants, which the user also needs to get via Response.body (), which is a bit cumbersome.
Retrofit+ Kotlin Coroutine request interface process
As mentioned earlier, Retrofit considers requests to be coroutines to see if there is a Continuation argument in them. Normally, if you add suspend to a method, the compiler will suspend it after you compile it, and then at the end of the method argument, HttpServiceMethod will create a RequestFactory, which will parse the argument and capture the Continuation.
//from RequestFactory
private @NullableParameterHandler<? > parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
.....
// Handle parameters normally.if (result == null) {
if (allowContinuation) {
try {
// Capture the Continuation
if (Utils.getRawType(parameterType) == Continuation.class) {
// Set the flag bit
isKotlinSuspendFunction = true;
return null; }}catch (NoClassDefFoundError ignored) {
}
}
......
return result;
}
Copy the code
Q: Why Rxjava does not follow the Observable return value?
Because the RequestFactory was created before the CallAdapter was created, when the RequestFactory parsed the parameters and found a Continuation, a flag bit was set to indicate that the coroutine was to be used.
So we can only write as follows: Continuation classes are as follows:
So, when parsing the argument, the Continuation argument is excluded. It is used to retrieve the response type. The compiler’s generics are not visible in IDE decompression.However, you can still use reflection Method to get its generic type at run time.
Q: It is still possible to use reflection Method to retrieve its generic type at runtime. The generic type is erased.
Take a look at the following example:Let’s decomcompile and see, can we capture any evidence that generics are still there?
After the javac is compiled, run the javap -verbose command to view the information Reflection is not found in the method body using generic types like Map and List, but can be obtained in the method or method parameters, becauseSignature retains the corresponding generic information.
Q: coroutines request there is no Call interface method statement, how does it choose DefaultCallAdapterFactory?
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method) {
// Create a RequestFactory
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; //true. Annotation[] annotations = method.getAnnotations(); Type adapterType;if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
// Find the Continuation
parameter takes the lower bound of ExtendItem
Type responseType = Utils.getParameterLowerBound(0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]); ./ / return value type, and secretly add something here, Call. The class - > it seems to go DefaultCallAdapterFactory
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
// Add an additional SkipCallbackExecutor annotation to the method annotation
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
// Create CallAdapter between default and Rxjava
CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations);
// Check whether the Response type is okHTTP's Response, if so throw Exception
// Check if the Response type is retrofit's Response. If so, throw Exception without generics
If the request is head and the return value is Void, throw Exception is thrown.// Create the converter
Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if(! isKotlinSuspendFunction) {// Non-coroutine parts
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
// The coroutine returns a Response
type
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
// The return value of the coroutine is of type XXX --> we are ExtendItem here, so go here
return (HttpServiceMethod<ResponseT, ReturnT>)
newSuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code
RequestFactory. IsKotlinSuspendFunction, when creating requestFactory coroutines words will set to true. Create a requestFactory, callAdapter, responseConverter, and SuspendForBody(a subclass of HttpServiceMethod). –>callFactory is OkhttpClient. This place is a bit more detail, suspend the compiled, parameters and a Continuation, but only two, CallAdapter DefaultCallAdapterFactory coroutines walk is the default, and in order not to conflict with retrofit mode of Call request, He secretly poisoned our code.
Type adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
Annotation[] annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
Copy the code
RawType Utils. ParameterizedTypeImpl ParameterizedType subclass, here is the Call, responseType is ExtendItem
static final class ParameterizedTypeImpl implements ParameterizedType { ..... ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) { this.ownerType = ownerType; this.rawType = rawType; this.typeArguments = typeArguments.clone(); } @Override public Type getRawType() { return rawType; }... }Copy the code
The response type obtained from the Contiuation parameter is ExtendItem and then encapsulated.It is critical to return Call for getRawType of the response type, decided to walk DefaultCallAdapterFactory.
Take a look at the ensurePrensent method: sneak a new annotation to the method and return the SkipCallbackExecutor, then create the CallAdapter for use. Sweet.
SkipCallbackExecutor means to skip the thread and switch to the main thread, so that the coroutine does not switch to Retrofit’s main thread. MainThreadExecutor is implemented by the user using the coroutine scheduler.
Finally, let’s look at suspend in action: Create callAdapterWe talked about that before
See DefaultCallAdapterFactory the get method, useful to SkipCallbackExecutor annotation.If you look at getRawType(returnType), any CallAdapter that is call. class can return a non-null CallAdapter, so it is useful for Retrofit to encapsulate rawType of response type as Call.
The SkipCallbackExecutor annotation makes the CallAdapter adapt method directReturn the call of the input parameter as is.
Prepare the converter for the response body
private final List<Converter.Factory> converterFactories = new ArrayList<>();
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
Copy the code
Mentioned earlier, he would try converFactories, we are here in a ExtendItem type, then its converter, can only be GsonResponseBodyConverter
Where Retrofit’s Java code merges with coroutine code — the SuspendForBody invoke method
final @Nullable Object invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
// SuspendForBody's CallAdapter didn't do anything. The CallAdapter was Retrofit's Call, but it didn't handle thread switching.
Call<ResponseT> call = callAdapter.adapt(call, args)
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
try {
return isNullable ? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
} catch (Exception e) {
returnKotlinExtensions.suspendAndThrow(e, continuation); }}Copy the code
After Retrofit’s call is created, since our return value is ExtendItem and not nullable, we throw it to the kotlinExtensions.await method and start coroutine processing.
suspend fun <T : Any> Call<T>.await(): T {
// Support cancelable coroutines
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
// Cancel the interface request
cancel()
}
// go to the okHTTP asynchronous interface request
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)
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
Copy the code
From Java calls to kotlin calls, the first parameter is the Call of Java, here is the Call of await expansion method, and then the Java the second parameter, give suspendCancellableCoroutine receiving, this await method is turned back into a classic hang function template. Not a single line of template code has been simplified.
Q: Where does the switch to the main thread take place after the coroutine request?
Look at the code to see if the lifecycleScope launch has a mainline scheduler.
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get(a) = lifecycle.coroutineScope
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get(a) {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if(existing ! =null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl( this.// The coroutine context is here
SupervisorJob() + Dispatchers.Main.immediate)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
Copy the code
Coroutines context: SupervisorJob () + Dispatchers. Main. Immediate, see from here he is a Supervisor and subordinate scope, it is cancelled, will not affect the parent coroutines, very can. Scheduler is the Dispatchers. Main. Immediate, also can say what, coroutines scheduling through coroutines interceptors to intercept a Continuation.
Kotlin code debug is not neat, you can take a look.
Five, the subsequent
We saw retrofit+ coroutines writing an interface request and an explicit try catch, which is really, kind of, hey, look at the follow-up retrofit supports Kotlin’s Flow and if not, consider trying the whole thing yourself.