Understand the principle of Retrofit ~ transparent

What are RESTful APIs?

Summarize the REST ful API in one sentence: When we use the HTTP protocol for data transmission, we should abide by the rules of HTTP, including request method, resource type, Uri format, etc..

Author: Drew_MyINTYRE
Link: https://www.jianshu.com/p/3ec1b6a99612

Why set the request in the form of (interface + annotation)?

迪米特法则: Also known as the principle of minimum knowledge, that is, to minimize unnecessary dependencies between modules, that is, to reduce the coupling between modules.

门面模式: A design pattern based on Demeter's law, which aims to control the access of complex modules/systems more uniformly. For example: Now we want to do a function of getting pictures, first get from the local cache, if there is no cache, get from the network and then add it to the local cache, if you don’t do anything, then you have to write the cache logic every time you get a picture, the more you write, the higher the possibility of errors. At this time, the cache function can be encapsulated through the facade mode, and only one entry for obtaining pictures is exposed, so that the caller can use it more conveniently and with higher security. In fact, functional programming is also a product of the facade model.

Why Design Through the Facade Pattern ApiService?

RetrofitThe general process of making a request with is as follows:

interface ApiService {
    /**
     * 获取首页数据
     */
    @GET("/article/list/{page}/json")
    suspend fun getHomeList(@Path("page") pageNo: Int)
    : ApiResponse<ArticleBean>
}


//构建Retrofit
val retrofit = Retrofit.Builder().build()


//创建ApiService实例
val apiService =retrofit.create(ApiService::class.java)


//发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求)
apiService.getHomeList(1)

We all know Retrofitthat is just OkHttpan encapsulation of . If you use it directly , you have to do a lot of tedious work when constructing it. The most terrible thing is that Request may be constructed in multiple places (ViewModel, Repository...), and the more scattered the writing, the more difficult it is to troubleshoot errors OkHttp. RequestRetrofit attaches all the necessary information required by the Request to the method in the form of annotations (it is still an abstract method, try to remove all redundant information), as a user, only needs to call the corresponding method to realize the request. As for how to parse, construct, and initiate requests, Retrofit will do internal processing, and the caller does not want or need to know, so Retrofit helps the caller shield some useless information through the facade mode, and only exposes the only entry, so that the caller can focus more on business development . This mode is also used in our commonly used Room and GreenDao.

Dynamic proxy is not a tool

The Retrofit build looks like this:

Retrofit.Builder()
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .baseUrl(ApiConstants.BASE_URL)
    .build()

Very typical builder mode, you can configure OkHttp, Gson, RxJavaand so on, and finally build()do the build operation through , follow build()the code:

# Retrofit.class
public Retrofit build() {


        // 1. CallAdapter 工厂集合
        List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
        callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));


        // 2. Converter 工厂集合
        List<Converter.Factory> converterFactories =
                new ArrayList<>(
                        1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
        converterFactories.add(new BuiltInConverters());
        converterFactories.addAll(this.converterFactories);
        converterFactories.addAll(platform.defaultConverterFactories());


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

What is a dynamic proxy?

A dynamic proxy can dynamically create an instance of at runtime interface. The proxy class we Proxy.newProxyInstancegenerate through will be intercepted by the method when we call any method of the interface. InvocationHandler#invokeAt the same time, in this method, we can get the parameters passed in, and then do corresponding processing according to the parameter value.

Every time CRUD will do a manual reporting operation, this is obviously a template code, how to solve it? Let's look at the dynamic agent:

interface ToDo {
    fun buyHouse()
}


/**
 * 中介
 */
class Middlemen(private val any: Any) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        return method?.invoke(any, *(args ?: arrayOfNulls<Any>(0)))
    }
}


/**
 * 买家
 */
class Buyer : ToDo {
    override fun buyHouse() {
        print("中介,请帮我买房子")
    }
}


// 最后利用代理对象实现购房的动作:
fun main(args: Array<String>) {
        val toDo: ToDo = Buyer()
        val dynamicMid = Proxy.newProxyInstance(
            toDo.javaClass.classLoader, toDo.javaClass.interfaces,
            Middlemen(toDo)
        ) as ToDo
        dynamicMid.buyHouse()
    }

The most important part of dynamic proxy lies in the realization of proxy object InvocationHandlerand rewriting invokemethod. When the proxy object represents the request of the delegator, no matter how many requirements there are, when the proxy is executed, it will enter invoke()the method. This is the key point, circled and tested later.

Dynamic proxy gets ApiService

# Retrofit.class
public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];


              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // ⚠️
                if (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);
              }
            });
  }

invoke() This is a proxy method that invokes ApiService中any method and executes it, where the parameters methodand argsrepresent ApiServicethe corresponding methods and parameters. There is one in the return value isDefaultMethod, here if it is the default method of Java8 to execute directly, after all, we only need ApiServicethe method in the proxy. After repeated screening, the heavy responsibility finally fell on loadServiceMethod, which is also Retrofitthe core method in , let's follow it up:

loadServiceMethod(method)It mainly preliminarily processes the information in the network request method. When we create api service specific interface, we will add annotations ( , , ...), @GETparameters @POST( @PUT, @Path... @Query), etc. This method is to analyze the annotations, parameters, etc. in the interface. After analyzing the interface, a RequestFactoryrequest factory object is generated, and RequestFactoryone is created using this object CallAdapter.

CallAdapter: Adapter, we define API Servicethe return value of the method by default Callas type, but sometimes we customize the return type, for example, what should we do when we RxJavacombine with and return Observableor type? The role of is to help developers adapt to these return types. You can return any type of data you define through .SingleCallAdapterCallAdapter#adapt

# Retrofit.class
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;
  }

Creating an object is achieved ServiceMethodthrough its static method , followed by this method:parseAnnotations

# HttpServiceMethod.class
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;
        //1.获取adapterType,默认为method返回值类型
        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 {
            }
            adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
            annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
        } else {
            adapterType = method.getGenericReturnType();
        }
        //2.创建CallAdapter
        CallAdapter<ResponseT, ReturnT> callAdapter =
                createCallAdapter(retrofit, method, adapterType, annotations);
        Type responseType = callAdapter.responseType();
        //3.创建responseConverter
        Converter<ResponseBody, ResponseT> responseConverter =
                createResponseConverter(retrofit, method, responseType);


        okhttp3.Call.Factory callFactory = retrofit.callFactory;
        //4.创建HttpServiceMethod类型具体实例
        if (!isKotlinSuspendFunction) {
            return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
        }
        //兼容kotlin suspend方法
        else if (continuationWantsResponse) {
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (HttpServiceMethod<ResponseT, ReturnT>)
                    new HttpServiceMethod.SuspendForResponse<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
        } else {
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (HttpServiceMethod<ResponseT, ReturnT>)
                    new HttpServiceMethod.SuspendForBody<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
                            continuationBodyNullable);
        }
    }




# ServiceMethod.class
 static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        // ⚠️
        RequestFactory requestFactory =
            RequestFactory.parseAnnotations(retrofit, method);
        Type returnType = method.getGenericReturnType();
        ...
        // ⚠️
        return HttpServiceMethod.parseAnnotations(retrofit,
                method, requestFactory);
    }




# HttpServiceMethod.class
@Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
OkHttpCall 其实是对 OkHttp 中的realCall 进行了一层包装,realCall 是如何创建的?

First create OkHttpClientand Requestobjects, Requestthen create an RealCallobject, use it to perform asynchronous enqueueor synchronous executeoperations to send requests, and listen to the feedback of request failure or success Callback.

val okHttpClient = OkHttpClient()
val request: Request = Request.Builder()
    .url("https://cn.bing.com/")
    .build()


okHttpClient.newCall(request).enqueue(object : Callback{
    override fun onFailure(call: Call, e: IOException) {
    }


    override fun onResponse(call: Call, response: Response) {
    }
})

There are three main classes that need to be explained here: OkHttpClient, Requestand RealCall.

  • OkHttpClient: Equivalent to the configuration center, it can be used to send HTTP requests and read their responses. It has many configurations, such as connectTimeout: the timeout period for establishing a connection (TCP or TLS), readTimeout: the timeout period from initiating a request to reading the response data, Dispatcher: a scheduler, used to schedule network requests initiated by the background, and so on. There are other configurations to view the source code.

  • Request: A request class that mainly sets the network request Url, request method (GET, POST...), request header, and request body.

  • RealCall: is returned RealCallby the method, and is one of the core classes for executing requests. It is used to connect the application layer and the network layer, that is, combine and to initiate asynchronous and synchronous requests.newCall(Request)OkHttpOkHttpOkHttpClientRequest

loadServiceMethod(method).invoke(args);, loadServiceMethod(method)what is returned is HttpServiceMethodthat invoke()an instance of is created OkHttpCall, which is actually OkHttpa series of operations on .

Let's take a look at OkHttpCallthe details of the network request in :

@Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");


    okhttp3.Call call;
    synchronized (this) {
      ...
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        }
      }
    ...
    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              //解析请求返回值
              response = parseResponse(rawResponse);
            }
              ...
            try {
              callback.onResponse(OkHttpCall.this, response);
            }


          @Override
          public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }


          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            }
          }
        });
  }

Analyze the source code above:

1. First, a createRawCall()method is called internally to create rawCall, which rawCallactually refers to Okhttp3the call of , which is OkHttpa scheduler for making network request calls;

2. After creating the OkHttp call, start calling enqueueto make an asynchronous request, and find that the callback that responds in the asynchronous request belongs to okhttp3.Callback, and the returned results are also all okhttp3.Response. From here, you can roughly know that retrofitthe network request is actually implemented OkHttpby .

3. okhttp3.ResponseThis response is not convenient for developers to use directly, so retrofitafter receiving the result, a new round of parsing is performed on the response result response = parseResponse(rawResponse)and Responsereturned to the developer in the form of an object.

In addition, there is the last line: adapt(call, args); Its interior is actually CallAdaptedcalled by an adapter, as follows:

protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }

adaptis an abstract method, which passes in the one just created above OkHttpCallas a parameter. Adapt means adapter, the meaning of adapter, then we can guess, OkHttpCallis it passed in for the purpose of OkHttpCallre-adapting? However, OkHttpCallit is already possible to make network requests, why do you need to use it CallAdaptedfor re-adaptation? ⁉️This question should be written down first, and answered later.

From the above analysis, we know that Retrofitthe reason why it is easy to call is because the annotations such as @GET, @POST, @Pathand request parameters are parsed internally, so that developers only need to care about whether their newly added request methods meet the specifications. The most important thing to do with the above-mentioned dynamic agent is to create it CallAdapted. CallAdaptedIn fact, it is just an adapter. What you need to know is what it adapts to? Do you still remember that we raised a CallAdaptedquestion about : let's see how he was created first? ⁉️

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {


  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {


    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;
      }


      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }


    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
  ...


    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);


    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    }


public CallAdapter<?, ?> nextCallAdapter(
      @Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
    int start = callAdapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }

CallAdapterAccording to the types of returnTypeand , search from the factory to return the corresponding network request adapter, where returnType refers to the return value type of the method in the network request interface, such as , etc. It refers to the annotation type, such as , , etc.annotationscallAdapterFactoriesCallObservableannotations@GET@POST

An adapter factory is also mentioned here callAdapterFactories. The difference CallAdapteris that it is queried from this factory. If there is a search, it must be added. CallAdapterHow is the adapter added to the factory class? ⁉️This callAdapterFactoriesvariable belongs to Retrofitthe class, and it is found that it is Retrofitpassed in by the constructor, that is, it is Retrofitassigned a value when it is initialized.

RetrofitThe initialization of is created by a builder pattern. In the method Retrofitof build(), the addition of the adapter factory to its adapter is found:

public Retrofit build() {
     ...
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }


      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      //添加适配器callAdapter
      callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
     ...
      return new Retrofit(
          callFactory,
          baseUrl,
          unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories),
          callbackExecutor,
          validateEagerly);
    }

platform.defaultCallAdapterFactoriesIt refers to a default adapter factory on the Android platform. When we don't use a custom adapter factory, we add this default factory. The custom adapter factory is mentioned here. In fact, when we use Retrofit, sometimes it will be RxJavacombined with . For example, when creating Retrofit, addCallAdapterFactoryit will also RxJava2CallAdapterFactorybe added to callAdapterFactories.

mRetrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

The purpose of adding the call adapter factory is to support Callreturn types of service methods other than , such as support Observable, Singlereturn type, etc. When callAdapterFactoriesthe aggregator adds a default adapter factory, a parameter is also passed in callbackExecutor, callbackExecutorwhich is a default thread scheduler on the Java8 or Android platform. Its function involves a thread switching problem, that is, how to Retrofitswitch the sub-thread to the main thread that needs to be analyzed later? ⁉️

When the above source code was created CallAdapter, an object was also created at the same time, that is Converter.

Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
Converter 数据装换器。它的添加和获取方式和 CallAdapter 类似。在Retrofit 初始化时添加到 List<Converter.Factory> 工厂集合,根据类型 Type 取出不同的 Converter,对返回的网络响应,做出数据的转换,例如转换成实体类。基本逻辑与 CallAdapter 类似。
mRetrofit = new Retrofit.Builder()
                .client(mOkHttpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create()
                .build();
总结一下上面讲的:

1. At the beginning, loadServiceMethodthe method is called in the dynamic proxy, and the annotations, parameters, header information, etc. in the interface method are parsed;

2. According to the return type of the interface method, query from the adapter factory collection, generate the corresponding adapter, and CallAdapterdistinguish whether RxJavait Observableis Singleor Callother types . Take out the data converter in the same way ;RetrofitaddCallAdapterFactory()Converter

3. Using the method generated above CallAdapter, call invokethe method to create OkHttpCallthe object, that is, use to make asynchronous or synchronous network requests for the request information OkHttp, and perform entity class conversion on the response results;

4. After creating , use OkHttpCallthe adapter queried above to call and return the or object of .CallAdapteradaptRxJavaObservableSingleCall

How does Retrofit switch the child thread to the main thread?

When adding the default adapter factory defaultCallAdapterFactories, it will callbackExecutorbe used as a parameter, then its specific implementation is also in this default adapter factory. Let's see callbackExecutorwhat's going on inside.

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;
    ...


    @Override
    public void enqueue(final Callback<T> callback) {


      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));
            }
          });
    }

Learned in the above code, callbackExecutorthat is Executor, a thread scheduler. enqueueAn asynchronous network request is executed in the implementation of Call , and a thread is also executed in the response delegate.enqueueof the request . Here is a question, why call another thread in an asynchronous request? We know that is a thread scheduler, so what exactly does it implement internally?onResponseonFailureExecutorcallbackExecutor


The default callbackExecutoris created in Retrofitthe initialization of callbackExecutor = platform.defaultCallbackExecutor();

static final class Android extends Platform {


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


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


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

platformIt is an Android platform. defaultCallbackExecutorIt actually calls new MainThreadExecutor() internally. It is clear that handler.post(r) uses Handler internally to throw the response to the main thread. This is Retrofitthe core of switching the child thread to the main thread.

Why does Retrofit use dynamic proxy?

What are the advantages of dynamic proxies? Instead of exposing the real principal, different agents are created according to different delegations, and things are done through the agent.

So what shortcomings does Retrofit make up for OkHttp?

When OkHttp is used, is the configuration of request parameters very cumbersome, especially when some forms are submitted, which are smelly and long, and Retrofit makes up for this shortcoming, using @GET, @POST, @Path, @Body and other annotations and some parameters to easily construct requests.

Dynamic proxies come into play when Retrofit creates different interfaces. Whenever different interface methods are executed, the dynamic proxy will intercept the request, analyze the annotations and parameters in the interface, construct different Requests, and finally hand it over to OkHttp for actual execution.

Retrofit combines dynamic proxies without caring about real interface methods, and manages standardized interfaces in a unified manner, parses annotations and parameters in a unified way, and stitches them into requests.

Summarize:

RetrofitIn fact, it is OkHttpthe encapsulation class of , and internal network requests still rely on it OkHttp, so Retrofitwhat has changed after encapsulation?

  • The interface request is more convenient, and annotations @GET, @POST, @Path, @Body, etc. form a network request;

  • By default, it helps developers parse responseBody, and you can also customize the parsing strategy;

  • Retrofit helps developers to switch threads;

  • Retrofit gives developers more permissions to customize and adapt network requests.

Follow me for more knowledge or contribution

c8e4316ea48f3690e41ea704330e0d40.jpeg

067e63b3d8d4d764ca23da3ae736f095.jpeg

Guess you like

Origin blog.csdn.net/c6E5UlI1N/article/details/131218493