Abstract

Take a look at the Retrofit source code to see how this simple web library can improve our productivity. Through dynamic proxy technology, we create our interface object to provide us with the interface function to call. By parsing the functions defined by the interface, assemble the data required by the OkHttp network request and initiate the network request. We can customize request adapters and response converters to suit our needs.

Process analysis

A dynamic proxy

We define the service interface and create an object of type Service using the create function of the Retrofit object. From this object, we can invoke the function defined on the service interface to initiate the defined network request.

val service=retrofit.create(service::class.java)
Copy the code

Retrofit uses dynamic proxy technology to generate our defined Service interface proxy object, so all calls to the function of the proxy object are forwarded to the Invoke function of the InvocationHandler object.

  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 {
                // The default method for determining whether Obect is true
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args); } args = args ! =null ? args : emptyArgs;
                // A way to determine whether it is platform-specific, such as Android
                returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

loadServiceMethod

At Invoke, we only care about the loadServiceMethod(method).invoke(args) function. The loadServiceMethod(method) function returns a ServiceMethod object. ServiceMethod is an abstract class. The only direct implementation class is HttpServiceMethod. Httpservicemethod. invoke(args) is called.

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;
  }
Copy the code

The loadServiceMethod function first obtains the ServiceMethod object from the cache serviceMethodCache. If the ServiceMethod object is not obtained from the cache, By ServiceMethod. ParseAnnotations parsing ServiceMethod object, and put it in the cache. ServiceMethodCache is a ConcurrentHashMap with Method as Key and ServiceMethod as value. ServiceMethod. ParseAnnotations (this, method) is mainly analytical method object (method) we define annotations, Parameters and annotations as well as the return type, and then through HttpServiceMethod. ParseAnnotations (retrofit method, requestFactory) generated ServerMethod object.

ServiceMethod.parseAnnotations

This function calls the RequestFactory class and the parseAnnotations function of HttpServiceMethod, respectively. The RequestFactory class encapsulates most of the information about the web request, such as method, headers,httpMethod, and so on. HttpServiceMethod is a subclass of ServerMethod that is used to create request adapters and data converters.

  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    // Parse method annotations and parameter annotations and values
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
 	// Parse method annotations and parameter annotations and return values
    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);
  }
Copy the code

RequestFactory.parseAnnotations

RequestFactory. ParseAnnotations (retrofit, method) by building create RequestFactory object model. The process of building is to parse the information on the method object passed in and assign it to the properties of the RequestFactory object. The Method object represents a function on reflection and contains information about the function, such as annotations, parameters, return type, and so on.

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

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

RequestFactory build(a) {
  //1
  for(Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); }...int parameterCount = parameterAnnotationsArray.length;
  parameterHandlers = newParameterHandler<? >[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
      // Parse function parameters, including their annotations and valuesparameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); }...return new RequestFactory(this);
}
Copy the code

The build function mainly parses the annotation on the method, the parameter annotation. The parseMethodAnnotation(annotation) is used to parse the annotation of the method, and then set the properties based on the type of the anotation, such as GET, POST, and so on. For example, a GET request has no request body, hasBody=false, and a POST request has hasBody=true.

ParseParameter parses the function parameter annotation and gets the corresponding value. The parsing of parameter annotations is similar to the parsing of function annotations, mainly checking that we are using them correctly and setting the related properties. It also determines whether the current function is a suspended function in the Kotlin coroutine (we’ll see later).

HttpServiceMethod.parseAnnotations

HttpServiceMethod. ParseAnnotations (retrofit method, requestFactory) is dry? Parse the generated RequestFactory above, generate the request adapter CallAdapter and response Converter, and then subclass the three into HttpServiceMethod and return.

  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) {// Whether coroutines are supported
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      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 {
      adapterType = method.getGenericReturnType(); // Get the return type of the method
    }
	// Iterate through the Retrofit CallAdapter list to see if there are any that fit the adapterTypeCallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); .// Iterate through the list of Retrofit converters to find the right one
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if(! isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);// Subclass of HtttpServerMethod for non-coroutine cases
    } else if (continuationWantsResponse) {
      // Coroutines are used and the return type is Response
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      // Coroutines are used and the return type is ResponseBody
      return (HttpServiceMethod<ResponseT, ReturnT>)
          newSuspendForBody<>( requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code

After creating CallAdatper and Converter, return different HttpServiceMethod subclasses depending on whether they are coroutines or not.

createCallAdapter

CreateCallAdapter (Retrofit, Method, adapterType, annotations); Create the CallAdapted object.

  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); }}Copy the code

Call retrofit.calladapter (returnType, annotations) and select the appropriate request adapter based on the returnType retrunType. It is important to note that we are traversing from scratch, which means that if the previous adapter meets the criteria, it is preferred.

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

  publicCallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
	......
    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; }}... }Copy the code

The nextCallAdapter function iterates through the CallAdapter that Retrofit is stored in the list of callAdapterFactories, using their get function to see if they match. If the Android SDK_INT > = 24 returns CompletableFutureCallAdapterFactory, otherwise returns DefaultCallAdapterFactory.

Look at the get function CompletableFutureCallAdapterFactory object.

 @Override
  public @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) {if(getRawType(returnType) ! = CompletableFuture.class) {return null;
    }
    if(! (returnTypeinstanceof ParameterizedType)) {
      throw new IllegalStateException(
          "CompletableFuture return type must be parameterized"
              + " as CompletableFuture<Foo> or CompletableFuture<? extends Foo>");
    }
    Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);

    if(getRawType(innerType) ! = Response.class) {// Generic type is not Response<T>. Use it for body-only adapter.
      return new BodyCallAdapter<>(innerType);
    }

    // Generic type is Response<T>. Extract T and create the Response version of the adapter.
    if(! (innerTypeinstanceof ParameterizedType)) {
      throw new IllegalStateException(
          "Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>");
    }
    Type responseType = getParameterUpperBound(0, (ParameterizedType) innerType);
    return new ResponseCallAdapter<>(responseType);
  }
Copy the code

CompletableFutureCallAdapterFactory get function according to the function return type and whether he have to deal with the return type of consistent, inconsistent it returns null, said don’t processing, for example here is CompletableFuture. The return value is then further checked for validity, such as whether it is a generic object. Finally, a subclass of CallAdapter, BodyCallAdapter or ResponseCallAdapter, is returned based on the outermost type of the generic object. The main difference is the adapt function.

//BodyCallAdapter   
@Override
public CompletableFuture<R> adapt(final Call<R> call) {
  CompletableFuture<R> future = new CallCancelCompletableFuture<>(call);
  call.enqueue(new BodyCallback(future));
  return future;
}
//ResponseCallAdapter
public CompletableFuture<Response<R>> adapt(final Call<R> call) {
  CompletableFuture<Response<R>> future = new CallCancelCompletableFuture<>(call);
  call.enqueue(new ResponseCallback(future));
  return future;
}
Copy the code

loadServiceMethod(method).invoke(args)

Go back to loadServiceMethod(method).invoke(args), that is, the invoke function of the CallAdapter object is actually called.

@Override
final @Nullable ReturnT invoke(Object[] args) {
	Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, 					responseConverter);
	return adapt(call, args);
}
Copy the code

The Invoke function builds the OkHttpCall and calls the adapt function. Here we analyze the adapt function of the ResponseCallAdapter.

@Override
public CompletableFuture<Response<R>> adapt(final Call<R> call) {
  CompletableFuture<Response<R>> future = new CallCancelCompletableFuture<>(call);
  call.enqueue(new ResponseCallback(future));
  return future;
}
Copy the code

Network request

Adapt calls the enqueue function of OkHttpCall. Is it similar to the asynchronous request of OKHttp?

  @Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    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 (canceled) {
      call.cancel();
    }

    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);// Parse the raw data and call the transformation adapter
            } 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

A simple analysis of enqueue is to create an OkHttp Call object and Call enqueue. The network request is then made and parseResponse is called to parse the response data after the request succeeds.

 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();

    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally{ rawBody.close(); }}if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }

    ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
    try {
      T body = responseConverter.convert(catchingBody); // Convert the response data
      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

createResponseConverter

The last area of focus is in the function call T body = responseConverter. Convert (catchingBody); Convert the raw data into the type of data we need. CreateResponseConverter creates the response data converter in the parseAnnotations function of HttpServiceMethod.

  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); }}public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

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

The response converter is created much like the request adapter. When the Retrofit object is created, the associated converter is saved to the list of converterFactories, and the responseBodyConverter function determines whether the response data is processed. So let’s look at OptionalConverterFactory.

final class OptionalConverterFactory extends Converter.Factory {
  static final Converter.Factory INSTANCE = new OptionalConverterFactory();

  @Override
  public @NullableConverter<ResponseBody, ? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) {if(getRawType(type) ! = Optional.class) {return null;
    }

    Type innerType = getParameterUpperBound(0, (ParameterizedType) type);
    Converter<ResponseBody, Object> delegate =
        retrofit.responseBodyConverter(innerType, annotations);
    return new OptionalConverter<>(delegate);
  }

  @IgnoreJRERequirement
  static final class OptionalConverter<T> implements Converter<ResponseBody.Optional<T>> {
    final Converter<ResponseBody, T> delegate;

    OptionalConverter(Converter<ResponseBody, T> delegate) {
      this.delegate = delegate;
    }

    @Override
    public Optional<T> convert(ResponseBody value) throws IOException {
      returnOptional.ofNullable(delegate.convert(value)); }}}Copy the code

The responseBodyConverter logic is simple. It determines if Type is Optional. Class and returns OptionalConverter. The convert function is called when the network successfully returns the data, which is directly delegated to the next converter.

When building the converterFactories list, we add BuiltInConverters first, then our custom converters, and finally OptionalConverterFactory if Android SDK>=24. So you can see the priorities here.

The list of callAdapterFactories is built with our own custom and the platform’s default adapters.

Normally, we add the GsonConverterFactory adapter to convert Gson to an object type.

@Override public Converter<ResponseBody, ? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); }Copy the code

GsonResponseBodyConverter convert function.

  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

At this point, the entire Retrofit source code process is analyzed. So you should know how to customize adapters and converters.

coroutines

Retrofit has supported coroutines since version 2.6.0, which allows you to define suspended functions in an interface. So how does Retrofit determine that the functions defined by the interface are suspended?

. In the last section analysis RequestFactory parseAnotations analysis function annotations, said parseParameter analytic function parameter annotation, has referred to determine whether the function suspends function. So let’s see how that works.

    private @NullableParameterHandler<? > parseParameter(int p, Type parameterType, @Nullable Annotation[] annotations, booleanAllowContinuation) {......if (result == null) {// The parameters are not annotated
        if (allowContinuation) {// The last argument to the function
          try {
            if (Utils.getRawType(parameterType) == Continuation.class) {// The parameter type is Continuation
              isKotlinSuspendFunction = true;
              return null; }}catch (NoClassDefFoundError ignored) {
          }
        }
        throw parameterError(method, p, "No Retrofit annotation found.");
      }

      return result;
    }
Copy the code

There are three conditions for determining whether a coroutine is a function:

  1. Function parameters are not annotated
  2. This argument is the last position in the function
  3. The type of this parameter isContinuation.class

But that’s a little different from the way we think about coroutines as suspended functions. Because this is the Kotlin suspend function we understand, when it is compiled into Java bytecode, it automatically adds a Continuation type parameter to the last parameter of the function.

Kotlin function

suspend fun getData(a)
Copy the code

Java functions

void getData(Continuation c)
Copy the code

By resolving the last parameter of the function in the interface without an annotation and with a Continuation type, you determine that the function is suspended for a Kotlin coroutine and set the isKotlinSuspendFunction of ReqeustFactory to true.

In HttpServiceMethod. ParseAnnotations resolution according to the different branches of isKotlinSuspendFunction to walk, for example isKotlinSuspendFunction = true cases, SuspendForResponse is returned, and if =false, CallAdapter is returned. Let’s see what we did.

  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;
   	  // In the coroutine case, as in the non-coroutine case, get the return type adapterType, which is used to create the CallAdapter
      if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      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 {
      adapterType = method.getGenericReturnType();// Non-coroutine case} CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); . Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory;if(! isKotlinSuspendFunction) {// Non-coroutine case
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //// uses coroutines and returns type Response
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //// uses a coroutine and returns the type ResponseBody
      return (HttpServiceMethod<ResponseT, ReturnT>)
          newSuspendForBody<>( requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code

SuspendForResponse or SuspendForBody, depending on the type of function returned by the interface.

  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) {
        returnKotlinExtensions.suspendAndThrow(e, continuation); }}}Copy the code

The network request is implemented by calling the Call extension function of OkHttp. We know that non-coroutines are implemented by creating OkHttpCall.

suspend fun <T : Any> Call<T>.await(): 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

That is, the non-coroutine works much the same as the Retrofit under coroutine. In the case of coroutines, the RequestFactory parser, which determines the coroutine, takes a different path in the HttpserviceMethod parser, which implements the network request through the Call extension function.

A dynamic proxy

When it comes to dynamic proxies, static proxies and proxy patterns are always involved. Static proxies always write the interface, then the proxy class and the proxied class implement the interface, the client uses the functionality provided by the interface through the proxy class, and the proxy class transfers the actual work of the functionality to the proxied class. The problem is, every time we know how to implement proxy classes and proxy classes, if there are many interfaces, it will be very tiring, after all, programmers are lazy. Again, with a third-party library like Retrofit, how do you know what interfaces users are creating and how to implement their functionality? That is to take advantage of Java’s dynamic proxy technology. Dynamic proxies can also be understood as a set of templates in use.

First, we define the interfaces and the implementation classes, which are the functions that we provide to the outside world.

public interface Service { void coding(); } public class implements Service {@override public void coding() {system.out.println (" implements Service "); }}Copy the code

Second, implement a subclass of InvocationHandler. The core point is to implement the Invoke function, to which all functions that call the interface will go.

public class Handler implements InvocationHandler {

    private Object service;

    public Handler(Object service) {
        this.service = service;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("Developed");
        method.invoke(service,objects);
        System.out.println("It's over.");
        return null; }}Copy the code

Finally, create the proxy. newProxyInstance interface object. In Java, interfaces and abstract classes cannot be instantiated directly.

public class Main {
    public static void main(String[] args) {
        Service service = new CoderService();
        InvocationHandler handler = new Handler(service);
        Service se = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(), newClass[]{Service.class}, handler); se.coding(); }}Copy the code

This allows the functionality to be used through the instantiated interface object.

Retrofit retrives the data required by the network request by parsing the Method at the InvocationHandler’s Invoke.

conclusion

Retrofit uses Java dynamic proxy technology to generate the object of our request interface, and when we call any function of the object, we actually go to the Invoke function of the InvocationHandler object. The main work is to parse and invoke the function we defined in the interface functions, including parsing annotations, parameter annotation and values, as well as the return type, and then wrapped in RequestFactory, then through HttpServiceMethod. ParseAnnotations function, Find the appropriate request adapter and response converter, and finally encapsulate the RequestFactory with the adapter and converter as a subclass of HttpServiceMethod and call its Invoke function to initiate network requests through OkHttp.

From source analysis, we know that a good framework will use caching, such as parsing methods here, caching OkHttp requests. Also know that Retrofit currently supports coroutines, called suspended functions.

Welcome to like + follow + comment triple combo

【Github】【 Gold 】【 Blog 】