One, foreword

As an excellent web request library, OkHttp has accompanied us Android developers through many a rough night, so today I will write an in-depth understanding essay. In the whole series, OkHttp 3.14.0 version, according to the use of OkHttp as a lead, do the following structure:

  1. The implementation flow of synchronous requests.
  2. The implementation flow of asynchronous requests
  3. Important interceptor: parsing of the CacheInterceptor.
  4. Important interceptor: Resolution of the ConnectInterceptor.
  5. Important interceptor: resolution of the CallServerInterceptor.

Two, synchronous/asynchronous request implementation process

[2.1] Synchronous request
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(url) .post(body) .build(); // see [2.2] client.newCall() // see [2.3] realCall.execute () try (Response Response = client.newCall(request).execute()) {returnresponse.body().string(); }}Copy the code

The preceding is a synchronous network request, which mainly does the following:

  1. Build the Request using Builder mode.
  2. Call client.newCall() to generate a Call object from Request. His implementation class is RealCall.
  3. Realcall.execute () is then called to make the synchronization request.
【 2.2 】 OkHttpClient. NewCall ()
Java @override public Call newCall(Request Request) {// Create a new Realcall object by calling the Realcall method.return RealCall.newRealCall(this, request, false/ *for web socket */);
  }
  
  RealCall.java
  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
  
 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {// The OkHttpClient object is held by Call. this.client = client; this.originalRequest = originalRequest; this.forWebSocket =forWebSocket;
  }

Copy the code

As shown above, the call chain of okHttpClient.newCall () eventually builds a RealCall object and uses client as a member variable of RealCall for subsequent requests to get configuration from client.

【 2.3 】 RealCall. The execute ()
Realcall.java.override public Response Execute () throws IOException {synchronized (this) {// An error is reported if the request has already been executed.if (executed) throw new IllegalStateException("Already Executed");
      executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); Try {// See [2.4] to retrieve the Dispatcher from the client and record the request. client.dispatcher().executed(this); // See [2.5] requests will be made and results returned through the chain of responsibility. This is a synchronous action that blocks.returngetResponseWithInterceptorChain(); } finally {// The request is removed from the dispatcher client.dispatcher().finished(this); }}Copy the code

A Call does the following when it is executed:

  1. Judge the legality of Call.
  2. Executed (); the Dispatcher is created as a member of the OkHttpClient constructor.
  3. Turn on responsibility chain mode for request logic.
  4. When the execution is complete, the scheduler does the finishing work on the request.
【 2.4 】 the Dispatcher. Executed ()
Dispatcher.java  
 /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque
      
        runningAsyncCalls = new ArrayDeque<>(); synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
      Copy the code

The request is added to a set of two – end queue data.

【 2.5 】 RealCall. GetResponseWithInterceptorChain ()
Response getResponseWithInterceptorChain () throws IOException {/ / will request specific logic layer, and adopt the way of the chain of responsibility for construction. List<Interceptor> interceptors = new ArrayList<>(); // The user's own request interceptors.addall (client.interceptors()); / / retry and redirect the interceptor interceptors. Add (new RetryAndFollowUpInterceptor (client)); Add (new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); // Interceptors. add(new ConnectInterceptor(client));if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } // The network request interceptor will generate a Response interceptors.add(new CallServerInterceptor();forWebSocket)); // Construct a chain of responsibility for the request processing logic as described above, noting that the chain begins with the interceptor at position 0. Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); boolean calledNoMoreExchanges =false; Try {// For details see [2.6], press the switch to process the logic chain. Response response = chain.proceed(originalRequest);if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled"); } // return the request resultreturn response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if(! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code

As we can also tell by its name, this method is the process of getting Response through the interceptor chain. He did the following:

  1. Add user-defined interceptors to the collection first.
  2. Add interceptors that are required in a request. These interceptors represent a complete network request logic divided into several layers and their sequence. From the code we can see that their flow is: retry/redirect logic -> network bridge logic -> cache logic -> establish network connection logic -> network traffic logic.
  3. Construct a logically-processed chain of interceptors from the above collection and assign the interceptor subscript that the chain needs to use to 0, starting with the first interceptor.
  4. Call chain.proceed() to start the chain’s process. Using the responsibility chain design pattern to process the logic in a network request can effectively divide the logical layer. The previous interceptor can decide whether the next interceptor should continue processing based on the actual processing.

【 2.6 】 RealInterceptorChain. Proceed ()

RealInterceptorChain.java
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if(index >= interceptors.size()) throw new AssertionError(); calls++; . /** * index+1: builds a new chain of interceptors, but the new chain of interceptors is index+1 * implementation of the responsibility chain, handle the flow of logic. */ RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange, index + 1, request, call, connectTimeout,readTimeout, writeTimeout); // index = 0; So we get the first interceptor and call its Intercept method for specific logic processing. Interceptor interceptor = interceptors.get(index); // The current interceptor processes the network request. Response response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed().if(exchange ! = null && index + 1 < interceptors.size() && next.calls ! = 1) { throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once"); } // omit the check code for the validity of response...return response;
  }
Copy the code

Conclusion:

  1. Index +1 builds the chain of responsibility that index points to the next interceptor.
  2. Fetch the interceptor subscript current index (0 at this point) in the chain and call Intercept (Next) for the actual processing of the interceptor logic. Note here that the parameters passed in are the next chain of responsibility built up in 1. After doing its own logic inside the interceptor.intercept () method, it calls next.proceed() for a pass that is handled by the next Interceptor. This request is processed in the interceptors as well -> pass -> process ->… -> Returns the result

Three, asynchronous request implementation process

3.1

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(url) .post(body) .build(); //client.newCall() : same as [2.2] // see [3.2] realCall.enqueue () try (Response Response = client.newCall(request)Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        }) {
    returnresponse.body().string(); }}Copy the code

The above is an OkHttp asynchronous request, which is basically the same as the synchronous request [2.1], except that the call is called asynchronously through realCall.enqueue (). The result of the request is called back to the main thread via Callback.

【 3.3 】 RealCall. The enqueue ()

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true; } transmitter.callStart(); // see [3.5] : dispatcher.enqueue () client.dispatcher().enqueue(new AsyncCall(responseCallback)); }Copy the code

The user-created callback is passed as an argument to the AsyncCall() constructor. AsyncCall inherits from Runnable.

【 3.4 】 AsyncCall;

AsyncCall.java final class AsyncCall extends NamedRunnable { private volatile AtomicInteger callsPerHost = new AtomicInteger(0); . /** * The asynchronous request will execute the network request in the thread pool allocated to dispather when it needs to execute the request. */ void executeOn(ExecutorService executorService) { ... boolean success =false; Try {// The key to asynchrony: put the request into a thread pool for execution. executorService.execute(this); success =true;
      } catch (RejectedExecutionException e) {
       ...
       success = false;
      } finally {
        if(! success) { client.dispatcher().finished(this); // Finished with Dispatcher when execution fails. AsyncCall will never be used again. } } } @Override protected voidexecute() {
      boolean signalledCallback = false; transmitter.timeoutEnter(); Try {/ / here with [2.5] Response Response = getResponseWithInterceptorChain (); signalledCallback =true; / / request is successful, the callback Response to user responseCallback. OnResponse (RealCall. This Response). } catch (IOException e) { ... / / request error, error callback interface to users responseCallback onFailure (RealCall. This, e); } finally {// see [3.6] to end a request. client.dispatcher().finished(this); }}}Copy the code

AsyncCall inherits from Runnable, which provides the ability to place calls into a thread pool for execution, enabling asynchronous flow of requests.

【 3.5 】 the Dispatcher. The enqueue ();

Dispatcher.java // request ready for asynchronous invocation. private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); // An asynchronous request in progress. private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); Void enqueue(AsyncCall call) {synchronized (this) {readyAsynccalls.add (call); // find if there is a request to Host, if there is a reuseif(! call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host());if(existingCall ! = null) call.reuseCallsPerHostFrom(existingCall); }} // Perform runningAsyncCalls with promoteAndExecute(); // Perform runningAsyncCalls with promoteAndExecute(); }Copy the code

Conclusion:

  1. Enrol this request on the Dispatcher’s standby two-endian queue.
  2. The Host of this request is used to find doable asynchronous requests and reuse them if they exist.
  3. Attempt to execute the request that was just added to the reserve team.

【 3.6 】 Dipatcher. Finish ()

Dipatcher.java private <T> void finished(Deque<T> calls, T call) { Runnable idleCallback; synchronized (this) { ... After a request is completed, check whether there is any request waiting to be executed and process it. boolean isRunning = promoteAndExecute();if(! isRunning && idleCallback ! = null) {// Tell idlecallback.run (); }}Copy the code

Summary: The scheduler ends a request

  1. When an asynchronous task completes, the scheduler triggers a preparatory task execution flow. Allow requests that could not be executed before due to limitations such as maximum number of requests to be executed.
  2. The scheduler is notified of its idle state by idlecallback.run ().

【 3.7 】 Dipatcher. PromoteAndExecute ()

 private boolean promoteAndExecute() {... List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) {for(Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); // Check the maximum request limit andif (runningAsyncCalls.size() >= maxRequests) break;
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // If the condition is met, the reserve request is promoted to the execution queue. i.remove(); asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } // Put executable asynchronous requests into the thread pool for executionfor(int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); ExecutorService (executorService()); asynccall.executeon (executorService()); }returnisRunning; ! [](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/21/172d4b4a7205d520~tplv-t2oaga2asx-image.image )}Copy the code

Summary: This method is an attempt to promote a request from a preparatory queue to an execution queue and execute it. If it cannot be executed, the start time will be delayed until the end of other requests (e.g. [3.6] logic).

Conclusion: In this article, the usage of Okhttp is the main line, exploring its synchronous request, asynchronous request code logic. OkHttp’s main design pattern, the chain of responsibility pattern, is also involved. Finally, we use two images (not drawn by myself) to understand the whole process of synchronous and asynchronous requests:

A synchronous requestCopy the code

Asynchronous request flowCopy the code