Retrofit core source code analysis (2) - network request and response processing

In the previous article, we analyzed in detail the annotation parsing and dynamic proxy implementation in Retrofit. This article will continue to study the core source code of Retrofit in depth, focusing on how Retrofit handles network requests and responses.

network request

When using Retrofit to initiate a network request, we can define an interface and use Retrofit annotations to describe the request in this interface, and Retrofit will automatically generate a proxy object that implements the interface. When we call the method of this proxy object, Retrofit will construct a Request object according to the description of the annotation, and use OkHttp to send the Request.

In Retrofit, we Retrofit#executecan Retrofit#enqueuesend requests through the or method. The difference between these two methods is that executethe method will block the current thread until the request is completed, while enqueuethe method will add the request to the OkHttp request queue and notify us through the callback when the request is completed.

Let's first look at the implementation of executethe method :

public <T> T execute(Call<T> call) throws IOException {
  Utils.validateServiceInterface(call.request().tag(), call.request().url().toString());
  return (T) callAdapter(call, call.request().tag()).adapt(call).execute();
}

In this method, the interface is first verified to ensure that the interface is valid. Then we get the adapter based on the request's Tag and URL callAdapter, and use the adapter to execute the request.

The role of the adapter is to adapt the parameters of the request to a form that OkHttp can recognize, and to adapt the response of OkHttp to the form we need. Retrofit provides a series of adapters, including Call adapter, RxJava adapter, CompletableFuture adapter, etc.

Let's take a look at the implementation of callAdapterthe method :

private CallAdapter<?, ?> callAdapter(Call<?> call, Object tag) {
  Type responseType = call.request().method().equals("HEAD")
      ? Void.class
      : getParameterUpperBound(0, (ParameterizedType) call.request().tag());
  return callAdapter(tag, responseType);
}

In this method, we first judge the response type according to the request method. If it is the HEAD method, then the response type is Void; otherwise, we will obtain the response type of the request through reflection, and use this response type to obtain the adapter.

The way to get the adapter is callAdapter:

public <R, T> CallAdapter<R, T> callAdapter(Object tag, Type returnType) {
  // ...
  for (CallAdapter.Factory factory : adapterFactories) {
    CallAdapter<?, ?> adapter = factory.get(returnType, annotations, this);
    if (adapter != null) {
      return (CallAdapter<R, T>) adapter;
    }
  }
  // ...
}

In this method, we iterate through all adapter factories, trying to get an adapter. When getting the adapter, we pass the request's response type, annotation, and Retrofit instance as parameters. Each adapter factory will judge whether these parameters meet its own adaptation conditions, and if so, return an adapter instance, otherwise return null. After traversing all adapter factories, if no adapter has been obtained, an exception will be thrown.

After getting the adapter, we can use the adapter to execute the request. In the adapter, we will convert the request parameters into the OkHttp Request object, and convert the OkHttp Response object into the response type we need. For specific implementation, please refer to CallAdapterthe interface .

For enqueuethe method , we can first look at enqueuethe implementation of the method:

public <T> void enqueue(Call<T> call, Callback<T> callback) {
  Utils.validateServiceInterface(call.request().tag(), call.request().url().toString());
  callAdapter(call, call.request().tag()).adapt(call).enqueue(new CallbackRunnable<>(callback));
}

In this method, we first check the interface, then obtain the adapter according to the requested Tag and URL, and use the adapter to execute the request. The difference is that in enqueuethe method , we pass a Callback object as a parameter enqueueinto the method of the adapter, so that the callback will notify us after the request is completed.

In the adapter, we can see the implementation of enqueuethe method :

public void enqueue(final Callback<T> callback) {
  delegate.enqueue(new Callback<Response<T>>() {
    @Override public void onResponse(Call<Response<T>> call, Response<Response<T>> response) {
      Response<T> body;
      try {
        body = response.body();
      } catch (Throwable t) {
        if (response.code() == 204) {
          body = null;
        } else {
          callback.onFailure(call, t);
          return;
        }
      }
      if (response.isSuccessful()) {
        callback.onResponse(call, Response.success(body, response.raw()));
      } else {
        callback.onFailure(call, Response.error(response.errorBody(), response.raw()));
      }
    }

    @Override public void onFailure(Call<Response<T>> call, Throwable t) {
      callback.onFailure(call, t);
    }
  });
}

In this method, we will convert the incoming Callback object into an Callback<Response<T>>object , and use this object to call the enqueue method of OkHttp. After the request is completed, we will convert the Response object of OkHttp into the Response object of Retrofit, and judge the result of the request according to the response code. If the response code indicates that the request is successful, then we call onResponsethe method ; otherwise, we call onFailurethe method of the Callback object.

response processing

In Retrofit, we can describe our desired request format and response format by defining an interface and using annotations. For example, we can use @GETannotations to describe a GET request, use @Queryannotations to describe request parameters, use @Bodyannotations to describe the request body, use @Headersannotations to describe request headers, and so on.

When executing a request, Retrofit will automatically generate a corresponding request object based on these annotations, and convert the request object into an OkHttp Request object. When receiving the response, Retrofit will convert the OkHttp Response object into a corresponding response object, and convert the data in the response object into the data type we need. These conversions are done through Retrofit's converters. Retrofit provides two converters by default: GsonConverterFactoryand JacksonConverterFactory. We can also customize a converter to achieve our desired data conversion.

In the construction method of the Retrofit class, we can see that Retrofit uses Platform.get()the method to obtain the default converter factory of the current running platform and add it converterFactoriesto . We can then use addConverterFactorythe method to add custom converter factories.

public Retrofit(Builder builder) {
  // ...
  if (builder.converterFactories == null) {
    converterFactories.add(Platform.get().defaultConverterFactory());
  } else {
    converterFactories.addAll(builder.converterFactories);
  }
  // ...
}

public interface Platform {
  // ...
  Converter.Factory defaultConverterFactory();
}

In executethe method, we call the adapter's adapt method to execute the request and convert the returned Call object into a response object. During the conversion process, we will select the corresponding converter according to the response type for conversion. For specific conversion implementation, please refer to Converterthe interface and Converter.Factoryinterface provided by Retrofit.

public <T> T execute(Call<T> call) throws IOException {
  // ...
  Response<T> response = call.execute();
  if (response.isSuccessful()) {
    return response.body();
  } else {
    Converter<ResponseBody, ErrorResponse> converter = retrofit.responseBodyConverter(
        ErrorResponse.class, new Annotation[0]);
    throw new ApiException(converter.convert(response.errorBody()));
  }
}

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T adapt(Call<T> call) {
  return (T) new OkHttpCall<>(requestFactory, callFactory, converter, call);
}

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 (int i = start, count = converterFactories.size(); i < count; i++) {
    Converter<ResponseBody, ?> converter =
        converterFactories.get(i).responseBodyConverter(type, annotations, this);
    if (converter != null) {
      return (Converter<ResponseBody, T>) converter;
    }
  }

  throw new IllegalArgumentException(
      "Could not locate ResponseBody converter for " + type + " with annotations " + Arrays.toString(annotations));
}

The above is the core code for processing responses in Retrofit. When we execute a request, Retrofit will first convert the request into an OkHttp Request object and send it out, and then wait for the response to return. When the response is returned, Retrofit will convert the response into a response object and convert the data in the response object into the data type we expect. In this process, we can use the converters provided by Retrofit to customize the data conversion rules.

Here's an example of how to use Retrofit to send a GET request and convert the JSON data in the response into a Java object:

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

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

ApiService apiService = retrofit.create(ApiService.class);
Call<List<Repo>> call = apiService.listRepos("smallmarker");
List<Repo> repos = call.execute().body();

In the example above, we first created a Retrofit instance using the Retrofit builder, specifying the base URL for the request and the converter factory. Then, createwe create a ApiServiceproxy object of by calling the method. Finally, we call listReposthe method to send a GET request.

In the example above, we used Retrofit's GsonConverterFactoryto convert the JSON data in the response body into Java objects. For specific implementation, please refer to GsonConverterFactorythe classes .

public final class GsonConverterFactory extends Converter.Factory {
  private final Gson gson;

  private GsonConverterFactory(Gson gson) {
    this.gson = gson;
  }

  public static GsonConverterFactory create() {
    return create(new Gson());
  }

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

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

  @Override
  public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations,
                                                                  Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}

It can be seen that GsonConverterFactorythe Retrofit Converter.Factoryclass , and responseBodyConverterthe method and requestBodyConvertermethod are rewritten. In responseBodyConverterthe method , we convert the JSON data in the response body into a Java object, and requestBodyConverterin the method, we convert the Java object into the JSON data in the request body.

In addition GsonConverterFactoryto , Retrofit also provides other converters, such as JacksonConverterFactory、MoshiConverterFactoryetc. We can choose the converter that suits us according to our needs.

In general, the core code of network request and response processing in Retrofit is very concise and clear. We only need to describe the request and response by defining the interface, then use Retrofit's dynamic proxy mechanism to convert the interface into an actual implementation class, and specify the request and response converter through the Retrofit configuration. This method greatly simplifies the process of network requests, allowing us to focus more on the processing of business logic.

Guess you like

Origin blog.csdn.net/zl_china/article/details/129419455