Take you step by step through Retrofit source parsing: a network request framework based on OkHttp

What is the difference and connection between OkHttp and Retrofit?

Reference Answer:
OkHttp and Retrofit are both popular open source frameworks for networks

Packaging is different:
Retrofit encapsulates specific requests, thread switching, and data conversion.
retrofit encapsulates okhttp using proxy, appearance, and policy patterns
OkHttp is a set of requesting clients encapsulated based on the Http protocol

Duties differ:
Retrofit is mainly responsible for application-level encapsulation, developer-oriented, easy to use, such as request parameters, response data processing, error handling, and so on.
OkHttp is responsible for optimizing and encapsulating socket parts, such as network access, multiplexing, buffer caching, data compression, and so on.

(Leave a GitHub link by hand, and you can find it yourself if you need access to relevant interviews, etc.)
https://github.com/xiangjiana/Android-MS
(VX: mm14525201314)

Retrofit and OkHttp are brothers and brothers. They are network request libraries launched by Square Corporation. Retrofit is actually based on OkHttp. It encapsulates OkHttp's existing functions, supports configuration of network request parameters through annotations, and uniformly packages the parsing and serialization of data returned, even on OkHttp.Support for collaboration pairs has recently been introduced.

Today let's see how Retrofit elegantly encapsulates and extends functionality on the basis of an already fixed framework, OkHttp.

Basic Use
Let's first look at the basic use of Retrofit to get a general idea of it.

First, we can build a request Service class that annotates the methods and parameters of each request:

  public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
  }

After that, we can construct a Retrofit object and pass in the corresponding class through the Retrofit.create method to construct the corresponding Service object:

  Retrofit retrofit = new Retrofit.Builder()
     baseUrl("https://api.github.com/")
     build();
  }
  GitHubService service = retrofit.create(GitHubService.class);

Then we call the corresponding method in the service to get the Call object.

Asynchronous calls to requests can be made by calling enqueue on the Call object, and synchronous calls to requests can be made by using the execute method.

Construction of Retrofit Object

Retrofit is built in Builder mode and can be configured in many ways, including baseUrl, okhttpClient, converterFactory, callAdapterFactory, and so on.

Nothing special here. It's all simple assignments, so let's just see what parameters Retrofit passed in at the end.It finally invokes the constructor below to initialize the parameters.

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

Creation of Service Objects

Dynamic Proxy Create Service Proxy Class

Then we see how our own defined interfacet can achieve instance acquisition simply by passing class es to Retrofit.create, which is obviously just an interface?

  public <T> T create(final Class<T> service) {
     // Detect the interface of the Service
    validateServiceInterface(service);
    // Building proxy objects through dynamic proxies
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { 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 {
            // Object class method calls as usual
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            // Call as usual if a method exists for the class of the corresponding platform itself
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            // Otherwise, get the corresponding Method and invoke through the loadServiceMethod method
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
            }
         });
  }

As you can see, the acquisition of Service objects is actually done through a dynamic proxy.First, the interface is detected by the validateServiceInterface method, then it is proxied by the dynamic proxy.

For methods that are unique to the Object class itself and that exist in the corresponding platform itself, invoke the method as usual. Otherwise, the corresponding Method object in the Service is processed by the loadServiceMethod, and then the invoke method is called on it.

This illustrates that Retrofit does not annotate all methods in a Service interface immediately when creating its corresponding object, but instead uses the lazy load idea of annotating when a method is called.

Next, let's look at the validateServiceInterface method:

  private void validateServiceInterface(Class<?> service) {
      // Determine if it is an interface
    if (!service.isInterface()) {
      throw new IllegalArgumentException("API declarations must be interfaces.");
    }
    // Determines if the interface and all interfaces it inherits contain a norm parameter and throws an exception if it does
    Deque<Class<?>> check = new ArrayDeque<>(1);
    check.add(service);
    while (!check.isEmpty()) {
      Class<?> candidate = check.removeFirst();
      if (candidate.getTypeParameters().length != 0) {
        StringBuilder message = new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if (candidate != service) {
          message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
   }
   // If the Retrofit method was set very urgently when it was created, the non-platform-specific, non-static method was handled by the load Service Method method.
   if (validateEagerly) {
     Platform platform = Platform.get();
     for (Method method : service.getDeclaredMethods()) {
       if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
         loadServiceMethod(method);
       }
     }
   }
  }

First, this method detects a service, ensuring that it is an interface and that it and the classes it inherits do not have a generic parameter.

Later, if validateEagerly is set to true when Retrofit is created, all non-platform-specific and non-static methods in the Service will be handled ahead of time by the loadServiceMethod method.

Resolution of Methods in Service

So let's see what loadServiceMethod does:
  ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

First it will try to get the ServiceMethod object from the serviceMethodCache cache using Double Check. If it is not, it will use the ServiceMethod.parseAnnotations method to process the annotations to the Method and add the resulting ServiceMethod object to the cache.

That is, to avoid processing method annotations multiple times, Retrofit uses a serviceMethodCache to cache the parsed ServiceMethod.

Next, let's see how the parseAnnotations method parses the method's annotations.

  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(method,
         "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
     }
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
   }

This first parses the annotation using the RequestFactory.parseAnnotations method and obtains a RequestFactory object.

The requestFactory is then passed in via the HttpServiceMethod.parseAnnotations method to proceed with the annotation resolution and obtain the ServiceMethod object

Annotation resolution

Let's first look at RequestFactory.parseAnnotations:

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

It passes Method into Builder and builds a new RequestFactory:

  Builder(Retrofit retrofit, Method method) {
    this.retrofit = retrofit;
    this.method = method;
    this.methodAnnotations = method.getAnnotations();
    this.parameterTypes = method.getGenericParameterTypes();
    this.parameterAnnotationsArray = method.getParameterAnnotations();
  }

The comments included in the method, the paradigms included in the parameters, and the comments for the parameters are obtained through reflection in Builder.

Next, look at the build method:

  RequestFactory build() {
    for (Annotation annotation : methodAnnotations) {
        // Traversal method annotations parse each annotation
      parseMethodAnnotation(annotation);
    }
    // ...exception handling

   // Parsing parameters
    int parameterCount = parameterAnnotationsArray.length;
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
      parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
    }

    // ...exception handling
   return new RequestFactory(this);
  }

In the build method, parseMethodAnnotation is used to parse each comment of the method, and the parseParamter method is called for each parameter to parse into a ParamterHandler object.

The code for parseMethodAnnotation is as follows:

  private void parseMethodAnnotation(Annotation annotation) {
    if (annotation instanceof DELETE) {
      parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
    } else if (annotation instanceof GET) {
      parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
    } else if (annotation instanceof HEAD) {
      parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    } else if (annotation instanceof PATCH) {
      parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
    } else if (annotation instanceof POST) {
      parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
    } else if (annotation instanceof PUT) {
      parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
    } else if (annotation instanceof OPTIONS) {
      parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
    } else if (annotation instanceof HTTP) {
      HTTP http = (HTTP) annotation;
      parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
    } else if (annotation instanceof retrofit2.http.Headers) {
      String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
      if (headersToParse.length == 0) {
        throw methodError(method, "@Headers annotation is empty.");
      }
      headers = parseHeaders(headersToParse);
    } else if (annotation instanceof Multipart) {
      if (isFormEncoded) {
        throw methodError(method, "Only one encoding annotation is allowed.");
     }
     isMultipart = true;
    } else if (annotation instanceof FormUrlEncoded) {
      if (isMultipart) {
        throw methodError(method, "Only one encoding annotation is allowed.");
     }
     isFormEncoded = true;
   }
  }

This is essentially to support each HTTP supported type, get the url in the corresponding comment, call parseHttpMethodAndPath to process it, and process the Headers comment through parseHeaders.

Handling of Http requests and Path

For Method and Path, parameters are assigned through parseHttpMethodAndPath:

  private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
    if (this.httpMethod != null) {
      throw methodError(method, "Only one HTTP method is allowed. Found: %s and %s.",
          this.httpMethod, httpMethod);
    }
    this.httpMethod = httpMethod;
    this.hasBody = hasBody;
    if (value.isEmpty()) {
      return;
    }
    // Get the relative URL path and existing query string, if present.
    int question = value.indexOf('?');
    if (question != -1 && question < value.length() - 1) {
      // Ensure the query string does not have any named parameters.
      String queryParams = value.substring(question + 1);
      Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
      if (queryParamMatcher.find()) {
        throw methodError(method, "URL query string \"%s\" must not have replace block. "+ "For dynamic query parameters use @Query.", queryParams);
      }
    }
    this.relativeUrl = value;
    this.relativeUrlParamNames = parsePathParameters(value);
  }

This essentially assigns values to different HTTP request modes and Path, while guaranteeing that there are no parameters in Path for this interface through regular expressions.

Handling of Headers

  private Headers parseHeaders(String[] headers) {
    Headers.Builder builder = new Headers.Builder();
    for (String header : headers) {
      int colon = header.indexOf(':');
      if (colon == -1 || colon == 0 || colon == header.length() - 1) {
        throw methodError(method,
            "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
      }
      String headerName = header.substring(0, colon);
      String headerValue = header.substring(colon + 1).trim();
      if ("Content-Type".equalsIgnoreCase(headerName)) {
        try {
          contentType = MediaType.get(headerValue);
        } catch (IllegalArgumentException e) {
          throw methodError(method, e, "Malformed content type: %s", headerValue);
        }
      } else {
        builder.add(headerName, headerValue);
      }
    }
    return builder.build();
  }

For Headers, the list of Headers passed in is resolved to the corresponding Headers object.

Processing method parameters
Finally, let's look at how the method parameters are handled:
  private @Nullable ParameterHandler<?> parseParameter(
      int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
    ParameterHandler<?> result = null;
    if (annotations != null) {
      for (Annotation annotation : annotations) {
          // Parse each comment through parseParameter Annotation
        ParameterHandler<?> annotationAction =
            parseParameterAnnotation(p, parameterType, annotations, annotation);
        if (annotationAction == null) {
          continue;
        }
        if (result != null) {
          throw parameterError(method, p,
              "Multiple Retrofit annotations found, only one allowed.");
        }
        result = annotationAction;
      }
    }
    if (result == null) {
        // Special handling in the case of a protocol
      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;
  }

The parseParamterAnnotation method is too long to be pasted here. It handles each comment on the method uniquely and returns the corresponding ParamterHandler.

It can be found that the main function of RequestFactory.parseAnnotations is to complete the parsing of method annotation information for generating corresponding Requests.

Creation of ServiceMethod

Then let's look at HttpServiceMethod.parseAnnotations:
  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) {
        // If the method is suspend in kotlin
      Type[] parameterTypes = method.getGenericParameterTypes();
      // Gets the Continuation's canonical parameter, which is the return value type of the suspend method
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      // If the Continuation's canonical parameter is Response, then it requires Response, then set the Continuation WantsResponse to true;
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
          // TODO figure out if type is nullable or not
         // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
       // Determine if return type is nullable or not
      }
      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
        // Otherwise, get the normal parameter of the method return value, which is the type of return value required by the request
      adapterType = method.getGenericReturnType();
    }
    // Create CallAdapter object through createCallAdapter method
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    Type responseType = callAdapter.responseType();
    if (responseType == okhttp3.Response.class) {
      throw methodError(method, "'"
          + getRawType(responseType).getName()
          + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    if (responseType == Response.class) {
      throw methodError(method, "Response must include generic type (e.g., Response<String>)");
    }
    // TODO support Unit for Kotlin?
    if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
      throw methodError(method, "HEAD method must use Void as response type.");
    }
    // Create Converter object by createResponseConverter method
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
        // Create and return a CallAdapted object directly if it is not a suspend method
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
          callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
          continuationBodyNullable);
    }
  }

The code here is very long and can be roughly summarized in the following steps:

1. If this method is the suspend method in Kotlin, since it is implemented by a coprocess, you need to obtain the Continuation's canonical parameter, which is the real type of the requested return value.
2. If the suspend method returns a Response, that means it needs a Response, not a specific type, then set the continuationWantsResponse to true;
3. If it is not the suspend method, the type of the return value's canonical parameter is the real type of the requested return value (Call< ReturnType> then ReturnType is the type that is really needed after the conversion).
4. Create a CallAdapter object through the createCallAdapter method that adapts Call< ResponseT> objects to the type of ReturnT object required.
5. Once you get the CallAdapter, you get the type of Response and verify it.
6. Get the Converter object through the createResponseConverter method, which can complete the conversion from ResponseBody to ResponseT of type Response.
7. If it is not Kotlin's suspend method, the CallAdapter and Converter are passed in directly to create the CallAdapted object.
8. Otherwise, the SuspendForResponse and SuspendForBody objects are returned, depending on whether the suspend method requires a Response or a specific type.

You can see that Kotlin's protocol is supported by a new version of Retorofit.The main function of HttpServiceMethod.parseAnnotations is to create CallAdapter and Converter objects and build corresponding HttpServiceMethod.

CallAdapter

CallAdapter is used to adapt Call< R> objects to the required type of T object.It is declared as follows:

  public interface CallAdapter<R, T> {

      // Returns the type of Response
    Type responseType();
       // Convert Call<R>to T type 
    T adapt(Call<R> call);
  }

Let's first look at how the createCallAdapter method creates it:

  private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
      Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
    try {
      //noinspection unchecked
      return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
      throw methodError(method, e, "Unable to create call adapter for %s", returnType);
    }
  }

It calls the retrofit.callAdapter method:

  public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }

The retrofit.nextCallAdapter method is then called:

  public CallAdapter<?, ?> 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 (int i = start, count = callAdapterFactories.size(); i < count; i++) {
         // Traverse the callAdapterFactories and try to create the CallAdapter
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }
    // ... no corresponding CallAdapterFactory exists, throwing an exception
  }

This is actually an attempt to create a CallAdapter by traversing the allAdapter.Factory list passed in when the Retrofit object was created.If none of these CallAdapter.Factories can handle this corresponding returnType and annotations, an exception will be thrown.(The preceding Factory has a higher priority)

Retrofit has a default allAdapter factory, Default CallAdapter Factory, which has a lower priority than all custom factories. When it is created, it passes in an Executor, and we can see its get method:

  @Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
    final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null: callbackExecutor;
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }
      @Override public Call<Object> adapt(Call<Object> call) {
        return executor == null
            ? call: new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

You can see that when there is no Executor, it does not modify Call, and when there is an Executor specified, it is wrapped as Executor CallbackCall.Generally, this Executor is the callbackExecutor specified when Retrofit is created.

This callbackExecutor is actually used to specify the thread that calls the Callback, so that the Callback is not necessarily called back on the main thread:

  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");
      // Callback Executor wraps the allback and calls it back
      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));
        }
      });
    }
    // ...
  }

As you can see, this is actually just a case of Callback Executor wrapped to support callbackExecutor by calling back the passed Executor.

Converter

Next, let's look at the Converter class, which is an interface for converting data of type F to type T:

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

Next let's see how the createResponseConverter created it:

  private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
      Retrofit retrofit, Method method, Type responseType) {
    Annotation[] annotations = method.getAnnotations();
    try {
      return retrofit.responseBodyConverter(responseType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
      throw methodError(method, e, "Unable to create converter for %s", responseType);
    }
  }

To retrofit.responseBodyConverter:

  public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

Transferred to nextResponseBodyConverter:

  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 (int i = 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;
      }
    }
    // No corresponding ConverterFactory was found to process, throwing an exception
  }

You can see here that, similar to the allAdapter factory, you iterate through the Converter.Factory list that was passed in when you created Retrofit and try to create it, throwing an exception if no factory can handle it.(The preceding Factory has a higher priority)

Retrofit has two built-in Converter.Factor, BuiltInConverters and OptionalConverterFactory.

BuiltInConverters have a higher priority than all custom factories in order to avoid overwriting it by other factories, while OptionalConverterFactory has a lower priority than all custom factories.

Several converters are implemented in BuiltInConverters, such as converting ResponseBody to Void or Unit, Object to String, and so on.

OptionalConverterFactory is a defaultConverterFactories acquired through the platform and implemented to support Optional for Java 8, a class introduced by Java 8 to resolve null pointer exceptions.

ServiceMethod

Next, let's look at the ServiceMethod class we created earlier, which is an abstract class that requires subclasses to implement the invoke method.

  abstract class ServiceMethod<T> {
    abstract @Nullable T invoke(Object[] args);
  }

Its subclass is the HTTP ServiceMethod mentioned earlier

HttpServiceMethod
  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);
  }

The invoke method of HttpServiceMethod is very simple. It constructs an OkHttpCall, and then converts the Call through the virtual function adapt.Its subclasses only need to implement adapt to convert alls.

It has three subclasses, the AllAdapted class without using a protocol and the SuspendForResponse and SuspendForBody classes with a protocol.

CallAdapted

The CallAdapted class inherits from the HttpServiceMethod class and converts Call through the CallAdapter passed in.

  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) {
      return callAdapter.adapt(call);
    }
  }

SuspendForResponse

The SuspendForResponse class first constructs a Response< ResponseT> parameter from the incoming Call and then encapsulates the call's enqueue asynchronous callback procedure as a suspend function using the awaitResponse method implemented by Kotlin.

  static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    SuspendForResponse(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, Call<ResponseT>> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);
      //noinspection unchecked Checked by reflection inside RequestFactory.
      Continuation<Response<ResponseT>> continuation =
          (Continuation<Response<ResponseT>>) args[args.length - 1];
      // See SuspendForBody for explanation about this try/catch.
      try {
        return KotlinExtensions.awaitResponse(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
    }
  }

The awaitResponse method is as follows:

  suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
    return suspendCancellableCoroutine { continuation ->
      continuation.invokeOnCancellation {
        cancel()
      }
      enqueue(object : Callback<T> {
        override fun onResponse(call: Call<T>, response: Response<T>) {
          continuation.resume(response)
        }
        override fun onFailure(call: Call<T>, t: Throwable) {
          continuation.resumeWithException(t)
        }
      })
    }
  }

You can see that the collaboration is supported by calling continuation.resume and continuation.resumeWithException in onResponse and onFailure, respectively.

SuspendForBody

SuspendForBody, on the other hand, constructs a Continuation< ResponseT&gt based on the incoming Call; the object then encapsulates the call's enqueue asynchronous callback process as a suspend function using the await or awaitNullable methods implemented by 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];
      // Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes
      // invoke the supplied callback with an exception before the invoking stack frame can return.
      // Coroutines will intercept the subsequent invocation of the Continuation and throw the
      // exception synchronously. A Java Proxy cannot throw checked exceptions without them being
      // in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will
      // force suspension to occur so that it can be instead delivered to the continuation to
      // bypass this restriction.
      try {
        return isNullable
            ? KotlinExtensions.awaitNullable(call, continuation)
            : KotlinExtensions.await(call, continuation);
      } catch (Exception e) {
        return KotlinExtensions.suspendAndThrow(e, continuation);
      }
    }
  }

Call

Call is actually an interface that provides execute, enqueue, cancel, and so on, to fulfill a request. When we need to request an interface, we just need to call its enqueue or execute method.

  public interface Call<T> extends Cloneable {

    Response<T> execute() throws IOException;

    void enqueue(Callback<T> callback);

    boolean isExecuted();

    void cancel();

    boolean isCanceled();

    Call<T> clone();

    Request request();
  }

From the previous process, we can see that if we don't pass in CalAdapter, the Call returned by default is actually an OkHttpCall object, so let's see how Retrofit implements network requests based on OkHttp:

enqueue

First let's look at the enqueue code:

  @Override public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");
    okhttp3.Call call;
    Throwable failure;
    // Lock, set the state, and create okhttp3.Call through the createRawCall method
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;
      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }
    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }
    // If the external cancels the task, call okhttp3.Call.cancel
    if (canceled) {
      call.cancel();
    }
    // Queue messages through okhttp3.Call.enqueue
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
          try {
            // Parse through parseResponse after getting results
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          throwIfFatal(e);
          callFailure(e);
          return;
        }
        try {
            // Call back via onResponse after parsing is complete
          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) {
          // Request Failure Call Failure Callback Failed Request
        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
        }
      }
    });
  }

The code for enqueue looks like a lot of code, but it's actually simpler and consists of the following steps:

1. Lock, set the execution state, and call the createRawCall method to create the okhttp3.Call object if no rawCall exists.
2. Call okhttp3.Call.cancel if the task is cancelled by the outside world
3. Queue messages through okhttp3.Call.enqueue
4. If a Response is obtained, parseResponse method is used to parse the Response, and after parsing, onResponse callback is used to parse the result.
5. If the request fails, the onFailure callback request through the callFailure method fails.

A small detail can be found that Retrofit reuses okhttp3.Call that has already been created, avoiding duplicate creation and wasting efficiency.

execute

Next let's see how execute works:

  @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;
      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else if (creationFailure instanceof RuntimeException) {
          throw (RuntimeException) creationFailure;
        } else {
          throw (Error) creationFailure;
        }
      }
      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
          throwIfFatal(e); //  Do not assign a fatal error to creationFailure.creationFailure = e;throw e;
        }
      }
    }
    if (canceled) {
      call.cancel();
    }
    return parseResponse(call.execute());
  }

It's also very simple:

1. Set the execution state after locking first, or call the createRawCall method to create okhttp3.Call object if there is no rawCall.
2. If the outside world cancels the task, call okhttp3.Call.cancel.
3. If a Response is obtained, parseResponse is used to parse the Response and return it

Creation of okhttp3.Call

Next let's look at the createRawCall method:

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

It is actually created by calling the newCall method of callFactory, while the incoming okhttp3.Request was created by requestFactory.create:

  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, new Invocation(method, argumentList)) .build();
  }

A RequestBuilder is built here, and then parameters are applied to RequestBuilder by traversing the ParamterHandler list and calling its apply method.

parseResponse

Next let's see how parseResponse parses Response:

  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();
    // Remove the body's source (the only stateful object) so we can pass the response along.
    rawResponse = rawResponse.newBuilder().body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build();
    // ...
    ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
    try {
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.catchingBody.throwIfCaught();throw e;
    }
  }

As you can see, it converts the body of the Response to the type we need through the Converter.convert method.

summary

So here, we have basically finished the source code of Retrofit, and we can see that its code rarely involves the processing of network requests, basically is the encapsulation of OkHttp, which provides a more convenient framework on the existing API of OkHttp.

Retrofit is a good example of how to encapsulate existing frameworks in the most elegant way and expand more powerful functionality on a set of fixed API s.

Retrofit's annotations are runtime annotations that create Service objects through dynamic proxies and resolve annotations through reflection, which can lead to a loss of performance that leads to very easy-to-use API s.There is one interface for each ServiceMethod written by the user.

For the creation of Service, the method defined in each interface is proxied by dynamic proxy. If validateEagerly is set, annotation resolution and ServiceMethod creation will be done when the Service interface object is created. Otherwise, the corresponding ServiceMethod object will be created when the method is invoked. In case of multiple invocations, it passes through serviceMethodCache caches the resolved ServiceMethods to avoid performance degradation due to repeated parsing.

This object is first created by RequestFactory.parseAnnotations which parses the annotations in the method:

  • For method request mode annotations, it retrieves and saves information such as how the annotation is requested and the url in the annotation through the parseHttpMethodAndPath method, which is set into RequestBuilder when the request is created.
  • For a method's Headers annotation, it parses the Header annotation into a Headers object and saves it, which is set into RequestBuilder when the request is created.
  • For method parameters, it parses each parameter into its corresponding ParamterHandler based on the specific comment (@Query, etc.), and submits the parameter to RequestBuilder through its apply method when the request is created.

This object is then created through Http ServiceMethod.parseAnnotations with the CallAdapter and Converter corresponding to the interface.

CallAdapter is used to adapt Call< R> objects to the required type of T object, that is, to convert Call.

Converter is used to convert F-type data to T, and is often used to convert the body of a Response.

For both CallAdapter and Converter, they are created through their respective factory classes, which are created by trying backward and forward according to the order of the factory list, i.e. the factories that are higher in the factory list have a higher priority.

Retrofit also introduces support for the Continuation protocol, which ultimately wraps ServerMethod as a suspend method to support the protocol.

Retrofit's network request execution relies on OkHttp, which first constructs the Request through RequestFactory whose parameters are derived from the previously parsed information.This Request is then wrapped as an okhttp3.Call, which calls its corresponding execute and enqueue methods on synchronous and asynchronous requests, respectively.Also, to avoid duplicate creation of okhttp3.Call, it duplicates the previously created okhttp3.Call.

(Leave a GitHub link by hand, and you can find it yourself if you need access to relevant interviews, etc.)
https://github.com/xiangjiana/Android-MS
(VX: mm14525201314)

Tags: Android Retrofit OkHttp network github

Posted on Tue, 07 Jan 2020 10:33:13 -0800 by fox_m