In the previous article “Android Network framework OkHttp source code parsing” we analyzed OkHttp source code. Now let’s take a look at OkHttp’s sibling, Retrofit. About the Retrofit of the use of annotations, you can refer to the official document: square. Making. IO/Retrofit /.

Retrofit is also an open source library released by Square, which is a type-safe Http client for Android and Java. In essence, Retrofit uses Java’s dynamic proxy, internally using OkHttp for network access, and can be done by specifying “request adapters” and “type converters” : Request adaptation, conversion of method parameters to OkHttp requests, and conversion of responses to Java types.

1. Basic use

One of the nice things about Retrofit’s design is that it decouples the “request adapter” and “type converter” we mentioned above using policy patterns. Users can customize their own type converters according to their needs by implementing specified interfaces. So, when we use Gson for transformation and RxJava2 for adaptation, we need to specify the following three dependencies:

API 'com. Squareup. Retrofit2: retrofit: 2.4.0' API 'com. Squareup. Retrofit2: converter - gson: 2.4.0' API 'com. Squareup. Retrofit2: adapter - rxjava2:2.4.0'Copy the code

Then, we need to declare our API with an interface in our code based on the information about the API interface:

public interface WXInfoService {
    @GET("/sns/userinfo")
    Observable<WXUserInfo> getWXUserInfo(
        @Query("access_token") String accessToken, @Query("openid") String openId);
}
Copy the code

Here WXUserInfo is a Java object generated from Json returned by the API interface. We can then get a proxy object for this interface as follows:

WXInfoService wXInfoService = new Retrofit.Builder()
    .baseUrl("https://api.weixin.qq.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClient)
    .build().create(WXInfoService.class);
Copy the code

We can then use the object and call its methods to get the information returned by the interface:

Disposable disposable = wxInfoService.getWXUserInfo(accessToken, openId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(wxUserInfo -> { /*... Get the results and process them... * /});Copy the code

We only used Retrofit’s basic GET interface above. Of course, Retrofit itself is much more versatile than that, and for more information about its use, refer to its official documentation.

Dynamic proxy: Where the magic happens

We used Retrofit for web requests above, but it actually uses OkHttp internally to complete web requests. Dynamic proxies are a big part of the simplicity of just defining an interface and calling its methods to get the result of the request.

When we use retrofit.Builder’s create() method to get an instance of WXInfoService, we actually return the proxied object. Internally, the method calls the Proxy static method newProxyInstance() to get a post-proxy instance. To illustrate how this works, we write an example:

public static void main(String... args) { Service service = getProxy(Service.class); String aJson = service.getAInfo(); System.out.println(aJson); String bJson = service.getBInfo(); System.out.println(bJson); } private static <T> T getProxy(final Class<T> service) { InvocationHandler h = (proxy, method, args) -> { String json = "{}"; If (method.getName().equals("getAInfo")) {json = "{A request result}"; } else if (method.getName().equals("getBInfo")) {json = "{B request result}"; } return json; }; return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[]{service}, h); } Output of the program: {result of A request} {result of B request}Copy the code

In the example above, we use getProxy() to getA subsequent instance of the proxy, and then call its getAInfo() and getBInfo() methods in sequence to simulate calling interface A and interface B, and get the result of A request and the result of B request in sequence.

The effect above is similar to the process we used to access the interface with Retrofit. To illustrate what happens in this process, we need to look at the newProxyInstance() method here:

public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) { // ... }Copy the code

This method takes three arguments:

  1. The first is the classloader;
  2. The second is the Class type of the interface;
  3. The third is a handler, which you can think of as an interface for callbacks. When our proxy instance fires a method, it fires the method of the callback interface.

InvocationHandler is an interface that internally defines a method as follows:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Copy the code

The method also receives three parameters, the first being the proxy instance that triggered the method; The second is the method of triggering; The third is the argument to the triggered method. The return result of the invoke() method is the result of the method execution of the proxy class.

So, once we know the definition of the newProxyInstance() method, we can conclude as follows: After we get a proxy instance service using the newProxyInstance() method and call its getAInfo() method, the method information and parameter information are passed to H’s invoke() via Method and args, respectively. So, the net effect is that h’s invoke() is triggered when we call the Service’s getAInfo(). In the invoke() method we know that the triggered method is getAInfo according to method. We then return its corresponding request from the invoke() method as the result of service.getainfo ().

So, we can summarize Retrofit’s rough workflow: After we get a proxy instance of an interface and call its getWXUserInfo() method, the request parameters of that method are passed to the invocationHandler.invoke () method of the proxy class. Then in this method, we convert this information into an OkHttp Request and access it using OkHttp. Once we get the results from the network, we use a “converter” to convert the response to the Java type specified by the interface.

Following the basic flow of Retrofit request processing above, let’s take a look at what actually happens inside Retrofit’s proxy methods.

Source code analysis of Retrofit

3.1 create a Retrofit

According to the example above, when using Retrofit, we first need to create an instance of Retrofit using the builder of Retrofit. There are several important approaches that the builder should mention here:

3.1.1 addConverterFactory method

This method is used to add a Converter.factory to Retrofit. Converter.Factory, as its name suggests, is a Factory model. It is an interface required to implement two important methods. Each method needs to return a converter: a converter for a certain data type to the request body, and a converter for the response body to the data type we need. When we use Gson to complete the transformation, then we will need to use GsonConverterFactory. The create () to get a suitable for Gson Converter. The Factory.

public Builder addConverterFactory(Converter.Factory factory) {
  converterFactories.add(checkNotNull(factory, "factory == null"));
  return this;
}
Copy the code

3.1.2 addCallAdapterFactory method

The callAdapter. Factory is used to get the CallAdapter object that converts the native OkHttp Call to the request type we specify. For example, in the example above, we convert it to Observable

. Here is the definition of this method:

public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
  callAdapterFactories.add(checkNotNull(factory, "factory == null"));
  return this;
}
Copy the code

3.1.3 the build method

Once the parameters have been set according to the user’s customization, the build() method can be called to get an instance of Retrofit. In this method, the “adapters” and “converters” passed in by the above method are added to their respective lists, and then an instance of Retrofit is new and returned.

3.1.4 summary

To illustrate the role of the CallAdapter and Converter, we draw the following figure:

As you can see from above, the CallAdapter is mainly used to convert a request to the type we specify. For example, in our original example, we convert the request to Observable

. If the transformed request is an Observable, then subscribing to the transformed request initiates the OkHttp network request process.

Before making a network request, Converter is used to convert the request parameters into a RequestBody. The advantage of using it as an interface here is decoupling. For example, we used Gson for the conversion process above, but you can also use custom converters to use other frameworks such as Moshi etc. When we get the response, we’ll use the Converter again to convert the ResponseBody to the type we requested. Such as WXUserInfo.

From the above we can see that Retrofit is designed in such a way that the decoupling of the above two processes (policy pattern + factory pattern + adapter pattern) is very important. One is to convert the request into an Observable, and one is to convert the RequestBody and ResponseBody into the RequestBody and ResponseBody required by OkHttp. For the former, whether we use RxJava 1 or RxJava 2, we simply pass in a CallAdapter. For the latter, it doesn’t matter which Json conversion framework we use, as long as we implement the Converter interface.

3.2 Obtaining a Proxy Instance

3.2.1 Platform Division: Platform

Once an instance of Retrofit is created, we can use its create() method to get the service instance behind the proxy. Here is the definition of this method. In this case, the validategbit/s variable is used to determine whether the methods of the incoming service interface are resolved immediately. We then use the static method of Proxy to get a Proxy instance.

public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); // If (validatewaits) {// Whether Service methods are parsed immediately eagerlyValidateMethods(service); Return (T) proxy.newproxyInstance (service.getClassLoader(), new Class<? >[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @nullable Object[] args) throws Throwable {// This is an Object method, Class == object.class) {return method.invoke(this, args); } // If the default method is used, Then use the Java8 platform method performs the if (platform. IsDefaultMethod (method)) {return platform. InvokeDefaultMethod (method, the service, the proxy, args); } // Get information about the service method, And wrap it as ServiceMethod ServiceMethod<Object, Object> ServiceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.adapt(okHttpCall); }}); }Copy the code

The eagerlyValidateMethods() method here is defined as follows:

private void eagerlyValidateMethods(Class<? Platform = platform.get (); For (Method Method: service getDeclaredMethods ()) {/ / determine whether this Method is the default Method if (! platform.isDefaultMethod(method)) { loadServiceMethod(method); }}}Copy the code

It immediately parses the methods of the service interface and puts the parsed results into a cache. This way, when the ServiceMethod is triggered, the parsed ServiceMethod can be used directly from the cache. The method first determines whether the service method should be loaded based on the platform on which the program is currently running. Because, after Java 8, we can add a method of type default to the interface, so we don’t call loadServiceMethod() to parse it if it is of type default. Instead, call invokeDefaultMethod() from the Java8 platform. In invokeDefaultMethod(), an instance is created based on the incoming information and the method that triggers it is used with reflection. At this point, the default method is triggered indirectly.

To determine the platform, use the following code:

platform.isDefaultMethod(Method)
Copy the code

Here platform is obtained by calling platform.get (). It tries to use reflection in the get() method to retrieve a class that is unique to the Java8 platform to determine if it is a Java8 environment. In Retrofit, two classes, Java8 and Android, are provided to differentiate between platforms and determine which instance to return based on the running environment.

Therefore, Platform applies the policy pattern to treat different platforms differently. In the current version, its main role is to handle methods of type default.

3.2.2 Service Parsing method: ServiceMethod

The loadServiceMethod() method attempts to retrieve the ServiceMethod instance from the cache and returns it if it does. Otherwise, create one using the Builder pattern, put it in the cache, and return it.

ServiceMethod<? ,? > loadServiceMethod(Method Method) {// Get ServiceMethod from cache <? ,? > result = serviceMethodCache.get(method); if (result ! = null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); If (result == null) {// Create ServiceMethod instance result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); } } return result; }Copy the code

The build process for ServiceMethod is relatively simple. You simply pass in the current Retrofit instance and ServiceMethod method and call its build() method to complete the creation process. In the build() method, parsing method is done, such as determining what type of request it is based on annotations, parsing request body based on method parameters, and so on.

So, ServiceMethod’s job is to cache the request information corresponding to the ServiceMethod so that we don’t have to parse it again next time. In the meantime, it provides the following methods. Their main purpose is to extract request-specific information from the ServiceMethod:

ToCall () is used to get the Call object for the OkHttp request:

okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
            contentType, hasBody, isFormEncoded, isMultipart);
    // ...
    return callFactory.newCall(requestBuilder.build());
}
Copy the code

ToResponse (ResponseBody) is used to convert the ResponseBody from OkHttp into a Java object, etc. (in this case, WXUserInfo) :

R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
}
Copy the code

Adapt (Call

) converts OkHttp requests to the return type of our service method (Observable

in our example) :

T adapt(Call<R> call) {
    return callAdapter.adapt(call);
}
Copy the code

3.2.3 Request Encapsulation: OkHttpCall

After parsing the ServiceMethod, we have the ServiceMethod instance. We then use it to create an OkHttpCall instance. OkHttpCall, which implements the Call interface defined in Retrofit, calls ServiceMethod’s toCall() method inside the method to retrieve the Call object in OkHttp and then uses it for network access. When we get the result of the request, we use ServiceMethod’s toResponse() to convert the response to the type we specify. Here are some of the more important methods in this class:

  1. execute()Method to execute network requests synchronously:
    @Override
    public Response<T> execute() throws IOException {
        okhttp3.Call call;
        synchronized (this) {
            // ...
            call = rawCall;
            if(call == null) {try {// Create an OkHttp call instance call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); creationFailure = e; throw e; }}}if(canceled) { call.cancel(); } // Execute the request synchronously and parse the resultsreturn parseResponse(call.execute());
    }
Copy the code
  1. createRawCall()Used to create OkHttpCallExample:
Private okHttp3. Call createRawCall() throws IOException {// Use serviceMethod's toCall method to obtain the OkHttp Call instance. okhttp3.Call call = serviceMethod.toCall(args);if (call == null) {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }
Copy the code
  1. parseResponse()Used to convert the OkHttp response to the type defined in our interface. For example, in our case, the return isObservable<WXUserInfo>:
Response<T> parseResponse(okHttp3. Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); / /... ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody); T body = serviceMethod. ToResponse (catchingBody);returnResponse.success(body, rawResponse); } catch (RuntimeException e) { catchingBody.throwIfCaught(); throw e; }}Copy the code

3.3 Work process of Retrofit

Above is a functional breakdown of several key parts of the Retrofit framework design. Let’s take a closer look at which methods of which classes are involved, and when and what roles they play in the process from the methods that trigger the proxy class to the results of the response. Here we still use the original example:

In the figure above, we break down Retrofit’s request process into three steps:

  1. The process of creating a proxy instance: Mainly calls in this processProxy.newProxyInstance()To get a proxy instance. The main parameters involved arevalidateEagerly, which we use to decide whether to parse the methods of the incoming interface immediately. Whenever we parse, we cache the results of the parse.
  2. The process of triggering the proxy methodTriggering the proxy method is the second part of the request. At this point, we callWXInfoServiceProxy-instancegetWXUserInfo()Methods. At this point, it firesInvocationHandler.invoke()Methods. Is called inside the methodServiceMethodTo create the builder patternserviceMethodInstance. When the constructor pattern is calledbuild()Method, will be on the methodgetWXUserInfo()To parse the information. Then, useserviceMethodcreateokHttpCall. Finally, callserviceMethod.adapt()Method will beokHttpCallInstance conversion toObservable<WXUserInfo>. It is used during transformationCallAdapteradapt()Method to complete the adaptation.
  3. The process of executing network requests: got itObservable<WXUserInfo>After that, it needs to subscribe to trigger the network request. The relevant logic is inCallAdapterIn the finish. First, it decides which actuator to use based on whether you’re using synchronous or asynchronous. There are two actuators, and the difference is that one is called internallyOkHttpCallenqueue(), and the other is called in the executorOkHttpCallexecute()Methods. No matter the callenqueue()orexecute(), will be used firstOkHttpCalltoCall()Method to obtain aCallThe request. Is used during the process of getting the requestConverterTo convert an instance into a request body. Once you have the request, use it to access the network. When the response is received from the network, it is usedConverterTo convert the response body into an object. This way, after getting the actual result, the call is madeObserveronNext()Method notifies the observer of the result.

4, summarize

In this article, we started with a brief introduction to the use of Retrofit, and then, since Retrofit is implemented internally using dynamic proxies, we covered dynamic proxies. Finally, we analyzed the source code of Retrofit, first from the design idea, and then from the execution process of each link. Finally, we combined the two and illustrated it with a sequence diagram.

From the above, there are a few things Retrofit can learn from its design:

  1. Use runtime annotations and reflection to simplify the request description, but because reflection is inefficient, the results of one reflection are cached for future use.
  2. Dynamic proxy: The advantage of using an interface to describe a request is that it is concise, and the “description” is its own responsibility. However, we generally need to implement the interface to use it. This tells us that dynamic proxies can be used as well.
  3. Decoupling: As you can see from our diagram above, Retrofit’s design is fairly clear. It decouples the processes of a request. The first is usObservableThe transformation to the request, which is done using an adapter; Then we convert the request body to the response body, which is basically a Json conversion, using a converter. So whether you use RxJava 1 or RxJava 2, either Gson or FastXml can be used with Retrifut.

So that’s our analysis of Retrofit’s source code.