In Android development today, requesting data from the web is pretty much standard. In the early days of Android development, some people used HttpClient, HttpUrlConnection, or Volley for web requests, but today (2018) most developers use OkHttp+Retrofit+RxJava for web requests. For all three, the actual request network framework is OkHttp, so OkHttp is very important.

The basic usage of OkHttp

OkHttpClient client = new OkHttpClient(); String run(String URL) throws IOException {// Create a Request object. Request Request = new request.builder ().url(url).build(); // Create a Call object and perform synchronization to obtain network data. Response Response = client.newCall(request).execute();return response.body().string();
}
Copy the code

There are basically four steps to using OkHttp:

  • Create the OkHttpClient object
  • Create the Request Request object
  • Creating a Call object
  • Call call.execute() to synchronize the request; Asynchronous requests call call.enqueue(callback). I’ll explain these four steps in detail next.

Create the OkHttpClient object

In general, we use OkHttp without creating an OkHttpClient directly through new OkHttpClient(). In general, we do some configuration for the OkHttpClient, such as:

OkHttpClient.Builder().connectTimeout( DEFAULT_MILLISECONDS, TimeUnit.SECONDS).readTimeout( DEFAULT_MILLISECONDS, TimeUnit.SECONDS).addInterceptor { chain -> val builder = chain.request().newBuilder() headerMap? .forEach { builder.addHeader(it.key, it.value) } val request = builder.build() chain.proceed(request) }.addInterceptor(httpLoggingInterceptor).build()Copy the code

OkHttpClient is created using Kotlin code. It is clear that OkHttpClient uses The Builder mode internally. The advantage is obvious: we can configure the parameters we need as we create the object. Let’s take a quick look at the constructors in the OkHttpClient inner class Builder to see what configuration can be done inside OkHttpClient:

public Builder() {// Default 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; // The default connection timeout is 10s connectTimeout = 10_000; // The read timeout is 10 seconds by defaultreadTimeout = 10_000; // default writeTimeout 10s writeTimeout = 10_000; pingInterval = 0; }Copy the code

The code above is we are very familiar with the connection timeout, read, write, overtime timeout, their default event is 10 s, but it also reminds us that if we want to set a timeout time is 10 s, completely unnecessary repeated configuration, my advice is don’t actually need to be configured, it is good to use the default directly. It is worth noting that the Dispatcher class, which is a Dispatcher for network requests, is mainly used to do different distribution processing for synchronous and asynchronous network requests. We first have an impression, and Dispatcher will analyze it in detail later.

If you don’t need to interceptor the OkHttpClient, you need to interceptor the OkHttpClient. If you don’t need to interceptor the OkHttpClient, you need to interceptor the OkHttpClient.

Yes, the Builder class in OkHttpClient does have interceptor member variables inside it, but not in the Builder constructor:

Public static final Class Builder {// omit irrelevant code...... final List<Interceptor> interceptors = new ArrayList<>(); final List<Interceptor> networkInterceptors = new ArrayList<>(); // omit extraneous code...... }Copy the code

The interceptor we normally add is stored in the ArrayList interceptors. The configuration creation of the OkHttpClient object is not a tricky point, so let’s look at the creation of the Request object.

Create the Request Request object

Why do we create a Request object? It’s very simple. We need some necessary parameters to Request the network, such as THE URL, whether the Request is get or POST, etc. The Request class encapsulates these network Request parameters. Take a look at the code:

public final class Request { final HttpUrl url; final String method; final Headers headers; final @Nullable RequestBody body; final Object tag; private volatile CacheControl cacheControl; // Lazily initialized. // omit irrelevant code...... }Copy the code

As you can see, this Request class encapsulates the URL, Request mode, Request header, Request body and other information related to network requests. Request is also a Builder mode, which I won’t go into here.

Creating a Call object

A Call object is a wrapper around a network request. If you are familiar with OkHttp, you know that a Call object can only be executed once, either synchronously or asynchronously. How can this be guaranteed? Let’s look at the code:

@override public Call newCall(Request Request) {realCall. newRealCall actually obtains the Call objectreturn RealCall.newRealCall(this, request, false/ *for web socket */);
  }
Copy the code

Realcall. newRealCall(this, Request, false /* for Web socket */);

final class RealCall implements Call { final OkHttpClient client; / / error retry and redirect the interceptor final RetryAndFollowUpInterceptor RetryAndFollowUpInterceptor; Private EventListener EventListener; final Request originalRequest; final booleanforWebSocket; Private Boolean executed; private RealCall(OkHttpClient client, Request originalRequest, booleanforWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forRealCall Call = new RealCall(client, originalRequest, originalRequest);forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    returncall; Override public Response execute() throws IOException {synchronized (this) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");
      executed = true; } // omit extraneous code...... } @override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");
      executed = true; } // omit irrelevant code} // omit irrelevant code...... }Copy the code

As you can see, Call is just an interface, and what we are actually creating is a RealCall object. There is a execute member variable in RealCall, and execute is used in both execute() and enqueue(Callback responseCallback) methods to ensure that each RealCall object is executed only once.

Creating a Call object is actually quite simple, but the trouble is in the last step: **execute() and enqueue(Callback responseCallback) **

Synchronous versus asynchronous requests

The first three steps are very simple, we can see that there is no network request involved, so the core must be in this critical fourth step.

Execute () and enQueue (Callback responseCallback)

Let’s start with the sync request and look at the code:

@override public Response execute() throws IOException {// Synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");
      executed = true; } captureCallStackTrace(); Eventlistener.callstart (this); eventListener.callStart(this); Try {// Call the dispenser's executed(this) method client.dispatcher().executed(this); / / here is the real network request processing of the Response result = getResponseWithInterceptorChain ();if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      //网络请求失败的回调
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //网络请求结束
      client.dispatcher().finished(this);
    }
  }
Copy the code

The execute() method first ensures that each Call object is executed only once by executing it, followed by a Call to eventListener.callstart (this); To perform the callback initiated by the network request. Then we call client.dispatcher().executed(this) :

public Dispatcher dispatcher() {// Returns a dispather dispenser within OkHttpClientreturn dispatcher;
  }
Copy the code

This code first returns a dispatcher, which we also mentioned above. This is an important concept. Take a look at this dispatcher:

Public final class Dispatcher {private int maxRequests = 64; Private int maxRequestsPerHost = 5; // Network request callback when idle private @nullable Runnable idleCallback; ExecutorService private @nullable ExecutorService; Private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); Private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); Private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); // Ignore extraneous code...... synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else{ readyAsyncCalls.add(call); Synchronized void executed(RealCall call) {// Synchronized void executed(RealCall call) {runningSynccalls.add (call); } // Ignore extraneous code...... }Copy the code

The Dispatcher class defines a number of member variables: maxRequests maximum number of requests (64 by default); MaxRequestsPerHost Specifies the maximum number of requests per host. What is this host? For example, if a URL is http://gank.io/api, then host is http://gank.io/ equivalent to baseUrl. ** idleCallback** This is an idle state callback that will be executed when all of our network request queues are empty. ExecutorService is a thread pool, created primarily to efficiently execute asynchronous network requests, and covered again later. Here are the three more important queues:

  • ReadyAsyncCalls -> Asynchronous Call queue in ready wait
  • RunningAsyncCalls -> Asynchronous Call queue that is executing
  • RunningSyncCalls -> Synchronous Call queues that are executing

For all three queues, the Call object that performs the synchronous request is added to runningSyncCalls; Call objects that perform asynchronous requests are added to readyAsyncCalls or runningAsyncCalls, so when are they added to the wait queue and when are they added to the execution queue? Simply put, if the thread pool executing the asynchronous network request is busy, the Call object of the asynchronous request is queued. Otherwise, it is added to the execution queue. So what is this criterion for being busy? Size () < maxRequests && runningCallsForHost(call) < maxRequestsPerHost When the number of Call objects in the executing asynchronous queue is less than maxRequests (64) and the number of Call objects corresponding to the same host in the executing queue is less than maxRequestsPerHost (5).

Having said the key member variables of the Dispatcher, let’s look at the **executed(RealCall call) ** method:

Synchronized void executed(RealCall call) {// Add a call object to a network request; synchronized void executed(RealCall call) {// Add a call object to a network request; }Copy the code

This is a synchronized modified method to ensure thread safety. The executed(RealCall Call) method in the Dispatcher is as simple as adding a call object to a synchronous call queue. Yeah, you read that right, it’s really just one line of code, nothing complicated.

Having said synchronous methods in Dispatcher, let’s look at asynchrony:

Synchronized void enqueue(AsyncCall Call) {// Determine whether the call object should be added to the wait queue or to the execution queueif(RunningAsynccalls.size () < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {// Join the queue runningAsyncCalls.add(call); ExecutorService ().execute(call); // Thread pool enables threads to execute asynchronous network requests. }else{// Join the queue readyAsynccalls.add (call); }}Copy the code

There is a little more content in the asynchronous method than in the synchronous method. The first is to decide whether the Call object should be added to the wait queue or to the execution queue, as described above. After joining the execution queue, open the thread pool and execute the Call object. AsyncCall is a NamedRunnable. This AsyncCall is a NamedRunnable. This AsyncCall is a NamedRunnable. We definitely want to look at its execute() method:

@Override protected void execute() {
      boolean signalledCallback = false; Try {/ request/core network method Response Response = getResponseWithInterceptorChain ();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 { client.dispatcher().finished(this); }}}Copy the code

Actually the whole piece of code looks like very much, the core is the Response the Response = getResponseWithInterceptorChain () this sentence: through the interceptor chain access network to return the result. This line of code is at the heart of synchronous requests, not just asynchronous requests. Let’s take a look at the execute method in RealCall:

@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); / / same as asynchronous request, the core is to get through the interceptor chain network data Response result = getResponseWithInterceptorChain ();if (result == null) throw new IOException("Canceled");
      returnresult; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); }}Copy the code

Obviously, in the client. The dispatcher () executed (this) will join the rest of the synchronous request queue Call object, it is also called the Response result = getResponseWithInterceptorChain (). Got it, both in a synchronous request or an asynchronous request, finally obtain the core of the network data processing are consistent: getResponseWithInterceptorChain ().

Let’s take a look at this method that is central to OkHttp:

Response getResponseWithInterceptorChain () throws IOException {/ / interceptors create stored list list < Interceptor > interceptors = new ArrayList<>(); // Add interceptors.addall (client.interceptors()) to the list of interceptors we added when configuring OkHttpClient; / / added 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) {/ / if it weren't for the network access of WebSocket, joined the network interceptor interceptors. AddAll (client.net workInterceptors ()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Chain = new RealInterceptorChain(Interceptors, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); // Execute interceptor chainreturn chain.proceed(originalRequest);
  }
Copy the code

In this method, we first create an ArrayList to hold all the interceptors. As you can see from top to bottom, seven different interceptors have been added:

  • Interceptors () -> A request interceptor that we add ourselves, usually to add a unified token
  • RetryAndFollowUpInterceptor – > is mainly responsible for errors and retry the request is redirected
  • BridgeInterceptor -> Is responsible for adding the necessary headers related to network requests, such as Content-Type, Content-Length, Transfer-Encoding, user-Agent, and so on
  • CacheInterceptor -> Handles cache-related operations
  • ConnectInterceptor -> Is responsible for connecting to the server
  • NetworkInterceptors -> is another type of interceptor that can be added. It differs from the client.interceptors() in that it intercepts from a different position.
  • CallServerInterceptor -> Only in this interceptor will the actual network request be made

After adding various interceptors, a chain of interceptors is created and the chain’s proceed method is executed. Let’s look at the proceed method:

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
Copy the code

This method calls another proceed method from within the RealInterceptorChain.

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection Connection) throws IOException {// Ignore extraneous code...... 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); Response = interceptor.intercept(next); // Ignore extraneous code......return response;
 }
Copy the code

There are two main steps in this method: Get the next interceptor in the interceptor chain and call the Intercept (Next) method of that interceptor. If you added the Interceptor when you built OkHttpClietn, In the Intercept () method we must have chain.proceed(request). Something like this:

OkHttpClient.Builder().connectTimeout( DEFAULT_MILLISECONDS, TimeUnit.SECONDS).readTimeout( DEFAULT_MILLISECONDS, TimeUnit.SECONDS).addInterceptor { chain -> val builder = chain.request().newBuilder() headerMap? .forEach { builder.addHeader(it.key, It. Value)} val request = Builder.build () }.addInterceptor(httpLoggingInterceptor).build()Copy the code

Chain.proceed (request) must exist inside the intercept method of each interceptor. Similarly, OkHttp interceptors must have chain.proceed(request) inside the Intercept method. Except for the last interceptor, CallServerInterceptor.

If the whole logic is a little confusing, it doesn’t matter, let’s sort it out.

  • GetResponseWithInterceptorChain () method by calling chain. Proceed (originalRequest) open the interceptor chain.
  • The Intercept (chain) method in the next interceptor is called in the Proceed (request) method of the RealInterceptorChain
  • Chain.proceed (request) is called in an intercept(chain). Since the interceptor obtained each time is the next interceptor in the list of interceptors, the interceptor method is implemented in order to call each different interceptor in the list of interceptors. Because the last interceptor was not calledchain.proceed(request), so it can end the loop call.

Here’s another picture to help you understand the interceptor chain:

This should give you some idea of how the chain of interceptors works. So far, OkHttp source code analysis is over. The implementation details of each interceptor can be read in depth if you are interested. I won’t go into details here.