An overview of the

We know that the Retrofit Web framework is very common in Android development and can be easily combined with RxJava and Kotlin Coroutine.

I recently spent some time looking at the Retrofit framework, from its use to its implementation, and made some observations. So I decided to blog about Retrofit as a kind of summary and summary of my research on Retrofit, and I hope it will be helpful.

There are expected to be several articles about the Retrofit framework, featuring the following:

  • The way this article is organized is not to dive into the source code, but to start with use cases and then analyze how it works.

  • Retrofit is a network framework, so it would be nice to have a server counterpart to it so you can debug network requests better. So I set up a Tomcat + Servlet server to receive network requests, and I will analyze the source code to answer any questions that arise when debugging Retrofit.

  • An immediate benefit of analyzing source code is knowing how it is implemented. What else? Open source framework so many, we can extract, summarize some common things out, improve our code level. I’ll summarize my insights into Retrofit source code analysis in the final article in this series.

Start with a simple example

Retrofit is very simple to use, and you can basically get started by looking at the official documentation. So this article will not cover the use of every Retrofit annotation, but if you are not familiar with Retrofit, take a few minutes to read about it on the Retrofit website.

Let’s look at a simple case:

Interface UserService {@post ("register")
	@FormUrlEncoded
	fun register(
		@Field("username") username: String,
		@Field("mobile") mobile: String): Call<ResponseModel<User>>} private val userService by lazy { Retrofit = Retrofit.Builder() .baseUrl(API_URL) .addConverterFactory(GsonConverterFactory.create()) .build() // Create (UserService::class.java)} Private funrequest() {// Call the business method and return a Call object val Call = userService.register("chiclaim"."110") showLoading() // Execute network request call.enqueue(object: Retrofit2. Callback<ResponseModel<User>> {override fun onFailure(call: call <User>>, t: ResponseModel<User>> Throwable) { dismissLoading() ToastHelper.showToast(applicationContext, T.message) text_content.text = t.message} override fun onResponse(call: Call<ResponseModel<User>>, response: Response<ResponseModel<User>>) { dismissLoading() val repsModel = response.body() val code = response.code()if (code in200.. 299) { text_content.text = (repsModel? .message) text_content.append("\n") text_content.append(repsModel? .data.toString()) }else {
				text_content.text = "response code ${response.code()}\n${repsModel? .message}"}}})}Copy the code

The above case is very simple, and the official website introduction is much the same, but I wrote a simple back-end program to connect to this request.

But we can’t just be satisfied with API calls, we need to understand the implementation behind them. I’m going to throw out a few questions about this case, and then analyze the source code to find the answers.

Throw out problem

In response to the above case, I raise the following questions:

  • We only defined the interface (UserService), but we did not define the implementation class. How did Retrofit produce the implementation class?

  • Before sending a network request without using Retrofit, we needed to assemble the parameters ourselves. How did Retrofit assemble the request parameters through annotations?

  • What does ConverterFactory do? Where does it work?

Find the answer in the source code

The retrofit.create() method in the above example is used to create the business implementation class:

retrofit.create(UserService::class.java)
Copy the code

Then let’s look at an implementation of the create method:

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() { 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 called method is an Object method, the general call is acceptableif (method.getDeclaringClass() == Object.class) {
              returnmethod.invoke(this, args); } // If the method called is interface default (JDK1.8)if (platform.isDefaultMethod(method)) {
              returnplatform.invokeDefaultMethod(method, service, proxy, args); } // If we call the business method we definedreturn loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
}
Copy the code

Discover that it is implemented using dynamic proxies, so the first step is to understand the fundamentals of dynamic proxies. As for the function and basic principle of dynamic proxy, I have described it in the analysis of proxy pattern before. If you need it, you can refer to “Design Pattern ~ In-depth Understanding of Proxy Pattern”.

So to the first question: “We just defined the interface (UserService), we didn’t define the implementation class, how does Retrofit produce the implementation class?” Retrofit is implemented through dynamic proxies.

The next step is to look at a concrete implementation of Retrofit dynamic proxy. When calling a specific business method, such as:

val call = userService.register("chiclaim"."110")
Copy the code

The invoke method will eventually be called, and I have commented the main logic in the Invoke method above. The core code logic is:

loadServiceMethod(method).invoke(args ! = null ? args : emptyArgs);Copy the code

Let’s look at the loadServiceMethod method:

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

The main logic is to parse method annotations, encapsulate them into ServiceMethod, and place them in a serviceMethodCache container. Annotations The main logic of the loadServiceMethod method is configured from parseAnnotations:

  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    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.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

Copy the code

ServiceMethod. ParseAnnotations method are two main methods:

  • RequestFactory.parseAnnotations
  • HttpServiceMethod.parseAnnotations

Let’s look at RequestFactory. ParseAnnotations method:

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
  return new Builder(retrofit, method).build();
}
  
RequestFactory build() {// Parse annotations on methods such as @get, @post, @multipart, @formurlencodedfor(Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } // omit parameter verification... int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<? >[parameterCount]; // All parameter annotations have a class corresponding to them, This class inherits ParameterHandler. These classes are internal classes to ParameterHandler. The core method of ParameterHandler is Apply, which adds parameter values from the method to the RequestBuilderfor(int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) { parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); } // omit parameter verification...return new RequestFactory(this);
}
Copy the code

RequestFactory. ParseAnnotations method mainly will parse interface parameter annotation, encapsulated in RequestFactory object, and then to return it.

This answers the second question: How does Retrofit assemble request parameters from annotations? Mainly through RequestFactory. ParseAnnotations annotations on the analytical method.

RequestFactory is mainly used to create the Request object that OkHttp needs to initiate a Request, as discussed later.

We continue to see HttpServiceMethod. ParseAnnotations method:

  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) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else{// omit comment... } adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType); annotations = SkipCallbackExecutorImpl.ensurePresent(annotations); }else{ adapterType = method.getGenericReturnType(); } // Create a CallAdapter, CallAdapter<ResponseT, ReturnT> CallAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); // omit parameter verification... // create responseConverter to return the result of the request, ResponseConverter = responateresponSeconverter (retrofit, method, responseType);  // callFactory is the OkHttpClient set by default in retrofit. build, Okhttp3.call.factory callFactory = retrofit.callFactory; // If not kotlinsuspendfunctionif(! isKotlinSuspendFunction) {returnnew CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); } // If it is KotlinsuspendFunction and the return type of the function is Response, for example: //suspend fun register(
    //        @Field("username") username: String,
    //        @Field("mobile") mobile: String): Response<Userelse if (continuationWantsResponse) {
      return(HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter); } // Other cases. For example,suspendFunction that returns Body Data instead of Response, for example: //suspend fun register(
    //        @Field("username") username: String,
    //        @Field("mobile") mobile: String): User
	else {
      return(HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code

. So we will ServiceMethod parseAnnotations method in: RequestFactory. ParseAnnotations and HttpServiceMethod parseAnnotations analysis completed.

Because loadServiceMethod ServiceMethod is the main logic of the parseAnnotations, so loadServiceMethod also finished the analysis. So let’s examine the invoke method:

LoadServiceMethod (method).invoke(args! = null ? args : emptyArgs); abstract class HttpServiceMethod { @Override final @Nullable ReturnT invoke(Object[] args) { Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);returnadapt(call, args); } / / adapt method is an abstract, which subclass implementation depends on the specific use interface function of use, we've been HttpServiceMethod. ParseAnnotations analysis after / / if not kotlin interface functionsuspendUse the CallStuck class // if kotlinsuspendIf the return type is Response, use SuspendForResponse class // interface functions for other cases such as kotlinsuspendInstead of returning a Response, Protected abstract @nullable ReturnT adapt(Call<ResponseT> Call,  Object[] args); }Copy the code

Let’s examine the three subclasses of HttpServiceMethod:

  • CallAdapted
  • SuspendForResponse
  • SuspendForBody

Regardless of which subclass they are, their constructor depends on ResponseConverter, CallAdapter (note not callName-dependent)

Create ResponseConverter (ResponseConverter);

val retrofit = Retrofit.Builder()
                .baseUrl(API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
Copy the code

We are through addConverterFactory methods will GsonConverterFactory converter plant added, GsonConverterFactory 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 {
      T result = adapter.read(jsonReader);
      if(jsonReader.peek() ! = JsonToken.END_DOCUMENT) { throw new JsonIOException("JSON document was not fully consumed.");
      }
      returnresult; } finally { value.close(); }}}Copy the code

GsonResponseBodyConverter in short is the data in the response body through GSON into objects of what we need

Then we see CallAdapter, actually CallAdapter object is in HttpServiceMethod parseAnnotations method created:

class HttpServiceMethod { static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); // omit other code... } // createCallAdapter private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method method, TypereturnType, Annotation[] Annotations) {try {// Retrofit.callAdapter is calledreturn (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 final class Retrofit { public CallAdapter<? ,? > callAdapter(TypereturnType, Annotation[] annotations) {
		return nextCallAdapter(null, returnType, annotations); NextCallAdapter public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, TypereturnType, Annotation[] annotations) {// omit parameter validation... / / skipPast due to upload passed in parameter is null, so start equals 0 int start = callAdapterFactories. IndexOf (skipPast) + 1;for(int i = start, count = callAdapterFactories.size(); i < count; I++) {// get the factory from the collection of callAdapterFactories and create a CallAdapter CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
		  if(adapter ! = null) {returnadapter; }} // omit the assembly error message... throw new IllegalArgumentException(builder.toString()); }}Copy the code

Get the factory from the callAdapterFactories collection and create the CallAdapter. But we didn’t add a CallAdapterFactory in the case at the beginning of this article, we just added a ConverterFactory:

val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(API_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
Copy the code

Add the default CallAdapterFactory to the build method:

public Retrofit build() {// omit other logic... / / create CallAdapter Factory collection List < CallAdapter. Factory > callAdapterFactories = new ArrayList < > (enclosing callAdapterFactories); / / add the default CallAdapterFactory callAdapterFactories. AddAll (platform. DefaultCallAdapterFactories (callbackExecutor));return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
	  unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Copy the code

We look at Platform. DefaultCallAdapterFactories:

class Platform { List<? Extends CallAdapter. Factory > defaultCallAdapterFactories (@ Nullable Executor callbackExecutor) {/ / create DefaultCallAdapterFactory DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);returnhasJava8Types ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory) : singletonList(executorFactory); }}Copy the code

Then we see is how to create CallAdapter DefaultCallAdapterFactory factory:

final class DefaultCallAdapterFactory extends CallAdapter.Factory { private final @Nullable Executor callbackExecutor; DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) { this.callbackExecutor = callbackExecutor; Override public @nullable CallAdapter<? ,? > get( TypereturnType, Annotation[] annotations, Retrofit Retrofit) {// omit parameter validation... final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType)returnType); // If the interface method uses the @skipCallBackExecutor annotation, Do not use callbackExecutor final Executor Executor = Utils. IsAnnotationPresent (annotations, SkipCallbackExecutor. Class)? null : callbackExecutor; // Returns the CallAdapter anonymous inner class objectreturnnew CallAdapter<Object, Call<? >>() { @Override public TyperesponseType() {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        returnexecutor == null ? call : new ExecutorCallbackCall<>(executor, call); }}; }} static final class ExecutorCallbackCall<T> implements Call<T> {// omit other logic... @override public void enqueue(final Callback<T> Callback) { Callback<T>() {@override public void onResponse(Call<T> Call, Final Response<T> Response) {// Place the success callback in callbackExecutor and execute 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) {// Place the failure callback in callbackExecutor and execute callBackexecutor.execute (() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); } // omit other logic... }Copy the code

Now you know how to create an AdapterFactory.

This is the time to introduce the three subclasses of HttpServiceMethod (CallFranchise, SuspendForResponse, SuspendForBody).

From the above analysis, we know that when our interface method is not suspend, as in:

@POST("users/new") fun createUser(@Body user: User): Call<User>
Copy the code

It will use callCaller:

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> { private final CallAdapter<ResponseT, ReturnT> callAdapter; // omit constructor... @Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {returncallAdapter.adapt(call); }}Copy the code

It turns out it’s pretty simple, just calling the callAdapter.adapt method.

If our interface function is kotlin suspend and its return type is Response, for example:

suspend fun register(
	   @Field("username") username: String,
	   @Field("mobile") mobile: String): Response<User>
Copy the code

Retrofit uses SuspendForResponse sem, which is a little more complicated than CallStuck:

static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> { private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter; // omit constructor... @Override protected Object adapt(Call<ResponseT> call, Object[] args) { call = callAdapter.adapt(call); Continuation<Response<ResponseT>> continuation = (Continuation<Response<ResponseT>>) args[args.length - 1]; try {return KotlinExtensions.awaitResponse(call, continuation);
	  } catch (Exception e) {
		returnKotlinExtensions.suspendAndThrow(e, continuation); }}}suspend fun <T : Any> Call<T>.awaitResponse(): Response<T> {
  return suspendCancellableCoroutine {continuation -> continuation -> Call Call. Cancel () continuation. InvokeOnCancellation {the cancel ()} / Call/Call enenque, really start to perform network requests for the enqueue (object: Callback<T> { override fun onResponse(call: Call<T>, response: Resume (Response)} Override fun onFailure(call: override fun onFailure(call: Call<T>, t: Throwable) { continuation.resumeWithException(t) } }) } }Copy the code

If the interface function is kotlin suspend, but the return value of the function is not Response, for example:

suspend fun register(
          @Field("username") username: String,
          @Field("mobile") mobile: String): User
Copy the code

Retrofit uses SuspendForBody, which differs from SuspendForResponsem in that SuspendForBody returns Response.body as the starting point of coroutine suspension:

static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> { private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter; private final boolean isNullable; // omit constructor... @Override protected Object adapt(Call<ResponseT> call, Object[] args) { call = callAdapter.adapt(call); Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1]; try {return isNullable
            ? KotlinExtensions.awaitNullable(call, continuation)
            : KotlinExtensions.await(call, continuation);
      } catch (Exception e) {
        returnKotlinExtensions.suspendAndThrow(e, continuation); }} // awaitNullable @jvmName ("awaitNullable")
suspendfun <T : Any> Call<T? >.await(): T? {return suspendCancellableCoroutine {continuation -> continuation -> Call Call. Cancel () continuation. InvokeOnCancellation {the cancel ()} / Call/Call enenque, really start to perform network requests for the enqueue (object: Callback<T? > { override fun onResponse(call: Call<T? >, response: Response<T? >) {if(response.issuccessful) {// Continue with the appropriate coroutine and return Response.body as the last suspension point continuation.resume(response.body())}else{ continuation.resumeWithException(HttpException(response)) } } override fun onFailure(call: Call<T? >, t: Throwable) { continuation.resumeWithException(t) } }) } }Copy the code

Whether it’s CallAdapted, SuspendForResponse, or SuspendForBody, their adapt methods all rely on OkHttpCall:

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

As the name suggests, the final request is to perform the final network request through OkHttp. Let’s take a look at OkHttpCall:

Final class OkHttpCall<T> implements Call<T> {// // For space reasons, only synchronous and asynchronous requests are reserved... @override public void enqueue(final Callback<T> Callback) {okHttp3. Call; Throwable failure; synchronized (this) {if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if(call == null && failure == null) {try {// The implementation inside is equivalent to OkHttpClient. NewCall Call = rawCall = createRawCall(); } catch (Throwable t) { throwIfFatal(t); failure = creationFailure = t; }}} // omit other code... // Make an asynchronous network request through OkHttp... call.enqueue(new okhttp3.Callback@override public void onResponse(okHttp3. Call Call, okhttp3.Response rawResponse) {Response<T> Response;  Try {// Convert OkHttp Response to Retrofit Response Response = parseResponse(rawResponse); } catch (Throwable e) { throwIfFatal(e); callFailure(e);return; } try {// Success callback. OnResponse (okHttpCall. this, response); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); @override public void onFailure(okHttp3. Call Call, IOException e) {callFailure(e); } private void callFailure(Throwable e) {try {// Fail callback. OnFailure (okHttpCall.this, e); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); }}}); } @override public Response<T> execute() throws IOException {okHttp3. Call Call; synchronized (this) {if (executed) throw new IllegalStateException("Already executed.");
      executed = true; // omit exception throws... call = rawCall;if(call == null) {try {// The implementation inside is equivalent to OkHttpClient. NewCall Call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); creationFailure = e; throw e; }}}if(canceled) { call.cancel(); } // Convert OkHttp Response to Retrofit ResponsereturnparseResponse(call.execute()); } private okHttp3. Call createRawCall() throws IOException {// The default callFactory is OkHttpClient // requestFactory.create Call Call = callFactory.newCall(requestFactory.create(args));if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    returncall; } // parse OkHttp Response, Retrofit Response 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(); / / HTTP code not successful state if (code < 200 | | code > = 300) {try {ResponseBody bufferedBody = Utils. The buffer (rawBody); return Response.error(bufferedBody, rawResponse); } finally { rawBody.close(); } } // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/204 // / / https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/205 HTTP 204 said the request has been successful, but the client customers don't need to leave the current page. By default, 204 responses are cacheable. An ETag header is included in such a Response. // HTTP 205 indicates that the request has been successful and is used to inform the client to reset the document view. // 204, 205 Response.body is set to null, So the upper needs to pay attention to the condition of the body is null if (code = = 204 | | code = = 205) {rawBody. Close (); return Response.success(null, rawResponse); } ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); Try {/ / responseConverter here is actually GsonResponseBodyConverter / / this is in the build Retrofit object set: when / / val Retrofit: Retrofit = Retrofit.Builder() // .baseUrl(API_URL) // .addConverterFactory(GsonConverterFactory.create()) // .build() // Will return the results to need the type of T body = responseConverter. Convert (catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { catchingBody.throwIfCaught(); throw e; }}}Copy the code

With OkHttpCall out of the way, we can answer our third question: “What is ConverterFactory good for? Where does it work?”

ConverterFactory is mainly used for serialization and deserialization of objects, such as converting the requested object into JSON and passing it to the server to get the JSON returned by the server into the desired object.

For example, okHttpCall. parseResponse uses Converter to convert the JSON returned by the server into the desired object.

If the HTTP code is 204 or 205, the body of the Response will be null. So when you’re using Retrofit it’s important to be aware that the body is empty. Such as:

@POST("register")
@FormUrlEncoded
fun registerByRxJava(
	@Field("username") username: String? , @Field("mobile") mobile: String ): Observable<ResponseModel<User>? >Copy the code

If the HTTP code is 204 or 205, the network request succeeds, but a null pointer exception is thrown:

java.lang.NullPointerException: Null is not a valid element
Copy the code

We will continue this analysis when we introduce Retrofit’s use with RxJava.

So let’s start with a simple example of how Retrofit initiates network requests and some of the core concepts in Retrofit such as dynamic proxy, CallAdapter, Converter, etc

Later, I will introduce to you:

  • How does Retrofit customize the CallAdapter?
  • How to whole RxJava, Coroutine?
  • How do I upload Retrofit files
  • Retrofit file upload problems, how to analyze the source code to find answers

All the use cases for Retrofit are in myAndroidAll GitHubIn the warehouse. In addition to Retrofit, the repository also has other common Android open source library source analysis, such as “RxJava” “Glide” “LeakCanary” “Dagger2” “Retrofit” “OkHttp” “ButterKnife” “Router” and so on. In addition, there is a complete mind map of the technology stack that Android programmers need.