We used Retrofit to handle requests from the web, and it was quick and easy to use, especially with annotations to set request parameters, which is a lot less work than the handwritten interface. But it’s great to use, and there’s always one question in mind: how does Retrofit parse annotation parameters? How do you send requests using OkHttp? Now with that in mind, read the source code to get a little insight into how the Retrofit framework works inside.

Create a Retrofit

Use the built-in Builder to easily and quickly assemble a Retrofit object

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();
Copy the code

The Builder pattern is used here to create objects, so what is the builder pattern?

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations. The construction of a complex object is separated from its representation so that the same construction process can create different representations

To put it simply, use a chain call to set parameters step by step and assemble a complex object.

Reference links: zhuanlan.zhihu.com/p/58093669

Define the interface and create the corresponding Service

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

GitHubService service = retrofit.create(GitHubService.class);
Copy the code

We have defined only one interface, GitHubService. How does the create method create the implementation class? Go inside the create method and see what you’re doing.

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

          @Override public @Nullable Object invoke(Object proxy, Method method,
              @Nullable Object[] args) throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            returnloadServiceMethod(method).invoke(args ! =null? args : emptyArgs); }}); }Copy the code

You can see that the dynamic proxy pattern is used to create the implementation class of the interface. To understand create, you must first understand what dynamic proxy is.

In combination with GitHubService, proxy. newProxyInstance creates an implementation class for GitHubService and forcibly converts it to GitHubService.

With a service, we can call the interface:

Call<List<Repo>> repos = service.listRepos("octocat");

This actually calls the interface through a proxy class generated by the dynamic proxy, which fires the Invoke method of InvocationHandler and goes to the loadServiceMethod.

Let’s start with InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

The simple understanding is that when a method of a proxy instance is called, its method call is dispatched to the Invoke method of InvocationHandler.

Continue to read the source code

loadServiceMethod

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 main thing here is to get a ServiceMethod object, and it is cached. If it has already been loaded, it is fetched directly from the cache. You see that the parseAnnotations method has been called, as the name implies, so you can see that annotations have been parsed here, so go in and take a look.

ServiceMethod

abstract class ServiceMethod<T> {
  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);
  }

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

The annotations are parsed by the RequestFactory to get the request URL, request method, request parameters, etc. Finally HttpServiceMethod parseAnnotations returns a ServiceMethod object, in fact is one of the three subclasses HttpServiceMethod,

Callfranchise, SuspendForBody, or SuspendForResponse, which is used depends on the specific interface definition, Call > listRepos(@path (“user”) String user); Callstuck is returned, or one of the latter two if the interface definition uses Kotlin’s suspend function.

invoke

The invoke method of loadServiceMethod is the invoke method of HttpServiceMethod:

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

Here we create an OkHttpCall object, which encapsulates OkHttp. The request is ultimately made through OkHttpCall’s OkHttp method. See OkHttpCall’s source code for details. The OkHttpCall is created with a responseConverter, which is the responseConverter that converts the result of the request to the object we defined via the ConverterFactory set up when we created the Retrofit object, .addConverterFactory(GsonConverterFactory.create())

adapt

The Invoke method calls the Adapt method, and this example is actually the Adapt method that calls CallCaller.

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

The adapt method call callAdapter adapt method, continue to track, callAdapter if not explicitly set, there will be the default Settings, DefaultCallAdapterFactory can obtain a callAdapter object, Take a look at DefaultCallAdapterFactory get method:

@Override public @Nullable CallAdapter<?, ?> get(
    Type returnType, Annotation[] annotations, Retrofit retrofit) {
  ……
  
  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);
    }
  };
}
Copy the code

The Adapt method finally returns a Call object. If the SkipCallbackExecutor annotation is set, OkHttpCall is returned, and ExecutorCallbackCall is not set.

ExecutorCallbackCall

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) {
    checkNotNull(callback, "callback == null");

    delegate.enqueue(new Callback<T>() {
      @Override public void onResponse(Call<T> call, final Response<T> response) {
        callbackExecutor.execute(new Runnable() {
          @Override public void run(a) {
            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(new Runnable() {
          @Override public void run(a) {
            callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); }... }Copy the code

You can really see the enQueue method for sending requests from OKHttp. One of the main functions of ExecutorCallbackCall is to call back the result of a request to the main thread, using MainThreadExecutor, which is a very simple class. This is handled by a Handler that holds the main thread Looper.

static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); }}Copy the code

conclusion

With the basic analysis of the process by which the Retrofit framework parses annotations and sends requests through OkHttp, we summarize our initial questions:

  • How to parse the annotations?

    The annotations are parsed primarily through the RequestFactory to get the various parameters of the request.

  • How do I send requests using OkHttp?

    Use OkHttpCall to finally call the OkHttp method to send the request

Reference: juejin. Cn/post / 684490…