The meaning of okhttp

Okhttp is an open source project for handling network requests and is the most popular lightweight framework on AndroidCopy the code

OkHttp network architecture source code in depth

Function:

Get, POST Upload file download load image (internal image size automatically compressed) Request callback, directly return object, object collection support session persistenceCopy the code

Advantages:

Allows all requests to connect to the same host address, improving request efficiency sharing Socket, reducing the number of requests to the server through connection pooling, reducing request latency caching response data to reduce repeated network requests reducing data traffic consumption automatic processing GZip compression googleplay recommended network frameworkCopy the code

OkHttp source code and simple usage

First, basic use

A synchronous request

Json // (1) create OkHttpClient object OkHttpClient client = new OkHttpClient(); Request Request = new request.builder ().url(url).build(); // create a Call object. Call call = client.newCall(request); Response Response = call.execute(); String data = response.body().string();Copy the code

An asynchronous request

Json // (1) create OkHttpClient object OkHttpClient client = new OkHttpClient(); Request Request = new request.builder ().url(url).build(); // create a Call object. Call call = client.newCall(request); Call.enqueue (new Callback() {@override public void onFailure(call, Callback); IOException e) { } @Override public void onResponse(Call call, Throws IOException {// (5) Fetch the corresponding data. String data = response.body().string(); }});Copy the code

Second, source code analysis

OkHttpClient object

OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.proxy = builder.proxy; this.protocols = builder.protocols; this.connectionSpecs = builder.connectionSpecs; this.interceptors = Util.immutableList(builder.interceptors); this.networkInterceptors = Util.immutableList(builder.networkInterceptors); this.eventListenerFactory = builder.eventListenerFactory; this.proxySelector = builder.proxySelector; this.cookieJar = builder.cookieJar; this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; boolean isTLS = false; for (ConnectionSpec spec : connectionSpecs) { isTLS = isTLS || spec.isTls(); } if (builder.sslSocketFactory ! = null || ! isTLS) { this.sslSocketFactory = builder.sslSocketFactory; this.certificateChainCleaner = builder.certificateChainCleaner; } else { X509TrustManager trustManager = Util.platformTrustManager(); this.sslSocketFactory = newSslSocketFactory(trustManager); this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); } if (sslSocketFactory ! = null) { Platform.get().configureSslSocketFactory(sslSocketFactory); } this.hostnameVerifier = builder.hostnameVerifier; this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner( certificateChainCleaner); this.proxyAuthenticator = builder.proxyAuthenticator; this.authenticator = builder.authenticator; this.connectionPool = builder.connectionPool; this.dns = builder.dns; this.followSslRedirects = builder.followSslRedirects; this.followRedirects = builder.followRedirects; this.retryOnConnectionFailure = builder.retryOnConnectionFailure; this.connectTimeout = builder.connectTimeout; this.readTimeout = builder.readTimeout; this.writeTimeout = builder.writeTimeout; this.pingInterval = builder.pingInterval; if (interceptors.contains(null)) { throw new IllegalStateException("Null interceptor: " + interceptors); } if (networkInterceptors.contains(null)) { throw new IllegalStateException("Null network interceptor: " + networkInterceptors); }}Copy the code

The constructor pattern is used within New OkHttpClient() to initialize configuration information: support protocols, task dispatters (which internally contain a thread pool to execute asynchronous requests), connection pools (which internally contain a thread pool to maintain connections), connection/read/write timeout periods, and so on.

public Builder() { dispatcher = new Dispatcher(); // dispatcher protocols = DEFAULT_PROTOCOLS; // HTTP connectionSpecs = DEFAULT_CONNECTION_SPECS; // Transport layer version and connection protocol eventListenerFactory = EventListener.Factory (eventListener.None); ProxySelector = proxySelector. GetDefault (); // Proxy selector cookieJar = cookiejar.no_cookies; // cookie socketFactory = SocketFactory.getDefault(); / / socket factory hostnameVerifier = OkHostnameVerifier. INSTANCE; / / host name that certificatePinner = certificatePinner. DEFAULT; // Certificate chain proxyAuthenticator = authenticator.none; // Proxy server authenticator = authenticator.none; ConnectionPool = new connectionPool (); // Connection pool DNS = dns.system; // uploader = uploader; // Whether to follow SSL redirect followRedirects = true; // Whether to follow the redirection retryOnConnectionFailure = true; ConnectTimeout = 10_000; // Connection timeout readTimeout = 10_000; // writeTimeout = 10_000; // Write timeout pingInterval = 0; // Time interval between HTTP / 2 and Web socket ping}Copy the code

The Request object

Request request = new Request.Builder() .url(url) .build(); /*Request*/ //... final HttpUrl url; final String method; final Headers headers; final @Nullable RequestBody body; final Map<Class<? >, Object> tags; / /...Copy the code

Each network Request is a Request, which is the encapsulation of URL, Method,header and body, as well as the encapsulation of Http Request line, Request header and entity content

Usually we build a Request object by building a folding pattern to set the url, method, headers, body, and tag of the Request.

The Call object

Call call = client.newCall(request); @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */);  } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }Copy the code

RealCall is an implementation class for Call, which defines request-related operations such as synchronous asynchrony, request cancellation, and so on. So subsequent request-related operations are basically calling the methods defined by Call, which are actually executed by its implementation class, RealCall

The request data

The synchronization request code is as follows:

@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); try { client.dispatcher().executed(this); // (1) Response result = getResponseWithInterceptorChain(); // (2) if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); // (3) } } /*Dispatcher*/ synchronized void executed(RealCall call) { runningSyncCalls.add(call); } // RealCall.java Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of List<Interceptor> interceptors = new ArrayList<>(); 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) {/ / network interceptor interceptors. AddAll (client.net workInterceptors ()); } interceptors.add(new CallServerInterceptor(forWebSocket)); OriginalRequest: new RealInterceptorChain(interceptors, null, null, 0) originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }Copy the code

(1) according to the above source we can know getResponseWithInterceptorChain () returns the Response, so access to the network to get the data from the server operation are getResponseWithInterceptorChain ()

(2) The interceptor chain is introduced here. The Request needs to be linked through the interceptor to receive the corresponding processing before being sent to the server and receiving the response from the server

Order of interceptor execution

Application interceptor: developers add interceptors retryAndFollowUpInterceptor: responsible for reconnection operation failure, and redirect, if the call is cancelled throws IOException BridgeInterceptor: as the bridge between the network layer and application layer, Convert a user request into a network request, and then send a network request to the server. The (network-layer response) Network reseponse is converted to (application-layer response) User Response CacheInterceptor: processes requests in the cache and writes responses to the cache CallServerInterceptor: The last interceptor in the interceptor chain. It sends requests to and receives response data from the serverCopy the code

The code for the asynchronous request is as follows

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)); } AsyncCall call = new AsyncCall(responseCallback); Dispatcher dispatcher = client.dispatcher(); dispatcher.enqueue(call);Copy the code

Wrap responseCallback as AsyncCall

Return a Dispatcher call to the enqueue() of the Dispatcher to perform the call asynchronously

Solution to the Dispatcher

// Dispatcher. Java public Final class Dispatcher {private int maxRequests = 64; Private int maxRequestsPerHost = 5; private @Nullable Runnable idleCallback; /** Executes calls. Created lazily. */ ** A thread pool used to execute requests, and which is lazy */ private @executorService; /** 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<>(); . Public synchronized ExecutorService ExecutorService () {if (ExecutorService == null) {// Number of core threads is 0, The maximum number of threads is integer.max_value, ExecutorService = new ThreadPoolExecutor(0, integer.max_value, 60, timeUnit.seconds, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < MaxRequestsPerHost) {// If the number of asynchronous requests being executed does not reach the maximum value // put the runningAsyncCalls.add(call) flag as runningAsyncCalls.add(call); ExecutorService ().execute(call); // Execute the asynchronous request with executorService(). } else {// If the number of asynchronous requests being executed reaches the maximum value // put readyAsyncCalls.add(call) flag to wait for execution; }}... }Copy the code

The Dispatcher is a task scheduler that builds a thread pool internally and maintains three sets:

ReadyAsyncCalls: a collection of asynchronous requests that are waiting to be executed runningAsyncCalls: a collection of asynchronous requests that are being executed, including requests that have been canceled but have not been completed runningSyncCalls: A collection of synchronous requests that are in progress, including requests that have been canceled but not completedCopy the code

All asynchronous requests are handled by the thread pool ExecutorService.

A thread pool is a ThreadPoolExecutor with zero core threads, a maximum number of integer. MAX_VALUE threads, and a maximum lifetime of free threads of 60 seconds, which means that all threads will be destroyed after 60 seconds of execution. However, under Dispatcher, there is no excessive thread because Dispatcher limits the maximum number of requests (synchronous and asynchronous) being executed to 64 and the maximum number of simultaneous requests to the same host to 5.

The thread pool calls the thread to execute AsyncCall’s execute().

// RealCall.java final class AsyncCall extends NamedRunnable { ... @Override protected void execute() { boolean signalledCallback = false; Try {/ / from the server Response is obtained by the interceptor chain Response Response Response = getResponseWithInterceptorChain (); / / if retryAndFollowUpInterceptor. Cancel () is called a so abnormal if (retryAndFollowUpInterceptor. IsCanceled ()) {signalledCallback = true; / / tag callback callback function has been invoked responseCallback. OnFailure (RealCall. This new IOException (" Canceled ")); } else {signalledCallback = true; / / tag callback callback function has been invoked 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 {// The dispatcher is notified that the task is complete. Client.dispatcher ().finished(this); }}}Copy the code

AsyncCall the execute () logic is very simple, getResponseWithInterceptorChain () we have to know in the last article, The Response is called onFailure() or onResponse(), so the callback in enqueue() is called in the child thread, The finished() call notifies the Dispatcher that the task is complete and needs to be removed from runningAsyncCalls.

The Dispatcher’s judgment is in the finish(call) call at the end of each asynchronous task

// dispatcher. Java // AsyncCall call void finished(AsyncCall Call) {finished(runningAsyncCalls, call, true); } // Synchronous requests void finished(RealCall Call) {finished(runningSyncCalls, call, false); } 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(); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback ! = null) { idleCallback.run(); }} public synchronized int runningCallsCount() {return runningAsynccalls.size () + runningSyncCalls.size(); } private void promoteCalls() { // 1. Return if (runningAsynccalls.size () >= maxRequests) return; // Already running max capacity. // 2. Return if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. // 3. For (Iterator<AsyncCall> I = readyAsyncCalls.iterator(); i.hasNext(); ) {// Get the next waiting asynchronous task from the iterator AsyncCall call = i.next(); If (runningCallsForHost(call) < maxRequestsPerHost) {// Delete calli.remove () from readyAsyncCalls; // Add call to runningAsyncCalls.add(call); ExecutorService ().execute(call); } // Execute the for loop until runningAsynccalls.size () >= maxRequests) return; // Reached max capacity. } }Copy the code

Both synchronous and asynchronous requests will eventually call dispatcher.finish(). The internal difference is that asynchronous requests will call promoteCalls(), which is intended to schedule readyAsyncCalls and runningAsyncCalls

The interceptor

The interceptor code is as follows:

RealCall / * * / Response getResponseWithInterceptorChain () throws IOException {/ / create a blocker collection List < Interceptor > interceptors  = new ArrayList<>(); AddAll (client.interceptors()); // Add a user-defined interceptors.addall (client.interceptors()); / / add a retry and redirect the interceptor interceptors. Add (retryAndFollowUpInterceptor); Add (new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); Add (new ConnectInterceptor(client)); if (! ForWebSocket) {/ / add user custom network interceptor interceptors. AddAll (client.net workInterceptors ()); } // Add (new CallServerInterceptor(forWebSocket)); // Interceptor.Chain = new RealInterceptorChain(interceptors, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); Return chain.proceed(originalRequest); }Copy the code

As you can see, a number of interceptors are used here, and these interceptors are built into a chain of responsibilities and then dealt with one by one. The chain of responsibility mode is used here, where each interceptor is responsible for the corresponding function, and the completion of the last interceptor will be passed to the next interceptor, until the last interceptor is finished and then the Response will be returned one layer after another.

After looking at the source code, OkHttp is a very well designed framework. The framework utilizes many design patterns, such as the Builder pattern, the chain of responsibility pattern, and so on. Knowing that the core of OkHttp is the interceptor, the chain of responsibility model is adopted, where each interceptor is responsible for the corresponding function, each interceptor is executed from top to bottom when the request is initiated, and the response data is passed layer by layer.