Analysis of Retrofit principle absolutely worthy of collection

1 Preface

This paper mainly describes the source code analysis of Retrofit. Combined with the personal summary structure diagram and the Retrofit source code, it can help to understand the internal structure of Retrofit to a great extent

2. Retrofit structure Preview


This is the structure diagram I sorted out after reading the source code for a week, Click here to get pdf file And structure documents

3 structural analysis

There are a large number of articles on the use of Retrofit2 on the Internet. This article will not repeat it, but focus on the structure and process of Retrofit2. There is no more nonsense

3.1 Retrofit object instantiation

Here is a simple network request using retrofit

			Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://www.xxxxx.com")
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .build();
            OKHttpService httpService = retrofit.create(OKHttpService.class);
			httpService.login("ssss", "123123").enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.i("WeiSir", "Chen Gong: " + Thread.currentThread());
                }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("WeiSir", "fail: " + Thread.currentThread());
            }

Create a Retrofit object through the builder pattern and look at the function of each row in turn

  • Retro. Builder: in the static class of retro builder, the Platform.get() method is mainly executed. Its internal findplatform() determines the current execution Platform. Because it runs on the Android Platform, it will return new Android() to return a Platform object. After the network request ends, it will pass the post() of the internal main thread handler by default Switch the asynchronous thread to the main thread (there is the corresponding source code in the structure diagram)
  • baseUrl(): baseUrl is not marked in the figure. Its main function is to convert the incoming address into httpurl. Return baseUrl (httpurl) will obtain the path part of the web address, and divide each level path of this part into a collection to judge whether the last end of the path ends with '/'. Therefore, when passing the base address, the end of the path must end with '/', Otherwise, the illegalargumentexception ("baseUrl must end in /:" + baseUrl) exception will be thrown. Someone will say, "why didn't you encounter an exception before?". Note that this method judges the path part of the incoming address. If the address is' https:www.baidu.com ', there is only the protocol type and domain name address, and there is no path part, so there will be no error if' / 'is not written
  • addConverterFactory(): add a data conversion factory. If you want the return value of login() to be your own Call object, you need to use the data conversion factory. By default, BuiltInConverters is added to this factory and the original Call < ResponseBody > is returned. All data conversion factories added here will eventually be added to the list < converter. Factory > collection and will not be overwritten
  • Those familiar with the Builder mode of build() know that the previous operations only save the parameters. The real object creation is implemented in build(), in which the default network executor and callback method executor are added (the executor here is the mainthreadexecutor obtained under Platform.get() in the Builder object by default) , network request adapter, data converter, combined with the picture
 public Retrofit build() {
   okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {  //Add default request execution factory
        callFactory = new OkHttpClient();
      }
   Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {  //Create a network callback actuator
        callbackExecutor = platform.defaultCallbackExecutor();
      }
   List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));//Add network request adapter
      
   List<Converter.Factory> converterFactories = new ArrayList<>(1 + this.converterFactories.size()); 
   //Add default data conversion factory
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
 }

3.2 user defined network request interface definition

public interface OKHttpService{
    @FormUrlEncoded
    @POST("user/login")
    Call<ResponseBody> login(@Field("username") String userName, @Field("password") String password);
}

There is nothing to say here. If you want to use a custom JavaBean object, change the ResponseBody to the corresponding JavaBean object

3.3 create network interface proxy class (core)

    OKHttpService httpService = retrofit.create(OKHttpService.class);

create() internally implements the creation and scheduling of the core functions of Retrofit

  public <T> T create(final Class<T> service) {
//... omit
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
          //Here, it seems that the original method may not be called, but mainly executes the operation of the agent
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
           //Three lines of core code
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);   //The created proxy method object contains the annotation parameters and return values of the login method, as well as the adaptation operations of various factories and requester converters
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); //Create a network requester, so the underlying layer of Retrofit is implemented using okhttp by default
            return serviceMethod.adapt(okHttpCall); //Bind the okhttpcall object to the network request adapter
          }
        });
  }

According to step 2 in the figure, execute the create method, which return s the OKHttpService proxy object of a dynamic proxy

3.4 dynamic Proxy.newProxyInstance()

Dynamic proxy mode: during the running of the program, the proxy object of OkhttpService.class is dynamically created through the reflection mechanism, and some other operations are added to the methods in the object

Personally, I think Square company chose to use the dynamic proxy mode, which should be used to automatically create an empty implementation for the interface. His main concern is not to call the original method, but to perform key enhancement operations in invoke(), eliminating the need to create source code for the instantiation of each network request interface

Proxy.newProxyInstance(ClassLoader,Class<?>[] interfaces,InvocationHandler)
  • The ClassLoader object of the ClassLoader interface OKHttpService needs to be used to create an instance in the reflection mechanism
  • Class needs to generate the original interface of the proxy class
  • When invoking the login() method of the proxy class object, InvocationHandler will call back the invoke() method in h, and the proxy operation is added here
//The following is the key code of newProxyInstance
        final Class<?>[] intfs = interfaces.clone(); //If the interface does not override clone(), the default is to make a shallow copy, that is, the instance objects are the same and the references are different
        Class<?> cl = getProxyClass0(loader, intfs);//Generate or get (in cache) the Class object of the proxy Class
        final Constructor<?> cons = cl.getConstructor(constructorParams);  //Get construction method through Class object
        return cons.newInstance(new Object[]{h});  //Create instance objects through construction methods

For the newProxyInstance() of the dynamic proxy Class, there are only a few lines of logic. It should be noted that the Class object obtained by getProxyClass0() is a singleton mode. For the first time, an instance will be created and stored in the WeakCache for caching, and then the Class object will be obtained directly from the cache. For other operations, most of them are common calls for instantiating objects by reflection mechanism

3.5 Proxy callback interface InvocationHandler

For InvocationHandler, readers only need to remember that the internal invoke() is called when the developer calls the login() method, that is, the login() of the proxy class object (the proxy implementation class of OKHttpService) returned through newProxyInstance(), which will trigger invoke() to realize the proxy function. It is also a proxy function before and after the internal method.invoke()

public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
              //... do some extension operations
              method.invoke(this, args); //This line is the real call to the original object method
              //... including lower side
              }
 
 implement httpService.login()Time,invoke()It was executed
 

The above is the brief source code of Proxy.newProxyInstance(). Let's go back to the source code of create() and return the proxy object. So far, the proxy object of OKHttpService interface has been created. Next, we will use the proxy object httpservice to call the login() method

 httpService.login("19834431149", "123123")

As mentioned above, when the developer calls the login() method, it will execute invoke() in the InvocationHandler. The specific implementation of this method is as follows:

public Object invoke(Object proxy, Method method, @Nullable Object[] args){
 		//... three lines of core code
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);//Load ServiceMethod object
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);  //Create OkHttpCall
            return serviceMethod.adapt(okHttpCall);//Adapt the network request adapter inside servicemethod to call
}
3.5.1 ServiceMethod object instantiation

A servicemethod object corresponds to a method in a network interface. The object stores various parameters and method attribute annotations used by the method to process network requests. When reading the source code, find out the nature of the servicemethod, refer to the structure diagram, and call loadServiceMethod() to incorporate it into the method

	ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }

Similarly, the object will be obtained from the cache first, and the instance object will be created only when it is null. It is also a singleton object. In the builder (), some information of the resolution method (parameters, annotations, parameter annotations) and the attributes in the retrofit object will be saved to the serviceMethod object
Operating in the build() method, the structure diagram lists three key execution codes

callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();

//Let's take a look at each specific operation

3.5.1.1 createCallAdapter()

Used to obtain a network request adapter from the network request adapter factory

{Internal logic
 Type returnType = method.getGenericReturnType();//Gets the return value type of the method
 Annotation[] annotations = method.getAnnotations(); //Gets the annotation of the method
  return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
}
Enter into retrofit.callAdapter()Internal nextCallAdapter()in
{
	  for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
	  //callAdapterFactories is the collection of network request adapter factories added when creating retrofit.build(). It is assigned to the ServiceMethod object when loadServiceMethod(). If you can't remember, go back to loadServiceMethod's Builder() or retrofit's build() to deepen your impression
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
	  //get(i) get the network request adapter factory. Executorcaladapterfactory() is added to the collection by default. Of course, multiple adapter factories can be added through addCallAdapterFactory() of retrofit
	  //get(returnType, annotations, this) obtains the network request adapter in the factory according to the return value type and annotation
}

Take ExecutorCallAdapterFactory as an example: after get(i) gets this object, it calls the internal get(returnType, annotations,retrofit) method.

//If rxjava is used internally in get(), when the get method is executed, the threads in the returned object are switched internally through adapt(); To achieve
class ExecutorCallAdapterFactory{
	 CallAdapter<?,?> get(...){
return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
      }
      @Override public Call<Object> adapt(Call<Object> call) { //This method is called later
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
    }
}
3.5.1.2 callAdapter.responseType()

This operation is to call the responseType() above to check the definition of the return value type

3.5.1.3 createResponseConverter()

It is used to obtain a response data converter, or continue to click in and execute retrofit.responseBodyConverter(responseType, annotations);
Go in again until you enter nextResponseBodyConverter()

Converter<ResponseBody, T> nextResponseBodyConverter(){
	  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;
        }

It can be found that the work done here is actually similar to createCallAdapter(). The default data conversion factory is BuiltInConverters. The two internal methods of this class, requestBodyConverter() and responseBodyConverter, call responseBodyConverter() here to return the default return value type, that is, converter < ResponseBody, ResponseBody > type. If GsonConverterFactory is used, According to the returned value type of login() passed, it will be converted to the corresponding type of response object through gson. If you are interested, you can also try to look at the source code. It is very simple. I won't go into depth for the reason of length

3.5.1.4 build() other operations

build() contains not only these operations, but also a simple look to help understand the nature of ServiceMethod

parseMethodAnnotation(annotation);  //Check the annotation of the login() method
parameterHandlers = new ParameterHandler<?>[parameterCount]; //Save the parameter list and check it
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];//Save the Parameter annotation and check it
 The rest of the pile if Conditions are used to judge, such as request headers,body,And in the process of adding some parameter annotations,Is it used in conjunction with method annotations,

From here, we can know that ServiceMethod not only stores the parameters and attributes of method (i.e. login() method), but also stores various adapters, converters and some related check s required by http protocol. It needs to be understood in combination with http related knowledge

Combined with the structure diagram, return to the second line of logic of the invoke method

3.5.2 OkHttpCall instance

This class is easy to understand. It creates an OkHttpCall object, implements the Call interface, and rewrites the execute() and enqueue methods. In subsequent calls to the network request adapter, this method will be executed to access the network through OkHttp requests
Last line

3.5.3 serviceMethod.adapt(okHttpCall)

Adapt the created okHttpCall object to the network request adapter in serviceMethod, internal return callAdapter.adapt(call); And where did you get this callAdapter?
It is selected in createCallAdapter() in serviceMethod.loadServiceMethod(method), so the reference here is the adapter () of the previously selected network request adapter class object. Let's take a look at its default network request adapter, that is, executorcaladapterfactory

   @Override public Call<Object> adapt(Call<Object> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      //Returns a default ExecutorCallbackCall
      }

When serviceMethod.adapt(okHttpCall) is executed, the adapt() method here is actually called (by default) and a call object is returned

ok, let's recall here that when the client calls the login() method, it uses the proxy instance object httpService returned by Proxy.newProxyInstance(), and will call back the invoke() method in the InvocationHandler to perform proxy operations. Finally, it returns a call object through adapt() (this process is mainly understood in combination with the structure diagram)

3.6 login().enqueue() execute network request

As mentioned above, the login() method will return a Call object, which overrides enqueue(),execute() and other request processing methods. Here, enqueue() executes enqueue() in this object. Let's take a brief look at the operation of enqueue() in the default executorcallbackcall < > (callbackexecutor, Call)

public void enqueue(final Callback<T> callback) {
 
  	  //delegate is the second parameter passed when instantiating ExecutorCallbackCall. The object is OkHttpCall
      //That is, enqueue() is delegated to OkHttpCall to execute
          delegate.enqueue(new Callback<T>() { 
      @Override public void onResponse(Call<T> call, final Response<T> response) {
      //Running callbackExecutor in Android environment defaults to mainthreadexecution. The figure shows the relevant introduction
      //When the network request succeeds, the execution switches back to the main thread and calls back the onResponse() method of the callback object of the client
          callbackExecutor.execute(new Runnable() {  
            @Override public void run() {
              if (delegate.isCanceled()) {
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }
	//Failure will also switch to the main thread to callback onFailure() of the callback
       @Override public void onFailure(Call<T> call, final Throwable t) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
            }
          });
        }
      });
      }

You can see from the code line delegate. Enqueue (New callback < T > () that there is still no real network request execution here. Next, let's study delegate.enqueue(), that is, enqueue() of OkHttpCall

3.6.1 OkHttpCall.enqueue()
@Override public void enqueue(final Callback<T> callback) {
 // ... omit
  call = rawCall = createRawCall();
  call.enqueue(new okhttp3.Callback() {....
 //... see later
 }

Inside, a createRawCall() method is executed, in which okhttp3.Call call = serviceMethod.toCall(args) is executed, and then enter the method

/** Builds an HTTP request from method arguments. */
//The main two lines of code
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,contentType, hasBody, isFormEncoded, isMultipart);
return callFactory.newCall(requestBuilder.build());

Here, first create a request object according to the attributes of the login() method in the serviceMethod and the parsed annotation information, and finally
return callFactory.newCall(requestBuilder.build()) By default, the callfactory is OkHttpClient, so we won't show it in depth. Finally, a RealCall is returned inside. If you are interested, you can go in and learn about the source code of OkHttpClient. Here, the underlying network request of Retrofit is exposed in front of us. From here, we can see that the underlying network used by Retrofit is OkHttpClient
Then we return to the `enqueue() method and then call enqueue() of the call(RealCall) object to process the network request. As for the next thing, it is OkHttp's bottom line.
Towards the end, let's finally look at the Callback object passed in by calling enqueue() with RealCall

 call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
        Response<T> response;
        try {
         //After successful access, the response body will be converted to the format required by the user
          response = parseResponse(rawResponse);  
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        try {
        //Finally, call back the callback object method of the upper layer
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
      //As for the remaining part, it is the upper interface of access failure callback, which is relatively simple and is not summarized
3.6.1.1 parseResponse(rawResponse)

Finally, let's take a look at how to perform data conversion

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();//Get body response body
  int code = rawResponse.code();
  if (code < 200 || code >= 300) {.....} //If the response code is not within 200, callback failure() for request failure
  if (code == 204 || code == 205) {
Response.success(null, rawResponse)  //Returns a successful callback of an empty response body
}//Those within 200 have been visited successfully

  T body = serviceMethod.toResponse(catchingBody);//The key is in this line
  //responseConverter.convert(body) internally executes a line of code that uses the response body conversion factory to call convert() for data conversion

Here is an example of the gsonresponsebodyconverter (created by the gsonconverterfactory)

 @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);   //It is to use the adapter to convert the binary stream of the response body into the corresponding return value type. The specific type has been determined and assigned a value in the serviceMethod
      if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
        throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
      ...
  }

At this point, the delegate.enqueue() is almost over, successfully callback onResponse and failed callback onFailure(). Finally, the client can see the network access status

4 Summary

Originally, I just wanted to briefly introduce the structure diagram, but the more I wrote it, the longer the length of the whole article became. However, I finally hope to read this article, especially after reading the structure diagram, it is worthwhile to have a structural understanding of the Retrofit framework. Finally, if you want to obtain the pdf file of the structure diagram, leave a message in the comment area!!

appendix

HTTP status code table

Tags: Java Android

Posted on Thu, 02 Dec 2021 15:39:30 -0500 by domainshuffle