At the forefront of

The essential web framework for every Android development is, of course, OKHTTP, an open source project for handling web requests that has become the most popular Android lightweight framework. The following students I briefly record Okhttp framework source analysis journey

Introduction to the use of the OKHTTP framework

No matter what open source framework we learn, the first thing we learn is how to use it. Only by skillfully using the framework can we further analyze its underlying principles systematically

Okhttp Synchronous method request

  • First, let’s take a quick look at how okHTTP synchronous requests are used. There are roughly three steps
  1. Create the OkHttpClient and Request objects using the Builder design pattern
  2. Encapsulate the Request as a Call object
  3. Call the Call’s execute() to send synchronization to the request (the Call is actually an interface)

The specific code can be viewed as follows:

private fun synRequest(a) {
        val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
        val request = Request.Builder().url("https://www.baidu.com").get().build()
        val call = client.newCall(request)
        kotlin.runCatching {
            val response = call.execute()
            println(response.body().toString())
        }.onFailure {
            println("the error is $it")}}Copy the code

Ps: OKHTTP synchronization Note that once a request is sent, it blocks until a response is received

Okhttp Asynchronous method request

  1. Create the OkHttpClient and Request objects
  2. Encapsulate the Request as a Call object
  3. The asynchronous request is made by calling the enqueue method of the Call, which is then handled in onResponse and onFailure in the Callback
   private fun asyRequest(a) {
        val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
        val request = Request.Builder().url("https://www.baidu.com").get().build()
        val call = client.newCall(request)
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                println(e.message)
            }

            override fun onResponse(call: Call, response: Response) {
                println(response.body().toString())
            }

        })
    }

Copy the code

Ps: It is important to note that both onResponse and onFailure are executed in the child thread

Okhttp synchronization asynchronous different

  1. The method invocation that initiates the request is different. The synchronous invocation is call.execute (), and the asynchronous invocation is enqueue
  2. Synchronization blocks threads, but asynchrony does not. Instead, a new worker thread is opened to complete the network request
  3. The main difference between synchronous and asynchronous is that asynchronous requests start a thread AsyncCall, which is simply a Runnable

Summary of okHTTP synchronous asynchronous methods

Ps: Insert picture here

  • Simply put, whether synchronous Request or asynchronous Request, we need to create Request Request message, through the newCall method in Request, to create our actual HTTP Request Call
  • Call itself is an interface, and its concrete implementation class is in RealCall. In this case, we need to determine the invoked Chain method to synchronize the request.

Or AsyncCall makes asynchronous requests

  • Both synchronous and asynchronous request, will use getResponseWithInterceptorChain method, this is one of the core okhttp, inside is actually build the interceptor chain, through in order to carry out the interceptor in the chain blocker to the role of each of the different data access server to return
  • The Dispatcher master class is actually a dispenser of OKHTTP, and it’s a choice between executing this asynchronous request and being ready

Request flow and source code analysis

Now that we know the basic usage of OKHTTP requests, let’s analyze a wave of OKHTTP requests in synchronous and asynchronous flow through the corresponding source code

Synchronous request process source code analysis

  1. First of all, if you want to build okhttpClient, you need to use its inner class to set parameters. First of all, you can look at the constructor of okhttpClient, using Builder design pattern, because this object uses many parameters (additional topic, if you need to use many parameters, Then you can use the builder pattern.)
public Builder(a) {
      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
  • The dispatcher object, as mentioned above, is the okHTTP dispatcher object. It does not do much for synchronous requests and is simply placed in the synchronous request queue
  • ConnectPool refers to the connection between the client and the server. Each connection is placed in the ConnectionPool and managed by the ConnectionPool in a unified manner, which network connections can be kept open and which network connections can be reused.
  1. Request the request
  • First we look at how the internal build constructor is implemented. We can see that there are only two lines of code
public Builder(a) {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

Copy the code
  • The first line specifies the request mode, in this case GET
  • The second line creates the Headers internal class Builder object to hold its header information
  • Next, let’s look at its build method
 public Request build(a) {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
Copy the code
  • We create a Request object and pass in the current build object, so that the intent is clear: we set the Request object, the URL, and the header that we configured before.
  • Take a look at the request constructor, which, unsurprisingly, specifies the required request method, network address, header information, and so on
Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag ! =null ? builder.tag : this;
  }
Copy the code
  1. This completes the first two steps of the synchronization request
  • Create an okHttpClient object
  • The Request object that carries the Request information is built
  1. After completing the first two steps, we build the Call object for our ShijiHTTP request from the okHttpClient and request requests
  • First of all, it is through the implementation of the NewCall class, found that it is through RealCall to achieve, the source code is as follows
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
Copy the code
  • Let’s look at the RealCall implementation. How does it work?
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
  • When you create a RealCall object, assign a listener and return it, it’s worth asking yourself, what does this method actually do, or do you want to look inside the RealCall method
  • Let’s see what the RealCall method looks like. Okay
  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }
Copy the code
  • You can see here that it holds the okHttpClient and Request objects that were initialized in the previous two steps
  • It also assigns a redirect interceptor, which is worth learning more about
  1. The next step is to execute the call.execute() method to complete the synchronous request. Let’s take a look at the implementation of this method
 @Override public Response execute(a) throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      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 {
      client.dispatcher().finished(this); }}Copy the code
  • In the synchronized code block, first determine whether the executed flag is true, so why? This means that the same HTTP request will only be executed once. If it has not been executed, the flag bit will be set to true. If it has been executed, an exception will be thrown
  • CaptureCallStackTrace () captures some HTTP request stack information
  • With the listening time callStart() turned on, the source code comments are clear and will be turned on whenever our excute or enqueue methods use this listener
  • Then, of course, comes the most important thing, the Dispatcher method and the excute method for synchronization requests, which you can see in action here, adding it to the synchronization request queue via the synchronized method
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}
Copy the code
  • What about the dispatch method? What does that do? It maintains the state that call requests are sent to it, and it also maintains a pool of threads for executing network requests; When a call request executes a task, it is pushed to the execution queue for operation through dispatch, the distribution base class. When the operation is complete, the waiting queue task is executed
  • Above all, after the completion of execution getResponseWithInterceptorChain () method invocation interceptors in turn to for the corresponding operation
  • Finally, there is a subtle and important mechanism by which Finish automatically retrieves some synchronization requests. If you look at the implementation, the first action is to remove the synchronization request from the current queue. If the synchronization request cannot be removed, an exception is thrown. So synchronous requests do not call promoteCalls(), but asynchronous requests do; It then calls runningCallsCount() to count the number of requests that are currently being executed, and it then calls its run method if the number of requests being executed is zero and idleCallback is not empty
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();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0&& idleCallback ! =null) { idleCallback.run(); }}Copy the code

Through their own understanding, so far synchronous request process source analysis is completed

Summary of synchronous request process

  • Create an OkhttpClient object
  • Build a Request object, using OkhttpClient and the Request object, build a Call object
  • The execute method of the call is executed. Dispatcher distribution is less important for synchronous requests than for asynchronous requests