preface
The images above will look familiar to anyone who has used Retrofit2’s web request library. Yes, the image above is a screenshot from Retrofit’s website. A Type-Safe HTTP Client for Android and Java provides A brief introduction to Retrofit in one sentence: Retrofit is a type-safe HTTP client that can be used on Android and Java. Why is Retrofit a type-safe HTTP client? I will mention it in the article and summarize it later.
General steps for network requests
The steps shown above are for a generic web request implemented on Android using Retrofit, which returns a list of octocat users’ repositories on Github. Here’s a breakdown of the image above:
-
The GitHubService interface at annotation 1 is used to declare API methods. API methods must be declared in the interface, the interface must not take type parameters, and the interface must not inherit from other interfaces. If this convention is not followed, the code will throw an exception at runtime. Also, if the interface defines the default and static methods supported by JDK8 and above, the methods of these new features will not be executed. Here, the GitHubService API interface and API methods are validated to ensure type safety before making network requests.
-
Annotation 2 is to build a Retrofit object through a typical builder pattern. In the process of building the object, according to the needs of the project, It is possible to build Retrofit objects with different properties using methods such as Retrofit#addConverterFactory() and Retrofit#addCallAdapterFactory(), which illustrates the benefits of the builder pattern. As for the specific functions of the above two methods, I will describe them in detail below.
-
Note 3: GitHubService interface through Retrofit#create() method, internal through dynamic proxy mode, using bytecode technology to generate a GitHubService proxy object in memory, through the proxy object to call the API method. Then the bottom layer through reflection call to execute the real API method, to achieve during the program running, according to the different interface template, dynamically generate the corresponding proxy object, to execute the API method. This is also a good illustration of the dynamic proxy pattern.
-
Note 4 calls the Call
#enqueue() method through a Call interface implementation. Call
enQueue () is a Call that processes network requests asynchronously and returns the response body of the request, or the error that occurred, via the interface Callback. You can also send the request and return the response body synchronously through Call
#execute().
Network request process analysis
For the analysis of the flow of network requests, I will continue to focus on the diagram above. In general, when reading source code, with questions to drive reading, it will be easier to understand the logic of the source code, not easy to get lost in the details of the code.
So, the first question: in the figure above, what does the code go through when annotation 3 goes through annotation 4?
-
Retrofit#create()
We read Retrofit#create() source code and found that it mainly does validation of the API and uses dynamic proxies to generate implementation objects that return the interface from the validated API. The GitHubService proxy object generated here is typically named $Proxy0. I am not going to describe dynamic proxies in detail here, but if you find that you do not understand the dynamic proxy pattern, you can read my article on the principle of dynamic proxies, or search the web for other articles describing dynamic proxies.
The main logic in annotation 2 is to do Java version validation and perform different methods for different Java versions, mainly for compatibility with different version platforms.
-
Retrofit#loadServiceMethod()
In the figure above, Retrofit#loadServiceMethod() checks to see if there are API methods in the cache serviceMethodCache that need to be executed. Return the API method if it does not exist by parsing the method at annotation 2. Why do you need this caches checking feature? Because in the process of parsing API, the way we by looking at the mark 2, came to ServiceMethod < T >. # RequestFactory parseAnnotations () method, then we through reading process with three code below:
As shown above, in the end we came to mark 1, annotations from API method need to parse method, analysis method of the generic type parameters, analytical method and parameters of the annotations, these would be more time-consuming operation in code, on the mobile network request process, excessive perform time-consuming operation, obviously is not acceptable to us, We all want the results of web requests to appear faster on our phone screens. Hence, the above operation of reading the cache.
-
ServiceMethod#HttpServiceMethod.parseAnnotations()
Let’s go back to figure 4, where we continue parsing the API methods. As we all know, reflection is often used when parsing annotations, as the method’s official comment states: This requires potentially-expensive reflection so it is best to build each service method only once and reuse it. This also validates the need to read the cache above.
There are three main methods in the figure above, which I have highlighted separately. The rest of the omission is mainly a check on API method types to make them safe when requesting the network.
-
Annotation 1 is mainly related to the Retrofit#addCallAdapterFactory() method mentioned above. By default, if we don’t call this method manually, Retrofit will manually add a DefaultCallAdapterFactory object, if is JDK8 platform, will also add a CompletableFutureCallAdapterFactory object by default. What is the main role of CallAdapter in Retrofit? Simply put, the network request is returned to the response body, wrapped up in a new return object. If you Call Retrofit#addCallAdapterFactory() and add support for RxJava, the default return is Call
. Such as adding RxJava2CallAdapterFactory. The create (), then the interface API method can return the observables < ResponseBody >.
-
Annotation 2 directly converts the request body or corresponding body to the desired format.
Finally, we come to tag 3, which is the object that returns callstuck after the previous Retrofit#loadServiceMethod() call:
The ServiceMethod
#invoke() method is invoked to invoke the HttpServiceMethod
class. The HttpServiceMethod
class inherits from The ServiceMethod
class, so the object that is actually a CallSeller is called. The incoke() method finally calls CallAdapted#adapt() in the CallAdapted Class:
After reading the code, we finally came to the code at tag 1 and said, The callAdapter in the calladapter.adapt (call) is the Adapter in Retrofit#addCallAdapterFactory() mentioned above. Suppose we’re on building Retrofit, did not call this method, the Retrofit will default to call to DefaultCallAdapterFactory class DefaultCallAdapterFactory# adapt () :
When Retrofit# Create () completes and the API methods are called, an ExecutorCallbackCall object is returned. ExecutorCallbackCall#enqueue() calls the OkHttpCall#enqueue() method that actually makes network requests. In the source code here, there is also a design pattern used: the adapter pattern.
Adapter patterns can be broken down into two types: class adapters and object adapters
-
Class adapters are implemented using inheritance relationships;
-
Object adapters are implemented using composition;
By analyzing the source code, you can see that the adapter pattern used by CallAdapted is that of the object adapter. At this point, we have fully answered the question raised at the beginning of this article: what operations does the code go through from annotation 3 to annotation 4? . In summary, Retrofit constructs examples through the builder pattern, adding response body adapters, converters for requesting and returning content, etc., depending on the needs of the project, then generating proxy objects through dynamic proxies, calling API interface methods, and returning response bodies synchronously or asynchronously.
Here’s a timeline of Retrofit’s network requests:
summary
Why is Retrofit a type-safe HTTP client? In the process of analyzing Retrofit’s source code, we found that Retrofit has a strict check on the API and API methods to prevent Retrofit from having incorrect API and interface method types in the process of network requests.