OkHttp now dominates the Android web request landscape, with the most commonly used framework being Retrofit+ OkHttp. It is necessary to understand the implementation principles and design ideas of OkHttp, and to read and understand the popular framework is the only way to advance programmers, code and language is just the tools, the important is the idea.
Before OKhttp source code parsing, we must first understand the basics of HTTP, any network request is inseparable from HTTP.
An overview of the
Okhttp source code analysis, there are a lot of blog, but interpretation is some source dispensable knowledge, will not okhttp core ideas on design reach the designated position, we read some framework source code, learning is its design thought, understanding the overall framework design, in understanding the details of implementation will be more easy.
OkHttp source code parsing
1. Overall framework design of OkHttp
It is recommended to download the source code of okhttp and open it with AndroidStudio. The whole article is based on the analysis of the source code to learn the design skills and ideas of okhttp. If this article has content analysis is not in place, you are welcome to discuss with me.
Below is a complete flow chart of the OKHTTP request network (just a quick overview)
How to use okHTTP
OkHttpClient client = new OkHttpClient();
Copy the code
Let’s first take a look at the okHTTP constructor OkHttpClient() and some configuration related things to get a sense of it.
public OkHttpClient() {
this(new Builder());
}
Copy the code
If we do not need to configure the client, okHTTP will help us to implement the configuration by default.
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
Copy the code
If you need some configuration such as adding interceptors, then you need to call:
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.retryOnConnectionFailure(true)
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
Copy the code
Let’s take a look at what attributes OkHttpClient has, just to get a sense of it. In the analysis
final Dispatcher dispatcher; // Scheduler final @nullable Proxy Proxy; // Final List<Protocol> protocols; // Final List<ConnectionSpec> connectionSpecs; Final List<Interceptor> interceptors; // Final List<Interceptor> networkInterceptors; // Final eventListenerFactory eventListenerFactory; final ProxySelector proxySelector; // Final CookieJar CookieJar; //cookie final @Nullable Cache cache; // Cache final @nullable InternalCache InternalCache; // Final SocketFactory SocketFactory; // Socket factory final @nullable SSLSocketFactory; // Secure layer socket factory for HTTPS final @nullable CertificateChainCleaner CertificateChainCleaner; // Secure layer socket factory for HTTPS final @nullable CertificateChainCleaner CertificateChainCleaner; Final HostnameVerifier HostnameVerifier; // Confirm the response. // Confirm the host name in final CertificatePinner; // Confirm the host name in final CertificatePinner; // Final Authenticator proxyAuthenticator; Final Authenticator Authenticator; // Final ConnectionPool; // Link pool multiplexing final Dns Dns; // Domain final Boolean directs; // Directs final Boolean followRedirects; // Local redirection final Boolean retryOnConnectionFailure; // Connection retry failed final int connectTimeout; // Final int readTimeout; Final int writeTimeout; // Write timeoutCopy the code
Request network
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
Copy the code
As you can see from the source code, okHTTP implements the call.Factory interface
interface Factory {
Call newCall(Request request);
}
Copy the code
Let’s take a look at how okhttpClient implements the Call interface.
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
Copy the code
You can see that the real request is given to the RealCall class, and RealCall implements the Call method. RealCall is the real core code.
RealCall main methods: synchronous request: client.newCall(request).execute(); And asynchronous requests: client.newCall(request).enqueue();
Next, we will focus on analyzing asynchronous requests, because all network requests in the project are basically asynchronous, and synchronization is rarely used. Finally, we will analyze synchronous requests.
An asynchronous request
Follow the source code through the realCall.enqueue () implementation
Realcall.java.override public void enQueue (Callback responseCallback) {//TODO cannot repeat synchronized (this) {if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); Client.dispatcher ().enqueue(new AsyncCall(responseCallback)); }Copy the code
You can see that the above code does several things:
- synchronized (this)Ensure that each call can be executed only once and not repeatedly. If you want the exact same call, you can call the following methods: Clone the call
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
@Override public RealCall clone() {
return RealCall.newRealCall(client, originalRequest, forWebSocket);
}
Copy the code
- Use the Dispatcher to perform the actual executionclient.dispatcher().enqueue(new AsyncCall(responseCallback));In the okHttpClient. Builder above, you can see that the Dispatcher is initialized.
Let’s take a look at the implementation of the downloader.
The Dispatcher scheduler
Dispatcher#enqueue
Synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) {synchronized void enqueue(AsyncCall Call) If (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequests && runningCallsForHost(call) < MaxRequestsPerHost) {//TODO joins the run queue and sends it to the thread pool to execute runningAsyncCalls.add(call); //TODO AsyncCall is a runnable that can be executed in a thread pool. Execute implements executorService().execute(call); } else {//TODO join waiting queue readyAsyncCalls.add(call); }}Copy the code
From the above code you can see that the Dispatcher queues the call and then executes it through the thread pool.
Maybe you are still confused, let’s first understand several properties and methods of Dispatcher
Private int maxRequests = 64; SCHEME :// HOST [":" PORT] [PATH ["?" QUERY]] //TODO For example, https://restapi.amap.com restapi.amap.com - host private int maxRequestsPerHost = 5; /** * Ready async calls in the order they'll be run. * TODO Private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** * Running asynchronous calls. Includes canceled calls that haven't finished yet. * TODO Asynchronous queues that are in progress */ Private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();Copy the code
Obviously, OKHTTP can make multiple concurrent network requests, and a maximum number of requests can be set
The executorService() method is simple; it just creates a thread pool
Public synchronized ExecutorService ExecutorService () {if (ExecutorService == null) {//TODO thread pool to understand //TODO core threads ExecutorService = new ThreadPoolExecutor(0, integer.max_value, 60, timeUnit.seconds, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }Copy the code
Now that we put the call in the thread pool how does it execute? Notice that the call here is AsyncCall.
Let’s look at the implementation of AsyncCall:
final class AsyncCall extends NamedRunnable
Copy the code
What the hell is NamedRunnable? Click in to have a look
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
Copy the code
AsyncCall is a Runnable, and the pool actually executes execute().
We’re looking at AsyncCall’s execute()
final class AsyncCall extends NamedRunnable { @Override protected void execute() { boolean signalledCallback = false; Try {/ / TODO chain of responsibility pattern / / TODO interceptor chain execution request Response Response = getResponseWithInterceptorChain (); / / callback results if (retryAndFollowUpInterceptor isCanceled ()) {signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); }} finally {//TODO to remove queue client.dispatcher().finished(this); }}}Copy the code
Can be seen from the above code is actually perform the requested getResponseWithInterceptorChain (); The Response is then returned to the user via a callback.
Note that finally executes client.dispatcher().finished(this); The scheduler removes the queue and determines whether there is a wait queue. If there is a wait queue, it checks whether the execution queue has reached its maximum value. If there is no wait queue, it becomes an execution queue. This ensures that the wait queue is executed.
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; Synchronized (this) {//TODO calls to remove queue if (! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!" ); If (promoteCalls) promoteCalls(); if (promoteCalls) promoteCalls(); if (promoteCalls) promoteCalls(); // Number of TODO runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } // If (runningCallsCount == 0 && idleCallback! = null) { idleCallback.run(); }} private void promoteCalls() {//TODO check runqueue and wait queue if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; For (Iterator<AsyncCall> I = readyAsyncCalls.iterator(); // No ready calls to promote. i.hasNext(); ) { AsyncCall call = i.next(); If (runningCallsForHost(call) < maxRequestsPerHost) {i.emove (); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }Copy the code
Real perform network request and returns a response results: getResponseWithInterceptorChain (), we emphatically analyze the method below:
I commented each piece of code.
/ / TODO core code to start real perform network request Response getResponseWithInterceptorChain () throws IOException {/ / Build a full stack of List<Interceptor> interceptors = new ArrayList<>(); // The intercept set by TODO when configuring okhttpClient is interceptors.addall (client.interceptors()); / / TODO is responsible for handling the retry after failure and redirect interceptors. Add (retryAndFollowUpInterceptor); //TODO is responsible for transforming user-constructed requests into requests sent to the server, and the response returned by the server into a user-friendly response, handling information such as configuration requests //TODO is the bridge from application code to network code. First, it builds network requests based on user requests. Then it continues to call the network. Finally, it builds user responses based on network responses. interceptors.add(new BridgeInterceptor(client.cookieJar())); //TODO sets the request header (if-none-match, if-modified-since, etc.). The server may return 304(unmodified). //TODO Add (new CacheInterceptor(client.internalCache()))); Add (new ConnectInterceptor(client)); if (! ForWebSocket) {// networkInterceptors set when TODO configures okhttpClient //TODO returns a list of immutable interceptors that observe a single network request and response. interceptors.addAll(client.networkInterceptors()); } //TODO performs flow operations (write request body, get response data). TODO is responsible for sending request data to the server, read response data from the server //TODO encapsulates the HTTP request packet and parses the request packet CallServerInterceptor(forWebSocket)); = new interceptorChain (interceptors, null, null, null, 0, originalRequest, null, null); this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); Return chain-proceed (originalRequest); // Return chain-proceed (originalRequest); }Copy the code
From the above code, it can be seen that the Interceptor interface is implemented, which is the most core part of OKHTTP. The pattern of responsibility chain is adopted to separate each function, and each Interceptor completes its own task by itself, and assigns tasks that do not belong to it to the next one, simplifying their responsibilities and logic.
The chain of responsibility pattern is one of the design patterns that is also a fairly simple reference link and will not be repeated here.
Let’s focus on how the design implementation of OKHTTP passes the returned data through the chain of responsibility.
From the above code, we can see that the interceptors are passed to the RealInterceptorChain class, which implements interceptor.chain and executes chain.proceed(originalRequest).
The core code is chain-.proceed (), which executes the chain of responsibility.
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; //TODO creates a new interceptor chain, Index +1 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); //TODO executes the current interceptor - If the intercept is not set when configuring okhttpClient, the default is to execute first: RetryAndFollowUpInterceptor Interceptor Interceptor Interceptor = interceptors. Get (index); Response Response = interceptor.intercept(next); return response; }Copy the code
From the above code, we can see that we create a new RealInterceptorChain and index+1, and then execute interceptors.get(index); Returns the Response.
In fact, the interceptors are executed in sequence. Here I draw a simple diagram:
The order in which the interceptors are executed is shown in the figure above.
One advantage of this design is that each interceptor in the chain executes the code before the chain.proceed() method and returns the final response data when the last interceptor in the chain completes, while the chain.proceed() method gets the final response data. The code after the chain-.proceed () method of each interceptor is executed, which is essentially some operation on the response data.
The CacheInterceptor CacheInterceptor is a good example of this. Let’s analyze it with the CacheInterceptor CacheInterceptor, and you’ll see.
The CacheInterceptor implementation is as follows:
The code is long, so let’s go through it step by step.
First, let’s examine how the above code handles getting the cache when there is no network.
@override public Response intercept(Chain Chain) throws IOException {//TODO Obtain the Response of the cache corresponding to the request cacheCandidate == null Response cacheCandidate = cache ! = null ? cache.get(chain.request()) : null; Long now = System.currentTimemillis (); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //TODO if networkRequest == null, networkRequest is not used. NetworkRequest = strategy.networkRequest; //TODO obtain Response from cache (CacheStrategy) if (cache ! = null) { cache.trackResponse(strategy); } //TODO cache invalid disable resource if (cacheCandidate! = null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, //TODO networkRequest == null unavailable networkRequest and no cache cacheResponse == null failed to return if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); }}Copy the code
The above code does a few things:
- If the user has configured a cache interceptor, cacheCandidate = cache.Response retrieves the Response from the user. Otherwise, cacheCandidate = null. Obtain both cacheResponse and networkRequest from CacheStrategy
- If cacheCandidate! = NULL and cacheResponse == NULL Indicates that the cache is invalid. The cacheCandidate cache is clear.
- If networkRequest == NULL indicates that there is no network, cacheResponse == NULL indicates that there is no cache, and a failure message is returned. The chain of responsibility is terminated and no further execution is performed.
- If networkRequest == NULL, the cacheResponse! = null There is a cache, return the cached information, the chain of responsibility is terminated at this point, no further execution.
The above part of the code, in fact, is no network when the processing.
So the next part of the code must be, when there’s a network
//TODO executes the next interceptor Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate ! = null) { closeQuietly(cacheCandidate.body()); } // If we have a cache response too, Then we're doing a conditional get. // If (cacheResponse! = null) {//TODO 304 response code since last request, If (networkResponse.code() == HTTP_NOT_MODIFIED) {Response Response = Cacheresponse.newBuilder () .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); }} / / TODO cache the Response the Response Response. = networkResponse newBuilder () cacheResponse (stripBody (cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache ! = null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }Copy the code
The following part of the code does a few things:
- Execute the next interceptor, which is the request network
- When the chain of responsibility completes, it returns the final response data, updating the cache if it exists, and adding it to the cache if it does not.
This reflects the benefits of realizing the chain of responsibility. When the chain of responsibility is completed, if the interceptor wants to get the final data to do other logic processing, so that there is no need to do other call method logic, directly in the current interceptor can get the final data.
This is the most elegant and core feature of OKHTTP design.
Of course, we can verify by a small example, practice is the most important.
First we simulate an interceptor interface
/** * @author prim * @version 1.0.0 * @desc * @time 2018/8/3-4:29 */ public interface Interceptor { String interceptor(Chain chain); interface Chain { String request(); String proceed(String request); }}Copy the code
Then implement several interceptors
public class BridgeInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System.out.println(" Code before executing the BridgeInterceptor interceptor "); String proceed = chain.proceed(chain.request()); System.out.println(" The code gets the final data after executing the BridgeInterceptor interceptor: "+proceed); return proceed; } } public class RetryAndFollowInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System. The out. Println (" RetryAndFollowInterceptor blocker before code "); String proceed = chain.proceed(chain.request()); System. The out. Println (" execution RetryAndFollowInterceptor interceptor code after get the final data: "+ proceed); return proceed; } } public class CacheInterceptor implements Interceptor { @Override public String interceptor(Chain chain) { System.out.println(" The last interceptor that executes CacheInterceptor returns the final data "); return "success"; }}Copy the code
Then implement the Chain interface
public class RealInterceptorChain implements Interceptor.Chain { private List<Interceptor> interceptors; private int index; private String request; public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) { this.interceptors = interceptors; this.index = index; this.request = request; } @Override public String request() { return request; } @Override public String proceed(String request) { if (index >= interceptors.size()) return null; RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request); Interceptor = interceptors.get(index); return interceptor.interceptor(next); }}Copy the code
Then test it to see if we got it right
List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(new BridgeInterceptor());
interceptors.add(new RetryAndFollowInterceptor());
interceptors.add(new CacheInterceptor());
RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");
request.proceed("request");
Copy the code
The following log files are displayed:
Before execution BridgeInterceptor interceptor code execution RetryAndFollowInterceptor blocker before code execution CacheInterceptor last interceptor Returns the final data is carried out RetryAndFollowInterceptor interceptor code after get the final data: success implementation BridgeInterceptor interceptor code after get the final data: successCopy the code
OK, perfect, no problem with verification, I think now you understand the core design idea of OKHTTP.
Other implementations of okHTTP interceptors can be researched by yourself. This design idea of OKHTTP can be applied to the project to solve some problems.
A synchronous request
I’m going to talk a little bit about synchronous requests for OKHTTP, and the code is very simple and it’s also implemented in the RealCall class
@override public Response Execute () throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); //TODO calls the start method of the listener eventListener.callstart (this); Try {//TODO to the dispatcher to execute the client.dispatcher().executed(this); / / TODO get request return data of the Response result = getResponseWithInterceptorChain (); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally {//TODO execute the scheduler's completion method to remove the queue client.dispatcher().finished(this); }}Copy the code
I have done several things:
- Synchronized (this) avoids repetition, as described in the section above.
- client.dispatcher().executed(this); The actual scheduler simply adds the call to the synchronous execution queue. The code is as follows:
Synchronized void executed(RealCall Call) {runningSynccalls.add (call); // synchronized void executed(RealCall call); }Copy the code
- GetResponseWithInterceptorChain () is the core of the code, has been analyzed above, ask for the response data network, returned to the user.
- client.dispatcher().finished(this); The execution scheduler’s completion method removes the queue
It can be seen that in the method of synchronous request, the dispatcher only informs the execution status, the execution starts (” executed “), the execution is finished (” finished “), and the rest is not involved. Dispatcher is more about serving asynchronous requests.
conclusion
There are many details of OKHTTP in this article did not involve, such as: okHTTP is how to use DiskLruCache to achieve caching, HTTP2/HTTPS support, this article mainly explains the core design idea of OKHTTP, after a clear understanding of the overall, in depth details, easier to understand.
Brief description of okHTTP execution flow:
- OkhttpClient implements call. Fctory, which creates a Call for a Request;
- RealCall is a concrete implementation of Call. Its enQueue () asynchronous request interface is implemented by ExcutorService through Dispatcher() scheduler, and the final network request is consistent with the execute() interface. By getResponseWithInterceptorChain () function
- GetResponseWithInterceptorChain () using Interceptor in the chain, the chain of responsibility pattern Layered implement caching, transparent compression, network IO, and other functions; The response data is finally returned to the user.
Remove wheel series: remove OkHttp
OkHttp