preface

Known as 🤪, Retrofit internally encapsulates an implementation of OkHttp. Specifically, Retrofit is an extension of OkHttp, effectively addressing the problem of OkHttp code logic calls being less elegant, while providing more convenient functional modules for developers. A simple example of a network request is given in Retrofit’s official documentation. This article will examine Retrofit’s source code based on examples.

Version 2.9.0 of Retrofit is used in this article

Implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'

Flow profile

Sample code from the documentation:

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

// 1. Create a Retrofit instance
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

2. Instantiate a GitHubService object
GitHubService service = retrofit.create(GitHubService.class);

// 3. Initiate a network request
Call<List<Repo>> repos = service.listRepos("octocat");
call.enqueue(new Callback<List<Object>>() {
    @Override
    public void onResponse(@NonNull Call<List<Repo>> call, @NonNull Response<List<Repo>> response) {}@Override
    public void onFailure(@NonNull Call<List<Repo>> call, @NonNull Throwable t) {}});Copy the code

Making a network request using Retrofit starts with defining an interface, which is an abstraction of the Settings associated with the network request. Here are the three steps:

  • createRetrofitExample, usingRetrofit.Builder()To build aRetrofitObject, and some parameters are set during this process. Such as:
    • Server side baseUrl.
    • If you need to customize someOkHttpInterceptor, developers need to customize oneOkHttpClient.
    • RetrofitSupport for request and response type conversion customization (Converter), and adaptation to other libraries (e.gRxJava(Adapter). This will be covered in a future article.

    This is typically done in application initialization,RetrofitThe instanceThe life cycle will be consistent with the app.Avoid the overhead of duplicate creation.

  • Instance of aGitHubServiceObject, usingRetrofitExample Build an object with the interface defined aboveRetrofitTo help usAn imaginary object, more on that later).
  • To initiate a network request, a concrete method is first invokedservice.listRepos("octocat"), which means we need a request"users/{user}/repos"This interface. The next call withOkHttpSimilar, butThe thing to notice here is thatCallisRetrofitself-defined.

The above isRetrofitThe simple use of the process, the next will be a trilogy of analysis of the source. Before that, quote a reference toRetrofitFlowchart, covering the entire operation process:

Create a Retrofit

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

During the creation, it involves the idea of appearance mode and Builder mode. It’s just some global configuration logic as Retrofit. The focus here is on the build() method

// Retrofit.java
public Retrofit build(a) {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }
  // 1. If OkHttpClient is not customized, use the default
  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }
  
  // 2. A specific thread pool is required when requesting a callback
  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }
  
  // 3. The Adapter builds the factory list during execution
  // 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));
  
  // 4. Request/response data Converter builds factory list
  // 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);
}
Copy the code

The build() method provides the necessary parameters for the construction of a Retrofit object. BaseUrl is a little bit easier to understand, so I won’t expand it here.

callFactory

CallFactory is the OkHttpClient in OkHttp, which is created by default for subsequent network requests if not set by the developer. The Settings are in Retrofit.Builder:

// Retrofit.java
public Builder client(OkHttpClient client) {
  return callFactory(Objects.requireNonNull(client, "client == null"));
}
Copy the code

callbackExecutor

CallbackExecutor controls the thread that needs to be called back after a network request responds. This is also Retrofit’s encapsulation of OkHttp, allowing developers to do a thread transformation. There will be a default callbackExecutor, created platform specific. The idea of strategic patterns is used here.

// Retrofit.java
Builder(Platform platform) {
  this.platform = platform;
}

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

// Retrofit.Builder#build()
platform.defaultCallbackExecutor();

// Platform.java
private static final Platform PLATFORM = findPlatform();

static Platform get(a) {
  return PLATFORM;
}

// The current platform is Android or Java based on the JVM, and Java8 is supported by default
private static Platform findPlatform(a) {
  return "Dalvik".equals(System.getProperty("java.vm.name"))?new Android()
      : new Platform(true);
}
Copy the code

The default Plantform object is retrieved in the constructor of Retrofit.Builder, and the findPlatform() method differentiates platforms by JVM. As this article is biased towards Android, the Android class is selected as the parsing object.

As an extension, in older versions of Retrofit (e.g. 2.4.0), the platform also supports iOS. Is a technology for iOS development using Java using the RoboVM environment. RoboVM: Developing iOS applications using Java

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

  @Override
  public Executor defaultCallbackExecutor(a) {
    return new MainThreadExecutor();
  }

  @Nullable
  @Override
  Object invokeDefaultMethod( Method method, Class
        declaringClass, Object object, Object... args) throws Throwable {
    if (Build.VERSION.SDK_INT < 26) {
      throw new UnsupportedOperationException(
          "Calling default methods on API 24 and 25 is not supported");
    }
    return super.invokeDefaultMethod(method, declaringClass, object, args);
  }

  static final class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());

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

Android#defaultCallbackExecutor returns a MainThreadExecutor Handler that holds a main thread inside. Its execute method is a MessageQueue that sends a Runnable to the main thread. The child thread is switched to the main thread.

callAdapterFactories

CallAdapter is a wrapper generated class abstracted from Retrofit about okHttp3.Call performing network requests. Detailed introduction is given below.

// Retrofit.java
public Builder addConverterFactory(Converter.Factory factory) {
  converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
  return this;
}

// Platform.java
List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
    @Nullable Executor callbackExecutor) {
  DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
  return hasJava8Types
      ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
      : singletonList(executorFactory);
}
Copy the code
  • Developers can follow their own logic in theBuilderWhen passing throughaddConverterFactoryAdd relatedCallAdaptertheThe factory class. For example, this will be covered in subsequent articlesRxJavaCallAdapter.
  • PlatformClass implements one by defaultdefaultCallAdapterFactoriesMethod used to getRetrofitBring their own.CallAdapter.Factory, there will beAdd one based on whether the platform is compatible with Java8CompletableFutureCallAdapterFactoryAnd the defaultDefaultCallAdapterFactory. This paper introduces DefaultCallAdapterFactory.

converterFactories

Converter, Retrofit can perform a layer of data conversion according to Converter in request and response, converting the object passed from the upper layer to the common object of network request, or parsing the data back into the data required by the developer.

// Retrofit.java
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());

// Platform.java
List<? extends Converter.Factory> defaultConverterFactories() {
  return hasJava8Types ? singletonList(OptionalConverterFactory.INSTANCE) : emptyList();
}
Copy the code
  • One is added by defaultBuiltInConvertersTo supportOkHttpRequestBody, ResponseBody, ResponseBody
  • Developers can add custom ones based on their own needsConverter.
  • PlatformWill be added depending on whether Java8 is supportedOptionalConverterFactory, which will not be covered in this article.

summary

This completes the introduction of the build() method, which covers the OkHttpClient, the core of the network request, the thread setup for the callback, the adapter during the request, and the data converter for the request and response.

Retrofit#create

GitHubService service = retrofit.create(GitHubService.class);

// Retrofit.java
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 {
              // 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;
              returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

When defining the network interface layer, we defined an Interface GitHubService, which I initially expected to generate an Impl class at compile time, like libraries like Room. But Retrofit#create is a clever use of dynamic proxies. With dynamic proxy, when calling a specific interface Method, the invoke Method of the above code will be triggered. Then, the Method object will obtain the specific parameters, methods, and parameter annotations, and then convert them into the parameter format required by OkHttp.

Making a Network request

Making a network request can be divided into two steps based on the above sample code: 1. Invoking a specific network interface layer; 2

/ / 1
Call<List<Repo>> repos = service.listRepos("octocat");
/ / 2
call.enqueue(new Callback<List<Object>>() {
    @Override
    public void onResponse(@NonNull Call<List<Repo>> call, @NonNull Response<List<Repo>> response) {}@Override
    public void onFailure(@NonNull Call<List<Repo>> call, @NonNull Throwable t) {}});Copy the code

Invoke the specific network interface layer

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

As mentioned in Retrofit#create, a concrete method is actually called InvocationHandler#invoke

// Retrofit.java
@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;
  return platform.isDefaultMethod(method)
      ? platform.invokeDefaultMethod(method, service, proxy, args)
      : loadServiceMethod(method).invoke(args);
}
Copy the code

This is the default method for Java8’s interface feature. Here we focus only on loadServiceMethod(method).invoke(args); This is the case.

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

Retrofit#loadServiceMethod

The logic in Retrofit#loadServiceMethod is simple and can be understood as yes

  • throughServiceMethodClass parses and stores information about method, such as method annotations, parameter annotations, parameters, and so on.
  • throughserviceMethodCache, a Map forms a global cache,Optimize the overhead of repeated parsing on a second request.

ServiceMethod parseAnnotations namely the analytical method of information.

Ps: ServiceMethod is an implementation class at a lower version, and an abstract class at a higher version may be needed to accommodate Kotlin’s coroutines. Specific dependencies:

classDiagram
ServiceMethod <|-- HttpServiceMethod
HttpServiceMethod <|-- CallAdapted
HttpServiceMethod <|-- SuspendForBody

ServiceMethod. ParseAnnotations final call is HttpServiceMethod. ParseAnnotations, this article does not focus on Kotlin coroutines logic. The following code is ignored for now.

// ServiceMethod.java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
  // Parses the method information for subsequent network request data assembly.
  RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
  // Check for some return types...
  return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

// HttpServiceMethod.java
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) {
    // ...
  } else {
    adapterType = method.getGenericReturnType();
  }

  // Find the appropriate callAdapter using the method return type adapterType and method annotation Annotations
  CallAdapter<ResponseT, ReturnT> callAdapter =
      createCallAdapter(retrofit, method, adapterType, annotations);
  Type responseType = callAdapter.responseType();
  if (responseType == okhttp3.Response.class) {
    throw methodError(
        method,
        "'"
            + getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  if (responseType == Response.class) {
    throw methodError(method, "Response must include generic type (e.g., Response<String>)");
  }
  // TODO support Unit for Kotlin?
  if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
    throw methodError(method, "HEAD method must use Void as response type.");
  }

  // Find the appropriate Converter according to the method return type responseType
  Converter<ResponseBody, ResponseT> responseConverter =
      createResponseConverter(retrofit, method, responseType);

  okhttp3.Call.Factory callFactory = retrofit.callFactory;
  if(! isKotlinSuspendFunction) {// Create a ServiceMethod object corresponding to the OkHttp wrapper
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
  } else if (continuationWantsResponse) {
    // ...
  } else {
    // ...}}Copy the code
  • The first concernServiceMethod.parseAnnotationsIn theRequestFactory.parseAnnotations(retrofit, method);The main logic isStores the parameters of the request in the Method objectAnd assembles the corresponding Converter from parameters for request-time data assembly and validation for annotation use. I won’t tell you more here.
  • callHttpServiceMethod.parseAnnotations
    • Find the right callAdapter
    • Find the right Converter
    • Create one based on the above informationServiceMethodObject (CallAdapted)

Get callAdapter

// HttpServiceMethod.java
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); }}// Retrofit.java
publicCallAdapter<? ,? > callAdapter(Type returnType, Annotation[] annotations) {return nextCallAdapter(null, returnType, annotations);
}

publicCallAdapter<? ,? > 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;
  for (inti = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations,this);
    if(adapter ! =null) {
      returnadapter; }}// throw exception logic...
}
Copy the code

As mentioned earlier in Retrofit.builder #build, Retrofit holds a list of the callAdapterFactories that create callAdapters, or callAdapterFactories. The nextCallAdapter method is to find out that an appropriate callAdapter can be created based on the information of returnType and Annotations. Take the case of DefaultCallAdapterFactory:

// DefaultCallAdapterFactory.java
@Override
public @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) {if(getRawType(returnType) ! = Call.class) {return null;
  }
  if(! (returnTypeinstanceof 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;

  return newCallAdapter<Object, Call<? > > () {@Override
    public Type responseType(a) {
      return responseType;
    }

    @Override
    public Call<Object> adapt(Call<Object> call) {
      return executor == null ? call : newExecutorCallbackCall<>(executor, call); }}; }Copy the code

DefaultCallAdapterFactory# get first check is whether the method return type for the Call type. The corresponding is:

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

Notice that an anonymous class object from a CallAdapter is returned. The Adapt method will be invoked later in the process.

Access to the converter

Getting a Converter is similar to the logic of getting a callAdapter, only this time find the appropriate Converter.Factory and create a reponseBodyConverter:

// HttpServiceMethod.java
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); }}// Retrofit.java
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) {
  Objects.requireNonNull(type, "type == null");
  Objects.requireNonNull(annotations, "annotations == null");

  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; }}// throw exception logic...
}
Copy the code

Note that BuiltInConverters added by Retrofit by default cannot actually be converted to custom objects, as shown in the following code

@Override
public @NullableConverter<ResponseBody, ? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) {if (type == ResponseBody.class) {
    return Utils.isAnnotationPresent(annotations, Streaming.class)
        ? StreamingResponseBodyConverter.INSTANCE
        : BufferingResponseBodyConverter.INSTANCE;
  }
  if (type == Void.class) {
    return VoidResponseBodyConverter.INSTANCE;
  }
  if (checkForKotlinUnit) {
    try {
      if (type == Unit.class) {
        returnUnitResponseBodyConverter.INSTANCE; }}catch (NoClassDefFoundError ignored) {
      checkForKotlinUnit = false; }}return null;
}
Copy the code

The Retrofit object we created from the sample code doesn’t actually have the ability to convert the response data into a custom object, so adding a GsonConverterFactory as a general rule changes the code to something like this:

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

Implementation ‘com. Squareup. Retrofit2: converter – gson: 2.9.0’

ServiceMethod#invoke

Back in InvocationHandler#invoke, ServiceMethod#invoke is invoked after loadServiceMethod is executed.

// ServiceMethod.java
abstract @Nullable T invoke(Object[] args);

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

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

Since ServiceMethod#invoke is an abstract method, implemented by HttpServiceMethod. Here invoke is declared final in HttpServiceMethod (ps: HttpServiceMethod is also an abstract class). The HttpServiceMethod#invoke method eventually calls adapt, which is implemented by subclasses.

  • HttpServiceMethod#invokeCreate a new one inOkHttpCallObject that inherits fromCallInterface (yes, and see the Call above isThe sameThis is the concrete implementation class. This class encapsulatesOkHttpNetwork request logic, yesWhere network requests are actually executed.
  • CallAdapted#adaptThe callAdapter is the one mentioned in the previous sectionDefaultCallAdapterFactory#getThe returnedanonymousCallAdapter object. Calling its adapt method returns oneExecutorCallbackCallObject, also inherited fromCallInterface. So this is usingA decoratorThoughts.
// DefaultCallAdapterFactory.java
@Override
public Call<Object> adapt(Call<Object> call) {
  return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
}
Copy the code

So back at the application layer, the Call object returned is actually an ExecutorCallbackCall object in this case.

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

The initiating

As you know from the previous section, the Call object type is ExecutorCallbackCall.

call.enqueue(new Callback<List<Object>>() {
    @Override
    public void onResponse(@NonNull Call<List<Repo>> call, @NonNull Response<List<Repo>> response) {}@Override
    public void onFailure(@NonNull Call<List<Repo>> call, @NonNull Throwable t) {}});Copy the code

ExecutorCallbackCall as DefaultCallAdapterFactory static inner class, by adopting the idea of such a decorator.

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

A call to enqueue is actually a call to the delegate’s Enqueue (ps: the delegate is the OkHttpCall). The ExecutorCallbackCall role here is to switch threads. The callbackExecutor is the one set up during Retrofit initialization, and the Android platform defaults to the main thread.

The relationship between Call, ExecutorCallbackCall, and OkHttpCall

classDiagram
Call <|-- ExecutorCallbackCall
Call <|-- OkHttpCall
OkHttpCall <-- ExecutorCallbackCall

OkHttpCall

Let’s start with a simple OkHttp use case that corresponds to the Retrofit sample code

Create a new Call, ps: okhttp3.Call and Retrofit#Call
String user = "abc";
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().get()
        .url("https://api.github.com/users/" + user + "/repos")
        .build();
okhttp3.Call call = client.newCall(request);
// 2. Initiate a request
call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {}@Override
    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {}});Copy the code

A brief description:

  1. usingOkHttpClientnewaokhttp3.CallobjectPay attention to the sum hereRetrofit of the CallInterface for differentiation.
  2. callokhttp3.Call#enqueueThe initiating

Continuing back to OkHttpCall:

// 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 {
        Create a new okHttp3.call
        call = rawCall = createRawCall();
      } catch(Throwable t) { throwIfFatal(t); failure = creationFailure = t; }}}if(failure ! =null) {
    callback.onFailure(this, failure);
    return;
  }

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

  // 2. Initiate a network request
  call.enqueue(
      new okhttp3.Callback() {
        @Override
        public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
          Response<T> response;
          try {
            response = parseResponse(rawResponse);
          } 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

The comparison of the above code with pure OkHttp and Retrofit’s OkHttpCall is not hard to understand.

  • Create a newokhttp3.CallIs used beforeServiceMethod.parseAnnotationsTo create theRequestFactoryObject, the main function isAssemble one by saving the Method information previouslyokhttp3.Requestobject. (PS: Converter is also used in data assembly).
    // OkHttpCall.java
    private okhttp3.Call createRawCall(a) throws IOException {
      okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
      if (call == null) {
        throw new NullPointerException("Call.Factory returned null.");
      }
      return call;
    }
    Copy the code
  • After the network request responds, the call is calledOkHttpCall#parseResponseIn thenormal200Response code caseWill useresponseConverterTransform the response data.
    // OkHttpCall.java
    Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
      ResponseBody rawBody = rawResponse.body();
    
      // ...
    
      ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
      try {
        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();
        throwe; }}Copy the code

    Because we’re usingGsonConverterFactory“Will eventually arriveGsonResponseBodyConverter#convert. In fact, isDo a JSON parsing using Gson.

    // GsonResponseBodyConverter.java
    @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

    Ps: hereThe parsingTThe type is the one you just definedList<Repo>.

Design patterns

To summarize the design pattern ideas encountered in this article (feel free to add and revise in the comments) :

  • RetrofitIs used to create theThe appearance model,Builder Pattern.
  • Retrofit#createWith the help ofA dynamic proxyInstantiate the defined interface as a fictitious object.
  • aboutCallAdatper,ConverterThe fetch is usedThe strategy pattern.
    • According to theDefine the return type of the method(such as:Call), get the appropriateCallAdatper
    • According to theThe data type to be converted for the response data(such as:The Call of < T > T), get the appropriate responseConverter.
  • CallAdatper,ConverterIs used to create theThe factory pattern(CallAdatper.Factory,Contercer.Factory).
  • CallAdatperWith the help ofAdapter mode, i.e.,Encapsulates theOkHttpThe ability ofOkHttpCallCan be adapted to different library calls, such asExectorCallbackCall/RxJava/Java8And so on.ConverterThe same is true of the mind of.

The last

This article briefly parses the process encapsulation of the entire OkHttp network request using sample code provided in the Retrofit documentation. When reading the source code, you need to pay attention to which CallAdapter and Converter are selected for the defined interface methods, as well as the confusion between Call and okHttp3. Call. Understanding the process will help you look at other extensions, such as RxJava.

Reference article:

Design patterns in Retrofit

Why don’t you take a look at Retrofit?