Android advanced notes -- Retrofit source code analysis

Keywords: Retrofit network Android OkHttp

Article directory

I. information

Author: yoyiyi(soleil)
Proofreader: yoyiyi(soleil)
Date of establishment: January 22, 2020
Revised on: January 28, 2020
Warehouse: Soleil-Notes

Two, introduction

This is the second part of Android advanced notes, retrofit source code analysis. Retrofit is a RESTful design style network request framework. As for what is the RESTful design style interface, let's not expand it here. Students who are interested in it can consult relevant materials. In the past, we designed all interfaces according to the RESTful design It's very nice to get up. Far away, back to the topic, for retrofit, the bottom Okhttp is actually responsible for the network request work. For the analysis of Okhttp, please refer to the previous article Android advanced notes - Okhttp4.X source code analysis.

Three. Introduction

Here is a small example. The interface is Fun and fun Interface, request item classification.

//1. Define API to describe the requested interface
public interface WanAndroidService {
    @GET("project/tree/json")
    Call<CategoryEntity> getCategory();
}
//2. Create Retrofit
val retrofit = Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

//3. Create a network request instance
val service = retrofit.create(WanAndroidService::class.java)

//4. Call the network request API, generate a call, and execute the request
val call= service.getCategory()
call.enqueue(object : retrofit2.Callback<CategoryEntity> {
            override fun onFailure(call: retrofit2.Call<CategoryEntity>, t: Throwable) {

            }

            override fun onResponse(
                call: retrofit2.Call<CategoryEntity>,
                response: retrofit2.Response<CategoryEntity>
            ) {
                val result = response.body()
                Log.d("result", result.toString())
            }

        })

From the above code, we can see that the use process of Retrofit is very simple, but this article is not about the use of Retrofit. In our learning, we should not only look at the appearance, but also look at the essence, so as to make continuous progress.

4, Source code analysis

4.1. Create retrofit

In the above code, there is a very important key point, which is the creation of Retrofit. Let's see how Retrofit is built.

4.1.1. Building

//Building Retrofit with builder mode 
val retrofit = Retrofit.Builder()
            .baseUrl("https://www.wanandroid.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

Next I'll take a look at Builder, which is an internal class of Retrofi

public static final class Builder {
    //Platform type
    private final Platform platform;
    //Request factory, Okhttp by default
    private @Nullable okhttp3.Call.Factory callFactory;
    //Address of the requested url
    private @Nullable HttpUrl baseUrl;
    //Factory set for data conversion
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    //Collection of adapter factories, default ExecutorCallAdapterFactory
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    //Call back the executor and switch the sub thread to the main thread. On Android, it encapsulates the main thread executor of the handler
    private @Nullable Executor callbackExecutor;
    //Cache, create ServiceMethod for true
    private boolean validateEagerly;
 }  

Let's see the default initialization of Builder

public static final class Builder {
 Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder() {
      this(Platform.get());
    }
  .....  
}
//Platform class is involved
class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    try {
      //Determine whether it is Android platform
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        //Create an Android class
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform(true);
  }
  
  //Create default network request adapter factory
  List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
      @Nullable Executor callbackExecutor) {
    //Default network adapter  
    DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
    return hasJava8Types
        ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
        : singletonList(executorFactory);
  }

  int defaultCallAdapterFactoriesSize() {
    return hasJava8Types ? 2 : 1;
  }

  List<? extends Converter.Factory> defaultConverterFactories() {
    return hasJava8Types
        ? singletonList(OptionalConverterFactory.INSTANCE)
        : emptyList();
  }


//Inherit Platform
static final class Android extends Platform {
    Android() {
      super(Build.VERSION.SDK_INT >= 24);
    }

    @Override public Executor defaultCallbackExecutor() {
      //Switch thread, sub thread to main thread
      return new MainThreadExecutor();
    }
    // Handler mechanism, sub thread switches to main thread
    static class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }

4.1.2. Add baseUrl

//Retrofit.java
public Builder baseUrl(String baseUrl) {
     Objects.requireNonNull(baseUrl, "baseUrl == null");
     //Convert string to HttpUrl
     return baseUrl(HttpUrl.get(baseUrl));
}

public Builder baseUrl(HttpUrl baseUrl) {
     Objects.requireNonNull(baseUrl, "baseUrl == null");
     List<String> pathSegments = baseUrl.pathSegments();
     if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
       throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
     }
     this.baseUrl = baseUrl;
     return this;
  }

4.1.3. Add GsonConverterFactory

//1. create of gsonconverterfactory
 public static GsonConverterFactory create() {
    return create(new Gson());
 }

//2. Call create
public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
  }

 private final Gson gson;
 //3. Create a GsonConverterFactory with a Gson object 
 private GsonConverterFactory(Gson gson) {
    this.gson = gson;
}
//4. Add addGsonConverFactory. To put it bluntly, add the GsonConverterFactory containing the Gson object to the data conversion factory converterFactories
public Builder addConverterFactory(Converter.Factory factory) {
    converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
    return this;
}

4.1.4.build()

Next let's see what's done in the build() method.

 public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        //Default request factory uses OkHttpClient
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
       //Callback
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
        callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));//Add default adapter

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(
          1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
      converterFactories.addAll(platform.defaultConverterFactories());

      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }
  }

4.2. Create network request

Next, analyze the process of retrofit.create(), which adopts appearance mode and agent mode.

 public <T> T create(final Class<T> service) {
   //Verify interface
    validateServiceInterface(service);
    //Use dynamic proxy to get all interface annotation configuration of request interface and create network request interface instance
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { 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);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

 private void validateServiceInterface(Class<?> service) {
    if (!service.isInterface()) {
      throw new IllegalArgumentException("API declarations must be interfaces.");
    }

    Deque<Class<?>> check = new ArrayDeque<>(1);
    check.add(service);
    while (!check.isEmpty()) {
      Class<?> candidate = check.removeFirst();
      if (candidate.getTypeParameters().length != 0) {
        StringBuilder message = new StringBuilder("Type parameters are unsupported on ")
            .append(candidate.getName());
        if (candidate != service) {
          message.append(" which is an interface of ")
              .append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }

    if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
          loadServiceMethod(method);
        }
      }
    }
  }

Next let's look at the 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;
  }

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    //Parsing comments for request configuration
    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.");
    }
    //Request method built by HttpServiceMethod 
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

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

Let's take a look at httpservicemethod and parseannotations

 static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
       
       //1. Get the corresponding network request adapter from the Retrofit object
       CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
  
//2. Obtain the corresponding data converter from the Retrofit object according to the return value and annotation type of the network request interface method 
 Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType); 

Let's look at createCallAdapter

 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);
    }
  }
  
 public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {
    Objects.requireNonNull(returnType, "returnType == null");
    Objects.requireNonNull(annotations, "annotations == null");

    int start = callAdapterFactories.indexOf(skipPast) + 1;
    //Cycle to get the right request factory
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }

Let's look at createResponseConverter

//Finally, they all came into this method
 public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    Objects.requireNonNull(type, "type == null");
    Objects.requireNonNull(annotations, "annotations == null");

    int start = converterFactories.indexOf(skipPast) + 1;
   //Cycle to get the right conversion plant
    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;
      }
    }
}

Finally, execute httpservicemethod "invoke"

 @Override final @Nullable ReturnT invoke(Object[] args) {
    //OkHttpCall responsible for network requests
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

4.3. Call the network request API, generate a call, and execute the request

val call= service.getCategory()

From the above analysis, we can see that the service object is actually the Call object obtained from the dynamic proxy object Proxy.newProxyInstance().

4.3.1. Asynchronous request

enqueue is called by asynchronous request

//DefaultCallAdapterFactory.java
 @Override public void enqueue(final Callback<T> callback) {
      Objects.requireNonNull(callback, "callback == null");
       //Using static proxy delegate for seven cattle
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          //Thread switch, sub thread switch to main thread
          callbackExecutor.execute(() -> {
            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(() -> callback.onFailure(ExecutorCallbackCall.this, t));
        }
      });
    }

Let's look at the enqueue in delegate

 //OkHttpCall.java
 @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 {
         //In fact, it is to create the Request object of Okhttp, which calls OkHttp.call.
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

4.3.2. Synchronization request

val response = category.execute()

OkhttpCall or called

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

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

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else if (creationFailure instanceof RuntimeException) {
          throw (RuntimeException) creationFailure;
        } else {
          throw (Error) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
          throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }
    //Call execute() of OkHttpCall to send network request
    return parseResponse(call.execute());
  }
  
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 {
      //Turn the response body into a Java object
      T body = responseConverter.convert(catchingBody);
      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();
      throw e;
    }
  }

At this point, the Retrofit process has been very clear. In a word, it can be summarized as follows: using dynamic agent, the encapsulated request is finally handed over to the underlying OkHttp for processing.

Five, reference

Source code analysis of Android mainstream tripartite Library (2. In depth understanding of Retrofit source code)
Android: hand in hand to help you understand the source code of Retrofit 2.0

Published 17 original articles, won praise 11, visited 30000+
Private letter follow

Posted by jandrews on Wed, 29 Jan 2020 04:17:57 -0800