Retrofit simplifies network implementation by repackaging OkHttp.

1. Code usage (source code example SimpleService)

The workflow is simple in four steps:

  • Create a Retrofit object
  • Create the request interface proxy object
  • Reflection generates an OkhttpCall object
  • Request and parse ResponseBody returns
Retrofit retrofit =
  new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);

// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square"."retrofit");

List<Contributor> contributors = call.execute().body()
Copy the code

2. Create a Retrofit object

The Builder pattern creates Retrofit objects

fucntion description
client Specify the custom OkHttpClient to create
callFactory Specify the custom invocation factory to create
baseUrl Specifies that the base url to be created generally contains a protocol part, a domain name part, and a port part
addConverterFactory Add converter factories for object serialization and deserialization
addCallAdapterFactory Add a call adapter factory that supports service method return types
callbackExecutor Add a thread pool for call execution
validateEagerly Instantiate the interface object to immediately validate the configuration of all methods in the supplied interface

3. Create a request interface proxy object

ValidateServiceInterface checks if the interface class and its parents are generic, and throws exceptions if they are. Using the Proxy request interface class, create an instance object of InvocationHandler, which is used to handle all request method calls of the Proxy class

public <T> T create(final Class<T> service) {
  validateServiceInterface(service);
  return(T) Proxy.newProxyInstance( service.getClassLoader(), new Class<? >[] {service}, new InvocationHandler() {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); } args = args ! =null ? args : emptyArgs;
              Platform platform = Platform.get(a);returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

The platform is android29, so the instance returned by the get method is Android24

private static final class Android24 extends Platform {
  static boolean isSupported() {
    return Build.VERSION.SDK_INT >= 24;
  }

  private @Nullable Constructor<Lookup> lookupConstructor;

  @Override
  Executor defaultCallbackExecutor() {
    return MainThreadExecutor.INSTANCE;
  }

  @Override
  List<? extends CallAdapter.Factory> createDefaultCallAdapterFactories(
      @Nullable Executor callbackExecutor) {
    returnasList( new CompletableFutureCallAdapterFactory(), new DefaultCallAdapterFactory(callbackExecutor)); }... }Copy the code

4. Generate OkHttpCall objects

  • The reflection resolves the interface method annotation, and the parameter annotation generates the CallStuck
  • The invoke method is called to generate the OkHttpCall
  • Create the ExecutorCallbackCall agent OkHttpCall

4.1 Reflection parsing Interface method annotations Parameter annotations get the request type and URLpath, etc

Need to get the following information for the use of Retrofit method annotations array methodAnnotations [7], parameterTypes parameter type array, the array parameter annotation parameterAnnotationsArray

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

[9-10] Parse the method annotation to get the request type httpMethod and url path relativeUrl

private void parseMethodAnnotation(Annotation annotation) {...else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); }... }Copy the code

[11] The re matches the URL to get a set of PATH strings assigned to relativeUrlParamNames

static Set<String> parsePathParameters(String path) {
  Matcher m = PARAM_URL_REGEX.matcher(path);
  Set<String> patterns = new LinkedHashSet<>();
  while (m.find()) {
    patterns.add(m.group(1));
  }
  return patterns;
}
Copy the code

[8] The generated method parameterHandler is then used to replace the path in the relativeUrl

RequestFactory build() { ... 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); }...return new RequestFactory(this);
}
Copy the code

[12-22] Generate the Call Component based on the Platform.Android24 and retrofit. Build method. Create defaultCallAdapterFactory. The get () callAdapter object and GsonResponseBodyConverter responseBodyConverter GsonResponseBodyConverte The r object callFactory is created with okhttpClient by default

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> { static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { ... CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); . Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory;if(! isKotlinSuspendFunction) {returnnew CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); }... }}Copy the code

4.3 Invoking the Invoke method generates an OkHttpCall

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {...@Override
  protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
    return callAdapter.adapt(call);
  }
  
 @Override
 final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  returnadapt(call, args); }}Copy the code

4.3 Creating an ExecutorCallbackCall agent OkHttpCall

Platform. The Android24 created a MainThreadExecutor. The INSTANCE, so adapt generates ExecutorCallbackCall object

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @NullableExecutor callbackExecutor; .@Override
  public @NullableCallAdapter<? ,? >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;

    returnnew 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

Android normally performs UI operations on the UI thread, but OKHTTP does not have this capability, so the author uses static proxy mode to implement the enqueue callback from the child thread to the UI thread

static final class ExecutorCallbackCall<T> implements Call<T> {...@Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");
    delegate.enqueue(
        new Callback<T>() {
          @Override
          public void onResponse(Call<T> call, final Response<T> response) {
            callbackExecutor.execute(
                () -> {
                  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(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }...@Override
  public Response<T> execute() throws IOException {
    returndelegate.execute(); }... }Copy the code

We currently store the following values in the RequestFactory in addition to generating the ExecutorCallbackCall object

Parameter names Source way value
httpMethod Interface method annotation name GET
baseUrl Custom incoming api.github.com/
relativeUrl Interface method annotation value /repos/{owner}/{repo}/contributors
ParameterHandler RequestFactory method parseParameterAnnotation Two ParameterHandler$Path name parameters, one owner and one repO

5. Request and parse ResponseBody return

  • Generate an okHttp3. Request object and call the OkhttpClient Request
  • Parse the ResponseBody using the responseConverter generated earlier

5.1 Invoking the OkhttpClient Request, invoking the OkhttpClient request

final class OkHttpCall<T> implements Call<T> {...@Override
  public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = getRawCall();
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }

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

5.2 analytical ResponseBody Gson

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 {
      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