Retrofit source reading

Keywords: Mobile Retrofit network Java jvm

1. A common network request process in development

url, parameter -- > request -- > Convert to Http protocol -- > request execution -- > return result -- > Convert to response -- > response convert to our object

Only the head and tail are customized by our daily development, and other processes in the middle are carried out by the network framework.

2. What retrofit does

  • 1.url, parameter Retrofit mainly adopts interface + annotation mode
  • 2. The intermediate network request framework uses dynamic agent to use a certain framework
  • 3. The response is converted to our object and adapts in adapter mode

3. Implementation process of retrofit

1) to request, url and parameter Retrofit mainly adopt interface + annotation mode

Let's take a simple POST request as an example

@POST("ssoService/v1/logoutCTGT")
Call<BaseResponseBean> logout(@Body LogoutRequest logoutRequest);

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
  String value() default "";
}

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Body {
}

Analysis of annotation knowledge points

  • Definition of annotation

    • @Documented

    The Documented annotation indicates that the annotation is recorded by javadoc, and there are similar recording tools by default. If a type declaration is Documented with comments, its comments become part of the public API.

    • @Target(METHOD)

    Comments may appear in the syntax of Java programs, such as TYPE, FIELD, METHOD, PARAMETER, etc

    • @Retention(RUNTIME)

    Describes the various policies for retaining comments, source (source phase, ignored during compilation), CLASS (compiled into CLASS file, but not in JVM), RUNTIME (all phases exist)

  • Get annotation (ServiceMethod.Builder class)

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

How to get annotations

this.methodAnnotations = method.getAnnotations();
this.parameterAnnotationsArray = method.getParameterAnnotations();

Annotation acquisition is relatively simple. The Method method can obtain various annotations directly, one is the annotation on the Method, the other is the annotation on the parameter

  • Retrofit processing of the acquired annotation (ServiceMethod.Builder.build())
 for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      
......

 int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
          throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
              parameterType);
        }

        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }

After annotation processing, it will be converted to related attributes

  private final String httpMethod;
  private final String relativeUrl;
  private final Headers headers;
  private final MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private final ParameterHandler<?>[] parameterHandlers;

Then convert to Request in ServiceMethod.toRequest method

  Request toRequest(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
  }

The above analysis comments to toRequest, and then we analyze the calling process of the code

Synchronization request process

OkHttpCall.execute()->OkHttpCall.createRawCall()->serviceMethod.toRequest()

Asynchronous process

OkHttpCall.enqueue()->OkHttpCall.createRawCall()->serviceMethod.toRequest()

2)toResponse

It is convenient for us to reverse how to use dynamic agent to call the network framework by using the priority resolution to response

Compared with toRequest, toResponse is much simpler. In our daily work, we often use Gson to transform the results. We analyze the toResponse through the addition of converter and the call process of transformation

  • Let's start with when we added the method of transforming into our own object
 public static <T> T createJsonApi(@Url String baseUrl, Class<T> service) {
        if (!checkUrl(retrofitRxJson, baseUrl)) {
            retrofitRxJson = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(okHttpClient)
                    .build();
        }
        return retrofitRxJson.create(service);
    }
                    .addConverterFactory(GsonConverterFactory.create())

Using Gson to transform the content returned by the platform into our objects

public class BaseResponseBean<T> {
    public Integer type;
    public String code;
    public String msg;
    public T data;
}
  • To response the whole calling process
  • Synchronous request

OkHttpCall.excute()-> OkHttpCall.parseResponse()->serviceMethod.toResponse()->serviceMethod.responseConverter.convert()

  • Asynchronous request

OkHttpCall.enqueue()-> hik.lib.okhttp3.Callback.onResponse()->OkHttpCall.parseResponse()->serviceMethod.toResponse()->serviceMethod.responseConverter.convert()

  • Initialization of responseConverter

ServiceMehtond.Builder.build()->ServiceMehtond.Builder.createResponseConverter()->retrofit.responseBodyConverter()-> retrofit.nextResponseBodyConverter()

< U > here is a better design. See the code < / u >

 public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    checkNotNull(type, "type == null");
    checkNotNull(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;
      }
    }

    StringBuilder builder = new StringBuilder("Could not locate ResponseBody converter for ")
        .append(type)
        .append(".\n");
    if (skipPast != null) {
      builder.append("  Skipped:");
      for (int i = 0; i < start; i++) {
        builder.append("\n   * ").append(converterFactories.get(i).getClass().getName());
      }
      builder.append('\n');
    }
    builder.append("  Tried:");
    for (int i = start, count = converterFactories.size(); i < count; i++) {
      builder.append("\n   * ").append(converterFactories.get(i).getClass().getName());
    }
    throw new IllegalArgumentException(builder.toString());
  }

< font color = "hotpink" > < U > if there are multiple types of return conversion, they will be matched according to the return type of the interface < / u > < font >

3) use of network framework by dynamic agent

We analyzed the process of calling two methods: toRequest() and toResponse(), as well as the network request from OkHttpCall. Next, we analyzed how the dynamic agent calls the execute() and enqueue() in OkHttpCall

When we use it, we define the interface, but we do not define the implementation class. Here is the implementation class of the interface using the dynamic proxy method

##Here's something to pay attention to******************* The < U > interface returns an object that encapsulates the network request, but not the network request

  • Creation of dynamic agent
 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public 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) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

In terms of code, dynamic proxy is relatively simple.

Analysis of dynamic agent knowledge points

dynamic proxy By using Java reflection technology, a new class (also known as "dynamic proxy class") and its instances (objects) are created at runtime to implement some given interfaces. The proxy is an interface, not a class or an abstract class. The specific implementation is only known at runtime, which is the principle of spring aop.           

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)throws IllegalArgumentException
                                          

newProxyInstance, the method has three parameters:

Loader: which class loader to use to load the proxy object

Interfaces: interfaces that need to be implemented by dynamic proxy classes

h: when the dynamic proxy method executes, it will call the invoke method in h to execute

< U > in combination with our knowledge of Retrofit.create method and dynamic agent, that is, all methods in our interface are finally called < / u >

serviceMethod.callAdapter.adapt(okHttpCall);

An object wrapped in OkHttpCall is returned here

Here is another knowledge point

Retrofit

  final hik.lib.okhttp3.Call.Factory callFactory;
  final HttpUrl baseUrl;
  final List<Converter.Factory> converterFactories;
  final List<CallAdapter.Factory> adapterFactories;
  final @Nullable Executor callbackExecutor;
  final boolean validateEagerly;
  

Whether the object returned by adapterFactories is RxJava2CallAdapterFactory or the default calladapterfactory

Let's take a look at the initialization of the callAdapter property in serviceMethod

ServiceMethod.Builder.buid()->ServiceMethod.Builder.createCallAdapter->retrofit.callAdapter->retrofit.nextCallAdapter

Upper code

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

    int start = adapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }

    StringBuilder builder = new StringBuilder("Could not locate call adapter for ")
        .append(returnType)
        .append(".\n");
    if (skipPast != null) {
      builder.append("  Skipped:");
      for (int i = 0; i < start; i++) {
        builder.append("\n   * ").append(adapterFactories.get(i).getClass().getName());
      }
      builder.append('\n');
    }
    builder.append("  Tried:");
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
      builder.append("\n   * ").append(adapterFactories.get(i).getClass().getName());
    }
    throw new IllegalArgumentException(builder.toString());
  }

The last step is to match the adapterfactory according to the return type of the interface

  • Next, let's look at the implementation of AdaperFactory

Let's first look at the implementation of a default calladapterfactory

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @Override
  public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }

    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        return call;
      }
    };
  }
}

The direct return is OkHttpCall, so the network request is execute, enqueue.

If it is RxJava2CallAdapter, it will return Observable, and then call the network request through RXJava method

  public static void asyHttpRequest(Observable observable , BaseNetCallback netCallback){
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
        observable.subscribe(new BaseNetObserver<>(netCallback));
    }
   

Set the execution thread, listen to the result thread, and then initiate signal execution

We're looking at the code in the signal execution

  @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);
    call.enqueue(callback);
  }
  

By now, we have basically understood the whole process of Retrofit2

####4. Code summary of retrofit The main code implementation of Retrofit

  • OKHttpCall is mainly where the network request is actually implemented, mainly including

    • execute
    • enqueue
    • createRawCall
  • ServiceMethod

    • toRequest
    • toResponse
    • Builder.build()
  • Retrofit

    • create
    • nextCallAdapter
    • nextResponseBodyConverter

Posted by hankster on Mon, 16 Dec 2019 04:55:18 -0800