0 foreword
To begin the series, I’d like to explain that the title “Beginner must know” is not Versailles! I believe that click in Android partners can appreciate the client side difficult. Android-er anxiety can be seen throughout the tech community:
Any company interview these days requires several algorithms, AMS/WMS/JVM on the left hand and performance optimization /JetPack on the right. As for the third party principle, which was common in senior interviews a few years ago, it is now at the primary level 😂.
“Everything can be rolled up,” Android engineers who are confused about the future suggest reading Liu Wangshu’s article carefully. Winter has come! Where does the embattled Android engineer go from here?
The enrolling effect is to work on the same job for a long time and keep it at a certain level without any change or improvement. This behavior is often a form of self-denial and self-consumption.
1 Sample code
// create a client object
OkHttpClient okHttpClient1 = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
//2. Client +call creates a RealCall
Request request = new Request.Builder()
.url(URL)
.build();
Call call = okHttpClient.newCall(request);
//3
Response response = call.execute();
// async request
okHttpClient1.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {}@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { System.out.println(response.body().string()); }});Copy the code
OkHttp requests can be divided into three phases:
- OkHttpClient + Request RealCall;
- Realcalls are executed synchronously or asynchronously and distributed by the Dispatcher;
- Through getResponseWithInterceptorChain () to obtain the Response.
2 OkHttpClient
OkHttpClient objects can be created in two ways, either directly new and using the default configuration, or by setting configuration parameters freely in Builder mode.
public OkHttpClient(a) {
this(new Builder());
}
Copy the code
The OkHttpClient class wraps various functional modules to provide a uniform API to the outside world. This design pattern is called the facade pattern.
After the client and Request are created, a Call object needs to be generated. A Call object in OkHttp can represent a request that is acted upon through various methods provided by the Call interface. The Call interface provides an internal interface, Factory, using the Factory method pattern.
interface Factory {
Call newCall(Request request);
}
Copy the code
Get the Call object in the code via client.newCall().
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
Copy the code
3 RealCall + Dispatcher
RealCall implements the Call interface, which is the implementation class for initiating requests. The following is the concrete implementation of two different request methods.
3.1 Synchronization Request
RealCall.class
@Override public Response execute(a) throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
/ / add
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
/ / remove
client.dispatcher().finished(this); }}Copy the code
Synchronized (this) is used to ensure that the same request (Call object) is not executed repeatedly. Then the Dispatcher dispatcher is used to add the request to the synchronous two-way queue runningSyncCalls and remove the Call from the queue after obtaining the result of the request.
Dispatcher.class
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
Copy the code
3.2 Asynchronous Request
RealCall.class
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code
Dispatcher.class synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); }}Copy the code
Like synchronous requests, the enQueue method in RealCall invokes the enQueue implemented in the Dispatcher and adds the request to the asynchronous two-way queue runningAsyncCalls. Notice the conditions for joining the queue (if the conditions are not met, the queue is entered) :
- Runningasynccalls.size () < maxRequests for concurrent requests less than 64
- RunningCallsForHost (call) < maxRequestsPerHost The number of a request is less than 5
Next, specific execution is left to the thread pool within the Dispatcher. Dig a hole 😥Java thread pools look forward to subsequent articles.
Dispatcher.class
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
In accordance with the “formula”, now need to use getResponseWithInterceptorChain () to obtain the Response to the request as a result, this part of the implementation? Look at the Call object in the method, which is of type AsyncCall. This may seem familiar; in RealCall, when okHttpClient executes enqueue through the scheduler, it has already new it once before in order to get the result of the request.
RealCall.class
client.dispatcher().enqueue(new AsyncCall(responseCallback));
Copy the code
Now that AsyncCall can take the result of the request and return it, let’s look at the actual class implementation.
RealCall.class
final class AsyncCall extends NamedRunnable {
private finalCallback responseCallback; .@Override
protected void execute(a) {
try {
Response response = getResponseWithInterceptorChain();
} catch (IOException e) {
...
} finally {
client.dispatcher().finished(this); }}}Copy the code
AsyncCall inherits NamedRunnable, which is essentially a runnable. Taking out some of the code here and looking at the trunk of the request, you can see that the asynchronous request flow is now consistent with the synchronous request flow. Having explained the flow of synchronous asynchronous requests, it is time to talk about the scheduler’s management and control of asynchronous requests.
3.3 ArrayDeque
Dispatcher.class
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Copy the code
The Deque interface inherits from the Queue interface and supports adding or removing elements from both ends at the same time, so it is also called a double-endian Queue. ArrayDeque relies on mutable arrays and is not thread-safe.
As mentioned earlier, synchronous requests add requests directly to runningSyncCalls, while asynchronous requests are limited by maxRequests and maxRequestsPerHost, Select to enter the execution queue runningAsyncCalls or wait queue readyAsyncCalls.
Synchronous requests are sent to the originating thread for execution, and asynchronous requests are sent to the thread pool for execution. Although the maximum number of threads supported by the thread pool is integer. MAX_VALUE, this value is limited by maxRequests. Let’s look at the recycle part of the logic. The client.dispatcher().finished(this) method is executed for synchronous asynchronous recycle requests.
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
Copy the code
The finished method is called for both asynchronous requests. The only difference is the Boolean promoteCalls parameter value.
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if(! calls.remove(call))throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0&& idleCallback ! =null) { idleCallback.run(); }}Copy the code
Focus on the promoteCalls() method.
private void promoteCalls(a) {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}Copy the code
To summarize the function of this method: By determining the number of requests, requests from the waiting queue are queued for execution and sent to the thread pool for execution. It can be seen that in the Dispatcher, thread pool mainly plays a role of cache and execution, and the main scheduling strategy is still completed by the scheduler itself.
4 InterceptorChain
OkHttp request the last step, through getResponseWithInterceptorChain () to obtain the Response. The chain of responsibility model is used here.
Response getResponseWithInterceptorChain(a) throws IOException {
// Build a full stack of
interceptors.
// Interceptor chain
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket));
// Responsibility chain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
// Execute the chain of responsibility
return chain.proceed(originalRequest);
}
Copy the code
The responsibility Chain is created using the RealInterceptorChain class, which implements the interceptor. Chain interface, and facilitates the request by calling chain.proceed. Let’s take a look at the RealInterceptorChain implementation.
public final class RealInterceptorChain implements Interceptor.Chain {...public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec ! =null&&!this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec ! =null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if(httpCodec ! =null && index + 1< interceptors.size() && next.calls ! =1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
returnresponse; }}Copy the code
This is really just a recursion. Each responsibility chain calls the next one, pushing the interceptor chain to execute, and finally returning Response in turn. It can be seen that there are both one-way and two-way Response flow charts on the Internet, which are essentially consistent. This section does not specify the role of each Interceptor, but only need to know the call chain of the entire request process.
Online to find a flow chart for your reference, picture source big guy blog open wheel series: open OkHttp, delete.
The last 5
This blog post only covers the basic request flow, but there are a few important aspects of OkHttp that are left unsaid: the difference between Interceptors and NetworkInterceptors, and connection pools, which are some of the most frequently asked questions. Connection pooling is very important, but requires an extended knowledge of HTTP/TCP, so this article will not be expanded as a “beginner’s must-know”. We will use this as an example for subsequent introductions to HTTP.
In the end, the “Beginner will” series is actually intended to use several commonly used project/communication frameworks (MVP, MVVM, Netty) for those who need them. The first set is OkHttp+Retrofit+RxJava+Dagger2. The next few articles will introduce the source code involving third parties, and finally post the address of the framework.
Flag: the first set of framework before the end of the year, to draw an end to 2020 🤪.