“This is my seventh day of the November Gwen Challenge.The final text challenge in 2021”
Before we look at OkHttp’s core processes and core classes, let’s be clear about two concepts. One is the builder pattern used when OkHttpClient and Request are created. The other is the interceptor pattern responsible for response processing;
OkHttpClient/Request builder pattern resolution
The basic concept
The Builder (aka builder) pattern allows us to build a complex object step by step from multiple simple objects.
Concept to explain
If you’re renovating a house, you want to think about the overall design of the house how do you do it, Mediterranean style? Euro-american style? Pure Chinese style? Are the walls painted white? Ceramic tile? Or do I do it in a different color? How to deal with water and electricity installation? How to deal with waterproofing? How to design the master bedroom? How is the balcony designed? When you start decorating, you find that you have to deal with too many things, so you think of a way to find a decoration company to help you do the design.
They provide you with some default parameters, such as using pure Chinese style, using diatom mud for walls, using plan A for waterproofing, using Plan B for water and electricity, etc. You think the default styles they provide you are all good, the only thing you feel bad about is that in this set of design, the balcony uses the “closed” design, and you want to use the “open” design, so you decide to use the default values for all the others, but the “open” design you put forward for this balcony.
- Pros: We don’t need to care about every detail when decorating the house; Because it provides default values;
- Disadvantages: in addition to the money of decoration construction, we also need to pay extra money for decoration design;
Let’s look at this example in the OkHttp source code:
Why are OkHttpClient and Request created using the builder pattern?
When the internal data of a class is too complex, such as OkHttpClient containing Dispatcher, Proxy, ProxySelector, CookieJar, Cache, SocketFactory, etc., Request also contains HttpUrl, Method, Headers, RequestBody, tag, etc. These objects may be more or less, but they all have one thing in common: many parameters do not need to be passed. For example, the code for these two objects is as follows:
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().url(url).build();
Copy the code
OkHttpClient and Request contain multiple parameters, but these parameters are not passable to the user. In the above code, only one URL parameter is passed to the Requset through builder.url (url).build(); This is equivalent to saying that if we pass no parameters, the builder pattern will create the default parameters for us; If we pass in parameters, the builder pattern replaces the default ones with the ones we pass in.
-
Advantages: We don’t have to worry about the details of the OkHttpClient/Request build. Instead of passing a lot of parameters every build, we just pass the parameters we care about, which is very easy to use.
-
Cons: The entire code can seem “code redundant” and confusing to those who don’t understand design patterns;
The Builder pattern is simple code implementation
public class House3 {
private double height;
private double width;
private String color;
public House3(Builder builder) {
this.height = builder.height;
this.width = builder.width;
this.color = builder.color;
}
public static final class Builder{
double height;
double width;
String color;
public Builder(a){
this.height = 100;
this.width = 100;
this.color = "red";
}
public Builder addHeight(double height) {
this.height = height;
return this;
}
public Builder addWidth(double width) {
this.width = width;
return this;
}
public Builder addColor(String color) {
this.color = color;
return this;
}
public House3 build(a){
return new House3(this); }}@Override
public String toString(a) {
return "House{" +
"height=" + height +
", width=" + width +
", color='" + color + '\' ' +
'} ';
}
Copy the code
The above code is a very simple Builder pattern code. We will find that the internal properties of Class Builder and House3 are exactly the same, which gives a feeling of “code redundancy” and makes people who don’t understand the design pattern feel awkward.
OkHttp responsibility chain pattern parsing
The basic concept
The Chain of Responsibility Pattern creates a Chain of recipient objects for requests. This pattern decouples the sender and receiver of the request. In this pattern, each receiver typically contains a reference to the other receiver. If the current recipient is unable to process the request, it is passed to the next recipient, and so on;
Concept to explain
If you miss your long-distance girlfriend so much that you decide to meet her in her city this weekend, no matter what, you inquire about the transportation to her city: Plane, high-speed rail, bus, car rental, ride a motorcycle this five ways (convenient degree) according to the order, so you buy a plane ticket, first bought, if you don’t consider other way, if you don’t buy, will choose to buy high iron, if got tickets high-speed off the bus, if they aren’t in high-speed rail tickets, consider the bus, if do not choose to rent a car, buy a bus ticket If you can’t buy a car…… And so on; As long as a node is processed (such as buying a plane ticket), it does not continue to select the next node, and instead continues to the next node.
-
Advantages: We have a variety of “choices”, as long as one is satisfied, there is no need to continue digging, wasting more time;
-
Disadvantages: need to spend a certain amount of time, to make a plan and the plan of each node; If you buy a plane ticket without planning, and you get it right, you’ll save time, but if you don’t, you’ll have to replan what to do next. May waste more time;
Responsibility chain simple code implementation
public interface IBaseTask {
// Parameter 1: Whether the task node is capable of executing; Parameter 2: next task node
public void doAction(String isTask,IBaseTask iBaseTask);
}
Copy the code
public class ChainManager implements IBaseTask{
private List<IBaseTask> iBaseTaskList = new ArrayList<>();
public void addTask(IBaseTask iBaseTask){
iBaseTaskList.add(iBaseTask);
}
private int index = 0;
@Override
public void doAction(String isTask, IBaseTask iBaseTask) {
if(iBaseTaskList.isEmpty()){
// Throw an exception
}
if(index >= iBaseTaskList.size()){
return;
}
IBaseTask iBaseTaskResult = iBaseTaskList.get(index);// Select taskOne from taskoneindex++; iBaseTaskResult.doAction(isTask,iBaseTask); }}Copy the code
public class TaskOne implements IBaseTask{
@Override
public void doAction(String isTask, IBaseTask iBaseTask) {
if("no".equals(isTask)){
System.out.println("Interceptor task node 1 handles.....");
return;
}else{ iBaseTask.doAction(isTask,iBaseTask); }}}Copy the code
This simple code implementation, just do a TaskOne class, if you need to add more tasks, according to the TaskOne class implementation, more implementation of several;
Final call:
ChainManager chainManager = new ChainManager();
chainManager.addTask(new TaskOne());
chainManager.addTask(new TaskTwo());
chainManager.addTask(new TaskThree());
chainManager.doAction("ok",chainManager);
Copy the code
Let’s look at this example in the OkHttp source code:
Why does OkHttp use the chain of responsibility pattern to process responses
When a request is made from the OkHttp framework, it is possible to do some other processing depending on business or background Settings; For example: when the request is sent out, we can verify whether the request is reasonable; If it is reasonable, it will continue to send. In the process of sending, it may first determine whether the request has ready-made cache data, and if so, it will not request the server, but directly obtain the local cache data. The advantage of this is that more relevant verification, caching and other work can be handled before the request is sent, which saves the traffic and time consumption brought by the request to the server.
The following code block adds an interceptor for OkHttp. The corresponding comments have been made; Once the chain of responsibility mode is solved, the entire interceptor invocation process can be analyzed more clearly.
Response getResponseWithInterceptorChain(a) throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// Add a custom interceptor. For example, when sending a request, I need to verify that the parameters sent are properly wrapped and encrypted according to the client-server convention; If yes, continue. If no, call back.
interceptors.addAll(client.interceptors());
// OkHttp's own retry and redirection interceptor;
interceptors.add(retryAndFollowUpInterceptor);
// OkHttp comes with a bridge interceptor
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// OkHttp comes with a cache interceptor
interceptors.add(new CacheInterceptor(client.internalCache()));
// OkHttp comes with a link blocker
interceptors.add(new ConnectInterceptor(client));
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }// OkHttp's own request service interceptor
interceptors.add(new CallServerInterceptor(forWebSocket));
// According to the concept of the chain of responsibility pattern, each interceptor needs to have a reference to the next interceptor.
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
Copy the code
-
Advantages: If we want to add a new interceptor, we only need to customize our own interceptor, which can be easily added through OkHttpClient.
-
Disadvantages: developers who do not understand the chain of responsibility mode will find it difficult to sort out the call relationship of the code in this chain of responsibility mode;
OkHttp main line flow analysis
The first main thread – the queue operation
/ / build OkHttpClient
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
/ / build Request
Request request = new Request.Builder().url(url).build();
// Create a RealCall object from Request. RealCall is the concrete implementation class of Call
Call call = okHttpClient.newCall(request);
// join the queue, add callback
call.enqueue(new Callback() { // omit Callback Callback function}
Copy the code
The second mainline – network access operations
Response getResponseWithInterceptorChain(a) throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// Add a custom interceptor
interceptors.addAll(client.interceptors());
// Retry and redirect interceptors
interceptors.add(retryAndFollowUpInterceptor);
// Bridge interceptor
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// Cache interceptor
interceptors.add(new CacheInterceptor(client.internalCache()));
// Connect interceptor
interceptors.add(new ConnectInterceptor(client));
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }// Request server interceptor
interceptors.add(new CallServerInterceptor(forWebSocket));
// Responsibility chain design pattern calls code
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
Copy the code
Run thread pool resolution of the request
public synchronized ExecutorService executorService(a) {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
}
return executorService;
}
Copy the code
-
Number of core threads: 0, maximum number of threads: integer.max_value;
-
3/4 parameter: When the number of threads in the thread pool is greater than the number of core threads, idle threads will wait 60 seconds before being terminated. If the number is smaller than the number of core threads, idle threads will be terminated immediately.
-
SynchronousQueue: blocking queue; This queue does not have any internal capacity; (Note: this definition is also a bit vague, because queues still have fetch and delete operations.)
-
Make sure a concept, the thread pool has its own internal queue, under normal circumstances, the initialization of the thread pool is created when idle thread, if there is a task to come in, you can use the idle thread to perform a task in the thread pool, after again into the idle thread, this is to reduce the number of threads created and destroyed, improve performance; The internal queue is a buffer zone. If there are not enough threads to process a task, the task is placed in the queue and executed on a first-in-first-out basis. But this raises the question, what if I submit a task that I want to execute immediately? That’s why SynchronousQueue is used; Because the SynchronousQueue has no internal capacity, the thread pool must start new threads for me when there are too many tasks, rather than waiting in a queue. In this case, however, you need to set the maximum thread pool to integer.max_value to prevent new tasks from being processed.
Why is OkHttp faster?
So why is OkHttp’s request thread pool designed this way? Basically, OkHttp uses two queues to maintain requests while processing requests. In this case, it is more controllable. Instead of maintaining the queue itself, the thread pool maintains its internal queue again, which can cause delays. This is one of the reasons why OkHttp requests are faster;
Other reasons:
-
OkHttp uses Okio for data transfer. Okio encapsulates IO/NIO. Higher performance;
-
Use of thread pools and connection pools;
-
Keep-alive mechanism support; (This can also be set on other frameworks.)
Interceptor details
Retry and redirect interceptors
RetryAndFollowUpInterceptor: The interceptor creates a StreamAllocation object. This object manages the pool of Socket connections (the parameters in the pool may change in the future (from the official note). Currently, the pool can hold up to 5 free connections for 5 minutes) and request addresses (such as Htpps or not). Responsible for request retries and redirects;
Bridge interceptor
BridgeInterceptor: This interceptor is responsible for adding headers to a request, such as content-Type, accept-Encoding, and so on.
Cache interceptor
CacheInterceptor: This interceptor is optimized for traffic and access speed. If there is a cache, it takes the cache and does not access the server. It also needs to maintain the cache.
Link interceptor
ConnectInterceptor: the interceptor is responsible for connecting to the server. This interceptor gets the StreamAllocation object and a RealConnection (a Socket connection encapsulation).
Request server interceptor
CallServerInterceptor: Is responsible for writing request data to the server, reading data sent from the server, and using the builder mode to build the returned data into a Response object;
Three things to watch ❤️
If you find this article helpful, I’d like to invite you to do three small favors for me:
- Like, forward, have your “like and comment”, is the motivation of my creation.
- Follow the public account “Xiaoxinchat Android” and share original knowledge from time to time
- Also look forward to the follow-up article ing🚀