Public number: byte array, hope to help you ๐Ÿคฃ๐Ÿคฃ

For Android Developer, many open source libraries are essential knowledge points for development, from the use of ways to implementation principles to source code parsing, which require us to have a certain degree of understanding and application ability. So I’m going to write a series of articles about source code analysis and practice of open source libraries, the initial target is EventBus, ARouter, LeakCanary, Retrofit, Glide, OkHttp, Coil and other seven well-known open source libraries, hope to help you ๐Ÿคฃ๐Ÿคฃ

Article Series Navigation:

  • Tripartite library source notes (1) -EventBus source detailed explanation
  • Tripartite library source notes (2) -EventBus itself to implement one
  • Three party library source notes (3) -ARouter source detailed explanation
  • Third party library source notes (4) -ARouter own implementation
  • Three database source notes (5) -LeakCanary source detailed explanation
  • Tripartite Library source note (6) -LeakCanary Read on
  • Tripartite library source notes (7) -Retrofit source detailed explanation
  • Tripartite library source notes (8) -Retrofit in combination with LiveData
  • Three party library source notes (9) -Glide source detailed explanation
  • Tripartite library source notes (10) -Glide you may not know the knowledge point
  • Three party library source notes (11) -OkHttp source details
  • Tripartite library source notes (12) -OkHttp/Retrofit development debugger
  • Third party library source notes (13) – may be the first network Coil source analysis article

One, foreword

Is Retrofit now a standard feature in Android app development? I have been using Retrofit for a long time, and have been very comfortable with it, without encountering any big pits. Always do not use this to understand the bottom of the implementation seems not good, while starting to write three library source notes series of articles on Retrofit for a (self-feeling) comprehensive source code parsing it ~

Retrofit is A type-safe HTTP client for Android and Java. This means that the internal implementation of Retrofit doesn’t need to rely on the Android platform, but can be used with any Java client, and Retrofit is a special implementation of the Android platform. In addition, Kotlin is now the dominant development language for The Android platform, so the examples in this article are Kotlin ~

For those of you unfamiliar with Kotlin’s language, here’s my introduction to Kotlin: 26,000 Words

The source code for Retrofit isn’t too complicated, but it can be a bit convoluting because of the many design patterns applied. The author from 2020/10/10 began to see the source code, after a few days to see the source code began to write, but the total feeling can not elaborate particularly clear, write write became the current appearance. Readers if you think I have written where is not too good place also welcome to give advice ๐Ÿ˜‚๐Ÿ˜‚

Two, small examples

Let’s start with a few simple examples, and the rest of the presentation will revolve around them

Introduce the latest version of the current Retrofit:

dependencies {
    implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'
}
Copy the code

Without introducing any dependencies other than Retrofit, our process for making a network request would look something like this:

/ * * * the author: leavesC * time: 2020/10/13 0:05 * description: * GitHub:https://github.com/leavesC * /
interface ApiService {

    @GET("getUserData")
    fun getUserData(a): Call<ResponseBody>

}

fun main(a) {
    val retrofit = Retrofit.Builder()
        .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
        .build()
    val service = retrofit.create(ApiService::class.java)
    val call: Call<ResponseBody> = service.getUserData()
    call.enqueue(object : Callback<ResponseBody> {
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
            valuserBean = response.body()? .string() println("userBean: $userBean")}override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
            println("onFailure: $t")}}}Copy the code

Output results:

userBean: {"userName":"JBl"."userAge":7816977017260632}
Copy the code

Retrofit is a network request encapsulated library built on top of OkHttp and internally relies on OkHttp to complete the actual network requests. Retrofit is simple to use, and the API is declared through an interface. The first time I used Retrofit, I thought Retrofit was amazing. All I had to do was declare the API path, the request mode, the request parameters, the return value type, and so on. The method is then called to initiate the network request, which is friendless compared to the OkHttp and Volley network request libraries

As you can see, the result of the getUserData() request is a Json string with a return value type defined as Call

, where ResponseBody is okHttp3.responseBody, Call is a wrapper class provided by OkHttp for the result of a web request.Call is retrofit2.call, which is a wrapper that Retrofit does for okHttp3.call. OkHttp uses the callback okHttp3.call when the actual request is made. The Call to Retrofit2.call is relayed inside the callback to forward the result of the request to the outside

1, the converter – gson

This request is simple, but not convenient, because since we already know that the API returns a Json value, we naturally want the getUserData() method to return a Bean object. Instead of taking a String and deserializing it yourself, you can do this by introducing the Converter gson library

dependencies {
    implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'
    implementation 'com. Squareup. Retrofit2: converter - gson: 2.5.0'
}
Copy the code

With a few more code changes, you can get the UserBean object directly in the Callback

/ * * * the author: leavesC * time: 2020/10/13 0:05 * description: * GitHub:https://github.com/leavesC * /
interface ApiService {

    @GET("getUserData")
    fun getUserData(a): Call<UserBean>

}

data class UserBean(val userName: String, val userAge: Long)

fun main(a) {
    val retrofit = Retrofit.Builder()
        .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    val service = retrofit.create(ApiService::class.java)
    val call: Call<UserBean> = service.getUserData()
    call.enqueue(object : Callback<UserBean> {
        override fun onResponse(call: Call<UserBean>, response: Response<UserBean>) {
            val userBean = response.body()
            println("userBean: $userBean")}override fun onFailure(call: Call<UserBean>, t: Throwable) {
            println("onFailure: $t")}}}Copy the code

2, the adapter – rxjava2

Then, if Call

is not ok, you want to make network requests via RxJava? This is where the adapter-rxJavA2 library needs to be introduced

dependencies {
    implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'
    implementation 'com. Squareup. Retrofit2: converter - gson: 2.5.0'
    implementation 'com. Squareup. Retrofit2: adapter - rxjava2:2.9.0'
}
Copy the code

With a few more changes to the code, we don’t need to use call. enqueue to explicitly initiate a network request at all. Instead, we will automatically initiate a network request when we subscribe

/ * * * the author: leavesC * time: 2020/10/13 0:05 * description: * GitHub:https://github.com/leavesC * /
interface ApiService {

    @GET("getUserData")
    fun getUserData(a): Observable<UserBean>

}

data class UserBean(val userName: String, val userAge: Long)

fun main(a) {
    val retrofit = Retrofit.Builder()
        .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
    val service = retrofit.create(ApiService::class.java)
    val call: Observable<UserBean> = service.getUserData()
    call.subscribe(object : Consumer<UserBean> {
        override fun accept(userBean: UserBean?). {
            println("userBean: $userBean")}},object : Consumer<Throwable> {
        override fun accept(t: Throwable?). {
            println("onFailure: $t")}}}Copy the code

Ask questions

As you can see, Retrofit is very abstract. Whether you need a Call class or an Observable wrapper class, just add a different CallAdapterFactory, even if you want to return the LiveData type. Whether you need a ResponseBody or a specific Bean object, you just need to add a different ConverterFactory, even if the network request returns a value in XML format

After that, let’s step through the Retrofit source code with a few questions:

  1. How does Retrofit translate the interface’s internal methods into actual GET, POST, DELETE, and various network requests? For example, how does Retrofit convert the getUserData() method to an OkHttp GET request?
  2. How does Retrofit map API return values to concrete Bean objects? For example, how does ResponseBody map to UserBean?
  3. How does Retrofit abstract the return values of different interface methods to wrap classes? For example, how does a Call replace an Observable?

Third, Retrofit. The create ()

What does the retrofit.create method do

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            newClass<? >[] {service},new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                // If the external call is a method declared in Object, call it directly
                // Examples include toString(), hashCode(), and so on
                  return method.invoke(this, args); } args = args ! =null ? args : emptyArgs;
                // How to call a method based on whether it is the default method
                returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

The key here is the dynamic Proxy pattern implemented by proxy.newProxyInstance. Through the dynamic proxy, Retrofit forwards our call to the ApiService to the InvocationHandler for completion. Retrofit then takes the configuration items we annotated when declaring getUserData() through reflection, such as API path, request mode, request parameters, return value type, and so on, and then splice these configuration items into a network request of OkHttp. When we invoke the call.enqueue method, this triggers the InvocationHandler to initiate the OkHttp network request

The loadServiceMethod(method) method has the following logic:

  1. Convert each method object representing an interface method to a ServiceMethod object, which contains the details of the interface method
  2. Because a single interface method may be called multiple times in sequence, the constructed ServiceMethod object is cached in the serviceMethodCache for reuse
  private finalMap<Method, ServiceMethod<? >> serviceMethodCache =newConcurrentHashMap<>(); ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        / / the key
        result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); }}return result;
  }
Copy the code

Four, ServiceMethod

LoadServiceMethod (method) returns a ServiceMethod object. From the name, we can guess that each ServiceMethod object corresponds to an interface method, which contains the resolution result of the interface method. The loadServiceMethod(method).Invoke (args) operation corresponds to the process of calling an interface method and passing network request parameters, which corresponds to the process of service.getUserData()

ServiceMethod is an abstract class that contains only an abstract Invoke (Object[] args) method. ServiceMethod uses the factory pattern, because the network request may end up being executed in a variety of ways, either through a thread pool or through a Kotlin coroutine. The purpose of using the factory pattern is to hide these differences in different ServiceMethod implementation classes, while the external unity is to obtain the ServiceMethod implementation class from parseAnnotations

The ServiceMethod returned from parseAnnotations is actually HttpServiceMethod, . So the key is coming to see HttpServiceMethod parseAnnotations method returns HttpServiceMethod concrete is how to implement, and is how to piece together a complete OkHttp request invocation chain

abstract class ServiceMethod<T> {
    
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    // The requestFactory contains the result of parsing the API annotation information
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    // Throw an exception if the return value contains an unspecified generic type or contains wildcards
    // Because Retrofit cannot construct an object of an undefined type as a return value
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    // The return value type cannot be void
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }
	
    / / the key
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
    
}
Copy the code

Fifth, HttpServiceMethod

ServiceMethod There is only one direct subclass of this abstract class, HttpServiceMethod. HttpServiceMethod is also an abstract class that contains two generic declarations: ResponseT, which represents the enclosing type of the value returned by the interface method, and ReturnT, which represents the actual data type we need. For example, for the fun getUserData(): Call

method, ResponseT corresponds to Call and ReturnT corresponds to UserBean

HttpServiceMethod implements the invoke method of the parent class and transfers the operation to another abstract method, adapt. As you can see, even if we declare the return type Observable

for the interface method, we still need to create a Call object inside the Invoke method. HttpServiceMethod simply allows adapt to transform a Call into an Observable

abstract class HttpServiceMethod<ResponseT.ReturnT> extends ServiceMethod<ReturnT> {
 
  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

  protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args); ...}Copy the code

Look at HttpServiceMethod. ParseAnnotations () method is how to construct a HttpServiceMethod object, and the object of adapt method is how to implement

abstract class HttpServiceMethod<ResponseT.ReturnT> extends ServiceMethod<ReturnT> {

  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) {
    // Whether the request is a Suspend function. That is, whether the request is made as a Kotlin coroutine
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    if (isKotlinSuspendFunction) {
        // Omit some processing logic for Kotlin coroutines
    } else {
      adapterType = method.getGenericReturnType();
    }
	
    //้‡็‚น1
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
      
    // Get the concrete type inside the wrapper class, for example, Observable
      
        inside UserBean
      
    //responseType cannot be okHttp3.response or a Response that does not contain a specific generic type
    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.");
    }
	
    //้‡็‚น2
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if(! isKotlinSuspendFunction) {//้‡็‚น3
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } 
    
    // Omit some processing logic for Kotlin coroutines......}}Copy the code

Retrofit already supports Kotlin coroutines for calls to Retrofit, but this example is unrelated to coroutines, so we’ll ignore the coroutine-related logic for a second. The main logic for parseAnnotations is:

  1. Through the firstcreateCallAdapter(retrofit, method, adapterType, annotationsMethod gets the CallAdapter object that is used to implement the interface methodReturn value wrapped classProcessing logic. For example,getUserData()MethodA wrapper classIf the type isCallThen return CallAdapter object is included DefaultCallAdapterFactory Adapter; If is observables, then returned is RxJava2CallAdapterFactory contains the Adapter
  2. throughcreateResponseConverter(retrofit, method, responseType)Method gets the Converter object, which is used to implement the interface methodThe return valueProcessing logic. For example,getUserData()If the target return type of the method is ResponseBody, then the Converter object corresponds to BuiltInConverters; If it is UserBean, then it corresponds to GsonConverterFactory
  3. Based on the values obtained in the first two steps, construct a CallAdapted object and return it

CallAdapted is a subclass of HttpServiceMethod. In the previous step, we found a CallAdapter that converts a Call to an Observable, so for CallAdapted, Its adapt method directly submits the Call to the CallAdapter, which implements the conversion process

abstract class HttpServiceMethod<ResponseT.ReturnT> extends ServiceMethod<ReturnT> {
    
    @Override
  	final @Nullable ReturnT invoke(Object[] args) {
    	Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    	returnadapt(call, args); }}static final class CallAdapted<ResponseT.ReturnT> extends HttpServiceMethod<ResponseT.ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      returncallAdapter.adapt(call); }}Copy the code

Six, OkHttpCall

OkHttpCall is where the actual OkHttp request is made. When we call fun getUserData(): The Call object returned by the Call

method is actually of type OkHttpCall. When we Call call.enqueue(Callback), the enqueue method makes an OkHttp request. The passed Retrofit2. Callback object is then relayed by okHttp3. Callback itself when the Callback is received

final class OkHttpCall<T> implements Call<T> {
  private final RequestFactory requestFactory;
  private final Object[] args;
  private final okhttp3.Call.Factory callFactory;
  private final Converter<ResponseBody, T> responseConverter;

  private volatile boolean canceled;

  @GuardedBy("this")
  private @Nullable okhttp3.Call rawCall;

  @GuardedBy("this") // Either a RuntimeException, non-fatal Error, or IOException.
  private @Nullable Throwable creationFailure;

  @GuardedBy("this")
  private boolean executed;
    
  @Override
  public void enqueue(final Callback<T> callback) {... okhttp3. Call the Call; ... the call. The enqueue (new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
              throwIfFatal(e);
              callFailure(e);
              return;
            }

            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              throwIfFatal(t);
              t.printStackTrace(); // TODO this is not great}}@Override
          public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              throwIfFatal(t);
              t.printStackTrace(); // TODO this is not great}}}); }...}Copy the code

Make a summary

The contents of the above sections talk about all the processes involved in the process of initiating the following request, but it is actually a little difficult to grasp each small point if you simply look at it like this. I am a little confused by myself, so here I will review the above content and connect all the knowledge points in series

  • First, we passretrofit.create(ApiService::class.java)I get an ApiServiceDynamic implementation class, which is provided natively through JavaProxy.newProxyInstanceRepresents the dynamic proxy function to implement. Once we have the ApiService implementation class, we can directly call all the methods declared in the ApiService
  • When we callservice.getUserData()Method, Retrofit abstracts and encapsulates each interface method as a ServiceMethod object and caches it. Our actions are passed to the ServiceMethod, which returns our target typeserviceMethod.invoke(Object[] args)Method, args stands for the argument we pass when we call the interface method, which in this case is an empty array
  • ServiceMethod is usedThe factory patternSince the final mode of network request may be diversified, either by thread pool or by Kotlin coroutine, the significance of using factory pattern is that this difference can be hidden in different ServiceMethod implementation classes, while external unification is throughparseAnnotationsMethod to get the ServiceMethod implementation class
  • ServiceMethod has a unique direct subclass, HttpServiceMethod. HttpServiceMethod itself has found a converter that converts a Call to an Observable and a ResponseBody to a UserBeaninvokeMethod builds an OkHttpCall object and forwards it to the abstract methodadaptBy theadaptTo initiate the actual network request
  • Regardless of whether the external interface method returns a value typeObservable<UserBean>The final network request is made through OkHttpCall. HttpServiceMethod hides OkHttpCall internally by relying on the found converter

How are interface methods resolved?

How does Retrofit translate the interface’s internal methods into actual GET, POST, DELETE, and various network requests? For example, how does Retrofit convert getUserData() to an OkHttp GET request?

The process in ServiceMethod parseAnnotations method is completed, the corresponding is RequestFactory parseAnnotations (retrofit, method) method

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    / / the keyRequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); ...return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}
Copy the code

As mentioned earlier, Retrofit is a network request encapsulation library built on top of OkHttp and internally relies on OkHttp to complete the actual network requests. The general request pattern for OkHttp is as follows

fun run(url: String): String {
    val request: Request = Request.Builder()
        .url(url)
        .build()
    OkHttpClient().newCall(request).execute().use { response ->
        returnresponse.body!! .string() } }Copy the code

OkHttp needs to build a Request object to configure the Request mode and Request parameters in order to initiate a network Request. So, Retrofit also needs a process to build the Request object, which is hidden in the RequestFactory

The RequestFactory follows the Builder pattern, so we don’t need to worry too much about the building process here, except that the RequestFactory contains the results of parsing the API methods. Its create(Object[] args) method will parse the items and eventually return an okHttp3.request Object

final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

  private final Method method;
  private final HttpUrl baseUrl;
  final String httpMethod;
  private final @Nullable String relativeUrl;
  private final @Nullable Headers headers;
  private final @Nullable MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private finalParameterHandler<? >[] parameterHandlers;final boolean isKotlinSuspendFunction;

  okhttp3.Request create(Object[] args) throws IOException {
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    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);

    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, newInvocation(method, argumentList)).build(); }}Copy the code

We now know that OkHttpCall is where the network request is actually made, so eventually the Create method of the RequestFactory will be called by the createRawCall() method of OkHttpCall

final class OkHttpCall<T> implements Call<T> {
     
  	private okhttp3.Call createRawCall(a) throws IOException {
    	okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
    	if (call == null) {
      		throw new NullPointerException("Call.Factory returned null.");
    	}
    	returncall; }}Copy the code

How to map ResponseBody to UserBean

How does Retrofit map API return values to concrete Bean objects? For example, how does ResponseBody map to UserBean?

The default OkHttp interface return value object is ResponseBody. If we do not introduce the Converter -gson, we can only define the interface result as ResponseBody, not the Bean object. Because Retrofit cannot automatically complete the ResponseBody to UserBean conversion, we need to inform Retrofit of this conversion rule. This conversion rule is defined by Retrofit as the Converter interface, corresponding to its responseBodyConverter method

public interface Converter<F.T> {
    
  @Nullable
  T convert(F value) throws IOException;

  abstract class Factory {
	
    // Convert ResponseBody to the target type type
    public @NullableConverter<ResponseBody, ? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) {return null; }...}}Copy the code

To get the UserBean object directly, we need to add the GsonConverterFactory when we build the Retrofit object. GsonConverterFactory deserializes the UserBean object through Gson based on the target type type

public final class GsonConverterFactory extends Converter.Factory {

  @Override
  publicConverter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));return newGsonResponseBodyConverter<>(gson, adapter); }...}final class GsonResponseBodyConverter<T> implements Converter<ResponseBody.T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);
      if(jsonReader.peek() ! = JsonToken.END_DOCUMENT) {throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally{ value.close(); }}}Copy the code

So, how does Retrofit know which types to delegate to the GsonConverterFactory? At the very least, the ResponseBody should not be handled by the GsonConverterFactory.

First, we pass GsonConverterFactory when we build the Retrofit object, and eventually Retrofit will sort all of the Converter.Factory, By default BuiltInConverters are first in the converterFactories, BuiltInConverters is a Retrofit Converter.Factory implementation class that defaults to ResponseBody resolution

public final class Retrofit {
    
   public static final class Builder {
          public Retrofit build(a) {...// Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories =
          new ArrayList<>(
              1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories()); ...}}Copy the code

BuiltInConverters’ responseBodyConverter method returns null if the target type is not ResponseBody, Void, Unit, etc

final class BuiltInConverters extends Converter.Factory {
    
  @Override
  public @NullableConverter<ResponseBody, ? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) {if (type == ResponseBody.class) {
      return Utils.isAnnotationPresent(annotations, Streaming.class)
          ? StreamingResponseBodyConverter.INSTANCE
          : BufferingResponseBodyConverter.INSTANCE;
    }
    if (type == Void.class) {
      return VoidResponseBodyConverter.INSTANCE;
    }
    if (checkForKotlinUnit) {
      try {
        if (type == Unit.class) {
          returnUnitResponseBodyConverter.INSTANCE; }}catch (NoClassDefFoundError ignored) {
        checkForKotlinUnit = false; }}return null; }...}Copy the code

And Retrofit nextResponseBodyConverter method of a class is for each interface methods select the Converter to return data type conversion method. This method will first traversal to BuiltInConverters, found its returns null, will ultimately choose to GsonResponseBodyConverter, so as to complete the data parsing. If a suitable handler is not found, an IllegalArgumentException is thrown

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    Objects.requireNonNull(type, "type == null");
    Objects.requireNonNull(annotations, "annotations == null");

    int start = converterFactories.indexOf(skipPast) + 1;
    for (inti = start, count = converterFactories.size(); i < count; i++) { Converter<ResponseBody, ? > converter = converterFactories.get(i).responseBodyConverter(type, annotations,this);
      if(converter ! =null) {
        //noinspection unchecked
        return(Converter<ResponseBody, T>) converter; }}...throw new IllegalArgumentException(builder.toString());
  }
Copy the code

How to replace Call with Observable

How does Retrofit abstract different interface return value wrapper classes? For example, how is a Call replaced with an Observable?

As in the previous section, Retrofit by default only supports wrapping retrofit2.call as the return value of an interface method. In order to support returning Observable types, We need to add RxJava2CallAdapterFactory when building Retrofit

Retrofit abstracts the rule that transforms a Retrofit2.Call into an Observable into a CallAdapter interface

public interface CallAdapter<R.T> {

  // Return the concrete internal type, that is, UserBean
  Type responseType(a);

  // To convert a Call into an Observable
  T adapt(Call<R> call);

  abstract class Factory {

    // Provides a CallAdapter object that transforms Call
      
        into Observable
       
    Observable
      
        Observable
       
    // If the CallAdapter cannot complete the conversion, then null is returned
    public abstract @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit); ...}}Copy the code

For the get method RxJava2CallAdapterFactory, how to return value type is not Completable, Flowable, Single type, Maybe will return null, Otherwise, the RxJava2CallAdapter object is returned

public final class RxJava2CallAdapterFactory extends CallAdapter.Factory {...@Override
  public @NullableCallAdapter<? ,? > get(Type returnType, Annotation[] annotations, Retrofit retrofit) { Class<? > rawType = getRawType(returnType);if (rawType == Completable.class) {
      		// Completable is not parameterized (which is what the rest of this method deals with) so it
      		// can only be created with a single configuration.
      		return new RxJava2CallAdapter(
          	Void.class, scheduler, isAsync, false.true.false.false.false.true);
    	}

    	boolean isFlowable = rawType == Flowable.class;
    	boolean isSingle = rawType == Single.class;
    	boolean isMaybe = rawType == Maybe.class;
    	if(rawType ! = Observable.class && ! isFlowable && ! isSingle && ! isMaybe) {return null; }...return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false); }}Copy the code

For this example, eventually RxJava2CallAdapter returns CallExecuteObservable, The CallExecuteObservable calls call.execute() to initiate the network request while the external subscribe, so we don’t need to explicitly initiate the network request in the example above. Instead, the request is automatically triggered when the subscribe is carried out, and the Observer just needs to wait for the network request result to automatically call back

final class RxJava2CallAdapter<R> implements CallAdapter<R.Object> {...@Override
  public Type responseType(a) {
    return responseType;
  }

  @Override
  public Object adapt(Call<R> call) {
    Observable<Response<R>> responseObservable =
        isAsync ? new CallEnqueueObservable<>(call) : newCallExecuteObservable<>(call); ...returnRxJavaPlugins.onAssembly(observable); }}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) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    CallDisposable disposable = new CallDisposable(call);
    observer.onSubscribe(disposable);
    if (disposable.isDisposed()) {
      return;
    }

    boolean terminated = false;
    try {
      // Initiate a network request
      Response<T> response = call.execute();
      if(! disposable.isDisposed()) {// Send the result of the request to the outside
        observer.onNext(response);
      }
      if(! disposable.isDisposed()) { terminated =true; observer.onComplete(); }}catch (Throwable t) {
      Exceptions.throwIfFatal(t);
      if (terminated) {
        RxJavaPlugins.onError(t);
      } else if(! disposable.isDisposed()) {try {
          observer.onError(t);
        } catch (Throwable inner) {
          Exceptions.throwIfFatal(inner);
          RxJavaPlugins.onError(newCompositeException(t, inner)); }}}} ยทยทยท}Copy the code

So, the question again, Retrofit is how to know what types can be passed on to RxJava2CallAdapterFactory for processing?

First of all, when we are building Retrofit object incoming RxJava2CallAdapterFactory, Retrofit will end up in accordance with the order for all the CallAdapter. Factory to save, And the default will be added in the next morning a DefaultCallAdapterFactory, used for packing type for retrofit2. Analyzes the situation of the Call

The nextCallAdapter method of the Retrofit class is the method that selects the CallAdapter for each API method to convert the return value to the data type. The method first iterates to the RxJava2CallAdapter, finds that it returns a non-null value, and then hands it over for processing

 publicCallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
    Objects.requireNonNull(returnType, "returnType == null");
    Objects.requireNonNull(annotations, "annotations == null");

    int start = callAdapterFactories.indexOf(skipPast) + 1;
    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

Eleven, summarize again

Here’s a summary of the previous two sections on the overall data transformation process for Retrofit

By default, the primitive return value type we get from the Callback is Response

, and with the introduction of converter-gson and Adapter-rxJavA2, We can get the target type UserBean directly

In order for Retrofit to achieve this conversion effect, it takes two steps:

  1. Convert the ResponseBody to a UserBean to get the interface method return valueResponse<UserBean>
  2. Converts a Call to an Observable that directly converts a Call to an ObservableResponse<UserBean>To get the target type UserBean directly by taking UserBean as the return value

In the first step described in Section 9, the conversion of ResponseBody to UserBean is defined through the Converter interface

public interface Converter<F.T> {
   
  // Used to convert type F to type T
  @Nullable
  T convert(F value) throws IOException; ...}Copy the code

This process occurs in OkHttpCall. The enqueue method gets the okHttp3.response object returned by OkHttp, and then calls parseResponse to complete the Response

logic. The convert method of the Converter interface is called, which returns Response

  final class OkHttpCall<T> implements Call<T> {
 
  @Override
  public void enqueue(final Callback<T> callback) {
    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              / / the key
              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}}}); }private final Converter<ResponseBody, T> responseConverter;
    
 Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {... ExceptionCatchingResponseBody catchingBody =new ExceptionCatchingResponseBody(rawBody);
    try {
      // Convert the ResponseBody type to T
      T body = responseConverter.convert(catchingBody);
      // Then wrap it as Response
      
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throwe; }}}Copy the code

In the second step, described in Section 10, the rules for converting a Call to an Observable are defined through the CallAdapter interface

public interface CallAdapter<R.T> {

  Type responseType(a);

  // This method is used to convert Call
      
        to the desired target type T, such as Observable
       
  T adapt(Call<R> call); ...}Copy the code

In the CallEnqueueObservable class, a custom callback interface, CallCallback, is used to initiate a network request to get the Response

object that has been parsed in the first step

final class CallEnqueueObservable<T> extends Observable<Response<T>> {
  private final Call<T> originalCall;

  CallEnqueueObservable(Call<T> originalCall) {
    this.originalCall = originalCall;
  }

  @Override
  protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    CallCallback<T> callback = new CallCallback<>(call, observer);
    observer.onSubscribe(callback);
    if(! callback.isDisposed()) {// Custom callback to initiate the requestcall.enqueue(callback); }}private static final class CallCallback<T> implements Disposable.Callback<T> {
    private finalCall<? > call;private final Observer<? super Response<T>> observer;
    private volatile boolean disposed;
    boolean terminated = false; CallCallback(Call<? > call, Observer<?super Response<T>> observer) {
      this.call = call;
      this.observer = observer;
    }

    @Override
    public void onResponse(Call<T> call, Response<T> response) {...// Pass Response
      
        directly, that is, Response
       
         object
       observer.onNext(response); ยทยทยท} ยทยทยท}Copy the code

The CallCallback class also holds an observer object that actually belongs to the BodyObservable class. After the BodyObservable gets the Response

object, if it determines that the network request is in a successful state, it will directly fetch the body (that is, UserBean) and pass it. So we can get the target type directly, without any wrapper classes

final class BodyObservable<T> extends Observable<T> {
  private final Observable<Response<T>> upstream;

  BodyObservable(Observable<Response<T>> upstream) {
    this.upstream = upstream;
  }

  @Override
  protected void subscribeActual(Observer<? super T> observer) {
    upstream.subscribe(new BodyObserver<T>(observer));
  }

  private static class BodyObserver<R> implements Observer<Response<R>> {
    private final Observer<? super R> observer;
    private boolean terminated;

    BodyObserver(Observer<? super R> observer) {
      this.observer = observer;
    }

    @Override
    public void onSubscribe(Disposable disposable) {
      observer.onSubscribe(disposable);
    }

    @Override
    public void onNext(Response<R> response) {
      if (response.isSuccessful()) {
        // If the network request is successful, then take out the body and pass it directly
        observer.onNext(response.body());
      } else {
        terminated = true;
        Throwable t = new HttpException(response);
        try {
          observer.onError(t);
        } catch (Throwable inner) {
          Exceptions.throwIfFatal(inner);
          RxJavaPlugins.onError(newCompositeException(t, inner)); }} ยทยทยท ยท}}Copy the code

Using Kotlin coroutines

The current version of Retrofit already supports calling Kotlin coroutines. How does Retrofit support coroutine calls

Import all the dependencies you need to use. Since this example is a pure Kotlin project, you will not import the Kotlin coroutine support library for the Android platform

dependencies {
    implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'
    implementation 'com. Squareup. Retrofit2: converter - gson: 2.5.0'
    implementation 'org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.3.9'
}
Copy the code

This example starts a coroutine with runBlocking to prevent the main thread from stopping before the network request has finished. It is important to note that you should avoid using coroutines in this way in real development, otherwise it makes little sense to use coroutines

/ * * * the author: leavesC * time: 2020/10/19 22:00 * description: * GitHub:https://github.com/leavesC * /
interface ApiService {

    @GET("getUserData")
    suspend fun getUserData(a): UserBean

}

data class UserBean(val userName: String, val userAge: Long)

fun main(a) {
    val retrofit = Retrofit.Builder()
        .baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    val service = retrofit.create(ApiService::class.java)
    runBlocking {
        val job: Job = launch {
            try {
                val userBean: UserBean = service.getUserData()
                println("userBean: $userBean")}catch (e: Throwable) {
                println("onFailure: $e")}}}}Copy the code

In this case, the return value of the getUserData() method doesn’t need any wrapper class. We can simply declare the target data type, which is much simpler to use than before

All right, let’s go through the process

Let’s declare a few more methods for ApiService to analyze the rules. Each method is decorated with the suspend keyword indicating that it can only be called inside a coroutine

interface ApiService {

    @GET("getUserData")
    fun getUserData(a): UserBean

    @GET("getUserData")
    suspend fun getUserData1(a): UserBean

    @GET("getUserData")
    suspend fun getUserData2(id: String): UserBean

    @GET("getUserData")
    suspend fun getUserData3(id: String, limit: Int): UserBean

}
Copy the code

Retrofit is implemented in the Java language, but the suspend keyword can only be used in Kotlin. There is a “communication barrier” between the two, but as long as the caller is also in the JVM language, Retrofit is technically available. Here we decompile the ApiService into the following Java classes using IDEA. See how suspend functions are implemented in Retrofit

public interface ApiService {
   @GET("getUserData")
   @NotNull
   UserBean getUserData(a);

   @GET("getUserData")
   @Nullable
   Object getUserData1(@NotNull Continuation var1);

   @GET("getUserData")
   @Nullable
   Object getUserData2(@NotNull String var1, @NotNull Continuation var2);

   @GET("getUserData")
   @Nullable
   Object getUserData3(@NotNull String var1, int var2, @NotNull Continuation var3);
}
Copy the code

As you can see, the results of the non-suspend functions are as expected, but the suspend functions are much different. The return type of the methods is Object. And at the end of the method of the parameter list has been added a kotlin. Coroutines. Continuation parameters. This parameter is important and will be used later

The RequestFactory class contains a Boolean variable isKotlinSuspendFunction that marks whether the Method is currently resolved as a suspend function. In the RequestFactory’s build() method, each parameter of the API method is parsed, including the logic to check whether the currently parsed parameter is the last parameter

RequestFactory build(a) {...int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = newParameterHandler<? >[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            //p == lastParameter. If true, parameterTypes[p] is the lastParameter of the API methodparseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); }...return new RequestFactory(this);
}
Copy the code

IsKotlinSuspendFunction will be true if the last parameter type is detected as continuation. class. This detection logic follows the rules described above for converting the ApiService code of type Kotlin to Java form

private @NullableParameterHandler<? > parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations, booleanAllowContinuation) {...if (result == null) {
        if (allowContinuation) {
          try {
            if (Utils.getRawType(parameterType) == Continuation.class) {
              isKotlinSuspendFunction = true;
              return null; }}catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
      }

      return result;
    }
Copy the code

Then, we use the isKotlinSuspendFunction in the parseAnnotations method of HttpServiceMethod

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) {...// Although the getUserData() method we directly define the return type as UserBean
      // But Retrofit still needs to convert the return type to Call
      
        to fit our data parsing process
      
      // The responseType is UserBean and the adapterType is Call
      
      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else{ adapterType = method.getGenericReturnType(); } CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); .../ / the key
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForBody<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
              continuationBodyNullable);
  }
Copy the code

Finally, for this example, the final return value of parseAnnotations is SuspendForBody, which is also a subclass of HttpServiceMethod. The main logic is:

  1. The last parameter of an interface method is strongly converted toContinuation<ResponseT>Type, which fits the above analysis
  2. Because isNullable is fixed to false, it will eventually be calledKotlinExtensions.await(call, continuation)This extension function for Kotlin
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT.Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    private final boolean isNullable;

    SuspendForBody(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, Call<ResponseT>> callAdapter,
        boolean isNullable) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
      this.isNullable = isNullable;
    }

    @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];
      try {
        return isNullable
            ? KotlinExtensions.awaitNullable(call, continuation)
            : KotlinExtensions.await(call, continuation);
      } catch (Exception e) {
        returnKotlinExtensions.suspendAndThrow(e, continuation); }}}Copy the code

Await () method to suspendCancellableCoroutine this support to cancel CoroutineScope as scope, still in the Call. The enqueue way to initiate OkHttp request, When it gets the responseBody, it passes through and completes the call process

suspend fun <T : Any> Call<T>.await(a): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    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

Retrofit & Android

As mentioned above, the internal implementation of Retrofit is not dependent on the Android platform, but can be used with any Java client, and Retrofit is a special implementation for the Android platform. So what specific support does Retrofit have for Android?

When building the Retrofit object, we have the option of passing a Platform object to mark the Platform on which the caller is on

public static final class Builder {
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private @Nullable HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder(a) {
      this(Platform.get()); }...}Copy the code

The Platform class has two functions:

  1. Determine whether Java 8 is supported. This is needed to determine whether the default method of an interface is supported, and whether Optional and CompletableFutures are supported. If an Android application wants to support Java 8, it needs to be actively configured in the Gradle file. Java 8 is not fully supported on The Android platform, so you need to determine whether to enable certain features
  2. Executor that implements the callback to the main thread. It is well known that the Android platform does not allow time-consuming tasks to be performed on the main thread, and all UI operations need to be switched to the main thread. So, for the Android platform, Retrofit performs a thread switch when calling back the result of a network request through an Executor executed by the main thread
class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get(a) {
    return PLATFORM;
  }

  private static Platform findPlatform(a) {
    // Use the JVM name to determine whether the user is an Android platform
    return "Dalvik".equals(System.getProperty("java.vm.name"))?new Android() //
        : new Platform(true);
  }
  
  // Whether Java 8 is supported
  private final boolean hasJava8Types;
  private final @Nullable Constructor<Lookup> lookupConstructor;

  Platform(boolean hasJava8Types) {
    this.hasJava8Types = hasJava8Types;

    Constructor<Lookup> lookupConstructor = null;
    if (hasJava8Types) {
      try {
        // Because the service interface might not be public, we need to use a MethodHandle lookup
        // that ignores the visibility of the declaringClass.
        lookupConstructor = Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookupConstructor.setAccessible(true);
      } catch (NoClassDefFoundError ignored) {
        // Android API 24 or 25 where Lookup doesn't exist. Calling default methods on non-public
        // interfaces will fail, but there's nothing we can do about it.
      } catch (NoSuchMethodException ignored) {
        // Assume JDK 14+ which contains a fix that allows a regular lookup to succeed.
        // See https://bugs.openjdk.java.net/browse/JDK-8209005.}}this.lookupConstructor = lookupConstructor;
  }
    
  // Get the default Executor implementation for the Android platform
  @Nullable
  Executor defaultCallbackExecutor(a) {
    return null;
  }

  List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
      @Nullable Executor callbackExecutor) {
    DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
    return hasJava8Types
        ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
        : singletonList(executorFactory);
  }

  int defaultCallAdapterFactoriesSize(a) {
    return hasJava8Types ? 2 : 1;
  }

  List<? extends Converter.Factory> defaultConverterFactories() {
    return hasJava8Types ? singletonList(OptionalConverterFactory.INSTANCE) : emptyList();
  }

  int defaultConverterFactoriesSize(a) {
    return hasJava8Types ? 1 : 0;
  }

  @IgnoreJRERequirement // Only called on API 24+.
  boolean isDefaultMethod(Method method) {
    return hasJava8Types && method.isDefault();
  }

  @IgnoreJRERequirement // Only called on API 26+.
  @Nullable
  Object invokeDefaultMethod(Method method, Class
        declaringClass, Object object, Object... args)
      throws Throwable { Lookup lookup = lookupConstructor ! =null
            ? lookupConstructor.newInstance(declaringClass, -1 /* trusted */)
            : MethodHandles.lookup();
    returnlookup.unreflectSpecial(method, declaringClass).bindTo(object).invokeWithArguments(args); }}Copy the code

The Platform class has only one unique subclass, the Android class. The main logic is to override the defaultCallbackExecutor() method of the parent class, using a Handler to execute a specific Runnable in the main thread, so that the results of network requests are all called back in the main thread

static final class Android extends Platform {
    Android() {
      super(Build.VERSION.SDK_INT >= 24);
    }

    @Override
    public Executor defaultCallbackExecutor(a) {
      return new MainThreadExecutor();
    }

    @Nullable
    @Override
    Object invokeDefaultMethod( Method method, Class
        declaringClass, Object object, Object... args) throws Throwable {
      if (Build.VERSION.SDK_INT < 26) {
        throw new UnsupportedOperationException(
            "Calling default methods on API 24 and 25 is not supported");
      }
      return super.invokeDefaultMethod(method, declaringClass, object, args);
    }

    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

As mentioned earlier, Retrofit has a default callAdapter.factory interface implementation class for handling cases where the return value of the interface method is wrapped in a Call type. DefaultCallAdapterFactory get Platform to return to the Executor of object, if the Executor interface method object is not null and not marked SkipCallbackExecutor annotations, The Executor object is used as a proxy to relay all callback operations for thread switching. The decorator pattern is used here

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public @NullableCallAdapter<? ,? > Get (Type returnType, Annotation[] annotations, Retrofit Retrofit) {ยทยทfinal Executor executor =
        // Determine whether annotations contain the SkipCallbackExecutor annotation
        // If you want to call the method in the original thread, no thread switch is required
        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) {
        Executor is used as an intermediate proxy if it is not null
        // Let ExecutorCallbackCall do the actual callback
        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) {
      Objects.requireNonNull(callback, "callback == null");

      delegate.enqueue(
          new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              callbackExecutor.execute(
                  () -> {
                    if (delegate.isCanceled()) {
                      // Emulate OkHttp's behavior of throwing/delivering an IOException on
                      // cancellation.
                      callback.onFailure(ExecutorCallbackCall.this.new IOException("Canceled"));
                    } else {
                      callback.onResponse(ExecutorCallbackCall.this, response); }}); }@Override
            public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }...}}Copy the code

14. Dynamic proxy mode

Dynamic proxies are mentioned in the retrofit.create section. The dynamic proxy pattern is the main reason Retrofit is able to make network requests so simple and convenient. Sometimes, for a given interface, we don’t want to declare and use the implementation class directly, but rather want the implementation class to be dynamically generated and provide the opportunity to implement AOP programming. This can be done through proxy.newProxyInstance

/ * * * the author: leavesC * time: 2020/10/20 so * description: * GitHub:https://github.com/leavesC * /
data class UserBean(val userName: String, val userAge: Long)

interface ApiService {

    fun getUserData(id: String): UserBean

}

fun main(a) {
    val apiService = ApiService::class.java
    var newProxyInstance = Proxy.newProxyInstance(
        apiService.classLoader,
        arrayOf<Class<*>>(apiService), object : InvocationHandler {
            override fun invoke(proxy: Any, method: Method, args: Array<out Any>?: Any {
                println("methodName: " + method.name)
                args?.forEach {
                    println("args: " + it)
                }
                return UserBean("leavesC".26)
            }

        })
    newProxyInstance = newProxyInstance as ApiService
    val userBean = newProxyInstance.getUserData("100")
    println("userBean: $userBean")}Copy the code

In the above example, although we did not declare any ApiService implementation class, we got the method object represented by the getUserData method and the request parameter args from the Invoke method, and finally got the return value externally. That’s where the agency model comes in handy

methodName: getUserData
args: 100
userBean: UserBean(userName=leavesC, userAge=26)
Copy the code

15. The End

Retrofit source code here, I still feel quite comprehensive, although may not be so easy to understand =_= from the start to see the source code to write the article spent ten days out of some, intermittently to see the source code, intermittently to write the article, finally finished writing. If you think it will help you, please click a like ๐Ÿ˜‚๐Ÿ˜‚

I’ll write more about Retrofit in the next article