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.