Old Android development talks by first-line Internet companies: Do you really understand the source code of Retrofit?

Preface

Today we will learn the source code of Retrofit.

1. The creation process of Retrofit

When we use Retrofit to request the network, we must first write the request interface:

public interface IpService {
    @GET("getIpInfo.php?ip=59.108.54.37")
      Call<IpModel> getIpMsg();

Then we create Retrofit by calling the following code:

Retrofit retrofit = new Retrofit.Builder()
             .baseUrl(url)
             .addConverterFactory(GsonConverterFactory.create())
             .build();

Retrofit is built through the builder mode, let's see what the Builder method does:

public Builder() {
    this(Platform.get());
  }

Very short, look at the get method of Platform, as shown below.

private static final Platform PLATFORM = findPlatform();
 static Platform get() {
   return PLATFORM;
 }
 private static Platform findPlatform() {
   try {
     Class.forName("android.os.Build");
     if (Build.VERSION.SDK_INT != 0) {
       return new Android();
     }
   } catch (ClassNotFoundException ignored) {
   }
   try {
     Class.forName("java.util.Optional");
     return new Java8();
   } catch (ClassNotFoundException ignored) {
   }
   try {
     Class.forName("org.robovm.apple.foundation.NSObject");
     return new IOS();
   } catch (ClassNotFoundException ignored) {
   }
   return new Platform();
 }

Platform's get method finally calls the findPlatform method, which provides different thread pools according to different operating platforms. Next, look at the build method, the code is shown below.

public Retrofit build() {
   if (baseUrl == null) {//1
     throw new IllegalStateException("Base URL required.");
   }
   okhttp3.Call.Factory callFactory = this.callFactory;//2
   if (callFactory == null) {
     callFactory = new OkHttpClient();//3
   }
   Executor callbackExecutor = this.callbackExecutor;
   if (callbackExecutor == null) {
     callbackExecutor = platform.defaultCallbackExecutor();//4
   }
   List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);//5
   adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
   List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);//6
   return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
       callbackExecutor, validateEagerly);
 }

It can be seen from Note 1 that baseUrl must be specified. Note 2 The default callFactory is this.callFactory, this.callFactory is passed in by calling the callFactory method when we construct Retrofit, as shown below.

public Builder callFactory(okhttp3.Call.Factory factory) {
   this.callFactory = checkNotNull(factory, "factory == null");
   return this;
 }

Therefore, if you need to set OkHttpClient, you can construct an OkHttpClient object, and then call the callFactory method to pass in the OkHttpClient that has been set. Note 3, if callFactory is not set, OkHttpClient is created directly. The callbackExecutor of Note 4 is used to pass the callback to the UI thread. The adapterFactories of note 5 is mainly used to store the objects that transform the Call, which will be mentioned again in the creation process of the Call later. The converterFactories at Note 6 is mainly used to store conversion data objects, which will be mentioned later. The addConverterFactory(GsonConverterFactory.create()) called in the example before is to set the returned data to support conversion to Gson objects. Finally, the configured Retrofit class will be returned.

2. Call creation process

Then we create an instance of Retrofit and call the following code to generate a dynamic proxy object for the interface:

IpService ipService = retrofit.create(IpService.class);

Next, look at what Retrofit's create method does. The code is shown below.

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, 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 serviceMethod = loadServiceMethod(method);//1
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

You can see that the create method returns a Proxy.newProxyInstance dynamic proxy object. When we call the getIpMsg method of IpService, we will eventually call the invoke method of the InvocationHandler. It has 3 parameters, the first is the proxy object, and the second is the method to be called. , The third is the method parameter. The method in loadServiceMethod(method) in Note 1 is the getIpMsg method we defined. Next look at what is done in the loadServiceMethod method:

private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
ServiceMethod loadServiceMethod(Method method) {
   ServiceMethod result;
   synchronized (serviceMethodCache) {
     result = serviceMethodCache.get(method);
     if (result == null) {
       result = new ServiceMethod.Builder(this, method).build();
       serviceMethodCache.put(method, result);
     }
   }
   return result;
 }

First, it will query whether the incoming method has a cache from the serviceMethodCache, if there is, use the cached ServiceMethod, if not, create one, and add the serviceMethodCache to cache. Next, let's see how ServiceMethod is constructed. The code is shown below.

public ServiceMethod build() {
   callAdapter = createCallAdapter();//1
   responseType = callAdapter.responseType();//2
   if (responseType == Response.class || responseType == okhttp3.Response.class) {
     throw methodError("'"
         + Utils.getRawType(responseType).getName()
         + "' is not a valid response body type. Did you mean ResponseBody?");
   }
   responseConverter = createResponseConverter();//3
   for (Annotation annotation : methodAnnotations) {
     parseMethodAnnotation(annotation);//4
   }
  ...
   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];//5
     if (parameterAnnotations == null) {
       throw parameterError(p, "No Retrofit annotation found.");
     }
     parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
   }
   ...
   return new ServiceMethod<>(this);
 }

The createCallAdapter method is called in Note 1, and it will eventually get the get method of the object added by adapterFactories when we call the build method when building Retrofit, and part of the code of Retrofit’s build method:

List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
   adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

The defaultCallAdapterFactory is added to the adapterFactories list by default. The defaultCallAdapterFactory refers to the ExecutorCallAdapterFactory. The get method of the ExecutorCallAdapterFactory is shown below.

public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
   if (getRawType(returnType) != Call.class) {
     return null;
   }
   final Type responseType = Utils.getCallResponseType(returnType);
   return new CallAdapter<Call<?>>() {
     @Override public Type responseType() {
       return responseType;
     }
     @Override public <R> Call<R> adapt(Call<R> call) {
       return new ExecutorCallbackCall<>(callbackExecutor, call);
     }
   };
 }

The get method will get the CallAdapter object, and its responseType method will return the true type of the data, for example  Call<IpModel>, it will return IpModel. The adapt method creates ExecutorCallbackCall, which forwards the callback of the call to the UI thread.
Then go back to the build method of ServiceMethod, and call the responseType of CallAdapter at Note 2 to get the true type of the returned data.
Note 3 call the createResponseConverter method to traverse the Converter.Factory stored in the converterFactories list and return a suitable Converter to convert the object. Before we were building Retrofit, we called addConverterFactory (GsonConverterFactory.create()) to add GsonConverterFactory (a subclass of Converter.Factory) to the list of converterFactories, indicating that the returned data supports conversion to Json objects.
Note 4 traverse the parseMethodAnnotation method to analyze the request method (such as GET, POST) and the request address. Note 5 to parse the parameter annotations in the method (such as @Query, @Part). Finally, create the ServiceMethod class and return.
Next, look back at the create method of Retrofit. After calling the loadServiceMethod method, OkHttpCall will be created, and the OkHttpCall constructor is just an assignment operation. Immediately after the call serviceMethod.callAdapter.adapt(okHttpCall), the adapt method of callAdapter was mentioned earlier, it will create ExecutorCallbackCall, and part of the code of ExecutorCallbackCall is shown below.

ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
     this.callbackExecutor = callbackExecutor;
     this.delegate = delegate;
   }
   @Override public void enqueue(final Callback<T> callback) {
     if (callback == null) throw new NullPointerException("callback == null");
     delegate.enqueue(new Callback<T>() {//1
       @Override public void onResponse(Call<T> call, final Response<T> response) {
         callbackExecutor.execute(new Runnable() {
           @Override public void run() {
             if (delegate.isCanceled()) {
               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() {
             callback.onFailure(ExecutorCallbackCall.this, t);
           }
         });
       }
     });
   }

It can be seen that ExecutorCallbackCall is an encapsulation of Call, which mainly adds callback to the UI thread through callbackExecutor.
When we get the Call object, we will call its enqueue method. In fact, we call the enqueue method of ExecutorCallbackCall. From Note 1, we can see that the enqueue method of ExecutorCallbackCall finally calls the enqueue method of delegate. From the code of the create method of Retrofit, we know that the delegate is actually OkHttpCall.

3.Call's enqueue method

Next we will look at the enqueue method of OkHttpCall, the code is shown below.

public void enqueue(final Callback<T> callback) {
  if (callback == null) throw new NullPointerException("callback == null");
  okhttp3.Call call;
 ...
  call.enqueue(new okhttp3.Callback() {//1
    @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
        throws IOException {
      Response<T> response;
      try {
        response = parseResponse(rawResponse);//2
      } catch (Throwable e) {
        callFailure(e);
        return;
      }
      callSuccess(response);
    }
    ...
}

Note 1 calls the enqueue method of okhttp3.Call. Note 2: call the parseResponse method:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();
 ...
  int code = rawResponse.code();
  if (code < 200 || code >= 300) {
    try {
      ResponseBody bufferedBody = Utils.buffer(rawBody);
      return Response.error(bufferedBody, rawResponse);
    } finally {
      rawBody.close();
    }
  }
  if (code == 204 || code == 205) {
    return Response.success(null, rawResponse);
  }
  ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
  try {
    T body = serviceMethod.toResponse(catchingBody);//2
    return Response.success(body, rawResponse);
  } catch (RuntimeException e) {
    catchingBody.throwIfCaught();
    throw e;
  }
}

Do different operations according to the different status code code values ​​returned. If it goes well, the code at comment 2 will be called. Next, let's see what is done in the toResponse method:

T toResponse(ResponseBody body) throws IOException {
   return responseConverter.convert(body);
 }

This responseConverter is the Converter returned by calling the createResponseConverter method in the build method of ServiceMethod. In the previous example, we passed in GsonConverterFactory, so you can view the code of GsonConverterFactory, as shown below.

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

There is a method responseBodyConverter in GsonConverterFactory, which will eventually create GsonResponseBodyConverter:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;
  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }
  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}

In the convert method of GsonResponseBodyConverter, the callback data will be converted into Json format. So we also know that the previous call responseConverter.convertwas to convert to a specific data format.
Call's enqueue method mainly uses OKHttp to request the network and converts the returned Response to the UI thread.
So far, the source code of Retrofit is here.

This article  has been included in the open source project: https://github.com/Android-Alvin/Android-LearningNotes , which contains self-learning programming routes in different directions, interview questions/faces, and a series of technical articles. The resources are continuously updated …

Guess you like

Origin blog.csdn.net/weixin_43901866/article/details/110631051