OkHttp is a popular network request library for Android development. It supports HTTP1, HTTP2 and other protocols. It is a very helpful helper for our daily development. This article, based on the OkHttp version 4.9.0 code, starts with the creation of OkHttp and explores how OkHttp initiates a network request. If you have the patience to watch it, you will have a good understanding of OkHttp. If it’s too long to read, it’s also a good idea to take a look at the summary (manual funny).

OkHttpClient

The first step to using OkHttp, of course, is to create an OkHttpClient:

OkHttpClient client = new OkHttpClient();
Copy the code

Enter the constructor to see what this OkHttpClient really is:

constructor() : this(Builder()) // The default construct is passed in to the Builder instance

class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()/ / scheduler
    internal var connectionPool: ConnectionPool = ConnectionPool()/ / the connection pool
    internal val interceptors: MutableList<Interceptor> = mutableListOf()// Whole process interceptor
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()// Network request interceptor
  // Process listener
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    internal var retryOnConnectionFailure = true// Whether the request fails to be automatically retried
    internal var authenticator: Authenticator = Authenticator.NONE// Server authentication Settings
    internal var followRedirects = true// Whether to redirect
    internal var followSslRedirects = true// Whether you can redirect from HTTP to HTTPS
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES//Cookie policy, whether to save cookies
    internal var cache: Cache? = null// Cache configuration
    internal var dns: Dns = Dns.SYSTEM/ / Dns configuration
    internal var proxy: Proxy? = null// Proxy configuration
    internal var proxySelector: ProxySelector? = null// Proxy selector
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE// Proxy server authentication Settings
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()/ / socket configuration
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null/ / HTTPS socket configuration
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS// Support protocol configuration
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier// Verify domain name
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT/ / certificate chain
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0// Request timeout 0 indicates that the request will not timeout
    internal var connectTimeout = 10 _000// Connection timed out
    internal var readTimeout = 10 _000// Read times out
    internal var writeTimeout = 10 _000// Write timeout
    internal var pingInterval = 0// Ping interval for HTTP2 and Web sockets
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    internal var routeDatabase: RouteDatabase? = null
  / /...
Copy the code

As you can see, the OkHttpClient instance is a configuration class that takes the user-set configuration when the request is actually made. The design of Builder mode is adopted to make it more convenient for users to configure this series of parameters and construct more configurations flexibly.

Request

Basic request constructor class, which basically uses:

Request request = new Request.Builder()
    .url(ENDPOINT)
    .build();
Copy the code

The Request class describes parameters of a single Request, including domain name, Request mode, Request header, and Request body. Through Builder we can chain call, more elegant configuration of this series of information. The Request class, like the OkHttpClient class, is essentially a description object.

Call

Once the request is created, we can call OkHttpClient to initiate a request. We need to initiate a request using the OkHttpClient instance method:

Call call = client.newCall(request);
Copy the code

Call is an interface defined as follows:

interface Call : Cloneable {
  /** returns the original request information */
  fun request(a): Request
	
  The sync method cannot be called directly from the main thread
  @Throws(IOException::class)
  fun execute(a): Response

  /** Make an asynchronous request */
  fun enqueue(responseCallback: Callback)

  /** Cancel the request */
  fun cancel(a)

  /** Has been executed */
  fun isExecuted(a): Boolean
	
  /** Is cancelled */
  fun isCanceled(a): Boolean

  /** Request timeout configuration policy */
  fun timeout(a): Timeout

  /** clone this Call */
  public override fun clone(a): Call

  fun interface Factory {
    fun newCall(request: Request): Call
  }
}
Copy the code

The newCall method implemented by OkHttpClient creates an instance of RealCall, a bridge between the application and network layers that holds the OkHttpClient reference and the original request information. We need to track the network request flow by observing its implementation.

enqueue

override fun enqueue(responseCallback: Callback) {
  //CAS determines whether it has been executed
  check(executed.compareAndSet(false.true)) { "Already Executed" }
	// Request notification start
  callStart()
  // Create an asynchronous request to the request queue
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code

The asynchronous request method creates an AsyncCall and calls the OkHttpClient configured Dispatcher to handle the request.

inner class AsyncCall(
  private val responseCallback: Callback
) : Runnable
Copy the code

AsyncCall implements the Runnable interface and is eventually executed by the scheduler’s thread pool, which will be discussed later.

execute

override fun execute(a): Response {
  //CAS determines whether it has been executed
  check(executed.compareAndSet(false.true)) { "Already Executed" }

  timeout.enter()// Request timed out
  callStart()// Request notification start
  try {
    client.dispatcher.executed(this)// Use the scheduler to queue requests
    return getResponseWithInterceptorChain()// Request responsibility chain creation
  } finally {
    client.dispatcher.finished(this)// The scheduler ends the request}}Copy the code

After execute is invoked, the request is added to the synchronous request queue and the response responsibility chain is created to initiate the request. Request completion removes the request from the scheduler.

getResponseWithInterceptorChain

OkHttp: OkHttp: OkHttp: OkHttp: OkHttp

@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(a): Response {
  val interceptors = mutableListOf<Interceptor>()
  interceptors += client.interceptors// User-configured interceptor
  interceptors += RetryAndFollowUpInterceptor(client)// Rewire the redirect interceptor
  interceptors += BridgeInterceptor(client.cookieJar)// Build request and response basic information
  interceptors += CacheInterceptor(client.cache)// Cache configuration processing
  interceptors += ConnectInterceptor// The connection interceptor really starts to initiate the connection here
  if(! forWebSocket) { interceptors += client.networkInterceptors// Network interceptor
  }
  // Perform the flow operations (write out the request body, get the response data) to send the request data to the server and read the response data from the server
  // Encapsulates and parses HTTP request packets
  interceptors += CallServerInterceptor(forWebSocket)
	// Create a chain of responsibility
  val chain = RealInterceptorChain(
      call = this,
      interceptors = interceptors,
      index = 0,
      exchange = null,
      request = originalRequest,
      connectTimeoutMillis = client.connectTimeoutMillis,
      readTimeoutMillis = client.readTimeoutMillis,
      writeTimeoutMillis = client.writeTimeoutMillis
  )

  var calledNoMoreExchanges = false
  try {
    // Execute the chain of responsibility
    val response = chain.proceed(originalRequest)
    if (isCanceled()) {
      response.closeQuietly()
      throw IOException("Canceled")}return response// Return the request result
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw noMoreExchanges(e) as Throwable
  } finally {
    if(! calledNoMoreExchanges) { noMoreExchanges(null)}}}Copy the code

GetResponseWithInterceptorChain method will build according to certain order list of interceptors, here with the chain of responsibility pattern, after processing the interceptor list, will create the interceptor chain of responsibility, the interceptor can in order to invoke, after processing is completed, will return information back to the user.

cancel

override fun cancel(a) {
  if (canceled) return // Return if it has been cancelled

  canceled = trueexchange? .cancel()// Cancel the I/O operationconnectionToCancel? .cancel()// Close the socket connection

  eventListener.canceled(this)// Event notification
}
Copy the code

A cancellation of a request cancels subsequent IO operations and disconnects, followed by event notification. Because the connection and IO may not have started when this method is called, nulling is required.

RealInterceptorChain

By tracing the initiation of a synchronous request, we can see that we end up creating a RealInterceptorChain instance and calling its Proceed method, and then trace its code to see how it is implemented internally.

@Throws(IOException::class)
override fun proceed(request: Request): Response {
  check(index < interceptors.size)// Check subscript out of bounds

  calls++

  if(exchange ! =null) {
    check(exchange.finder.sameHostAndPort(request.url)) {
      "network interceptor ${interceptors[index - 1]} must retain the same host and port"
    }
    check(calls == 1) {
      "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"}}// The next interceptor to execute, index+1
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]
	// Call the intercept method of the interceptor, passing in the next responsibility chain
  @Suppress("USELESS_ELVIS")
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")

  if(exchange ! =null) {
    check(index + 1 >= interceptors.size || next.calls == 1) {
      "network interceptor $interceptor must call proceed() exactly once"} } check(response.body ! =null) { "interceptor $interceptor returned a response with no body" }

  return response
}
Copy the code

The proceed method is also not complicated, with a series of tests and only a few lines of core code, roughly as follows:

  • 1, array subscript +1, fetch the next interceptor, then copy and create a new responsibility chain

  • 2. Get the interceptor of the current subscript

  • 3. Call the intercept method of the current interceptor and pass in the next interceptor responsibility chain instance

    Why do I chain it down? Take a look at the Interceptor’s interface definition

    fun interface Interceptor {
      @Throws(IOException::class)
      fun intercept(chain: Chain): Response
    }
    Copy the code

    Interceptor has only one method. If you implement the Intercept method, you need to call the Chain passed in. This is the next Interceptor. The chain.proceed method is called to return a Response, passing the logic to the next interceptor.

Dispatcher

An asynchronous request calls dispatcher.enqueue. What does dispatcher do?

The Dispatcher is responsible for the execution logic of asynchronous requests. The Dispatcher can define maxRequests to manage the maximum number of concurrent requests and maxRequestsPerHost to determine the maximum number of concurrent requests for a single host.

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    // Join the queue
    readyAsyncCalls.add(call)

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if(! call.call.forWebSocket) {// Find other calls that exist for this host
      val existingCall = findExistingCallWithHost(call.host)
      // If a counter to reuse other calls is found
      if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  // Actually execute
  promoteAndExecute()
}
Copy the code

After enQueue is called, the request is locked, and readyAsyncCalls are added to the asynchronous queue, and the host of the current request is checked for any other calls. If any other calls are found, the request counter of other calls is reused. Finally go to promoteAndExecute to execute.

private fun promoteAndExecute(a): Boolean {
  this.assertThreadDoesntHoldLock()

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {/ / thread lock
    val i = readyAsyncCalls.iterator()
    // Iterate through the asynchronous request queue
    while (i.hasNext()) {
      val asyncCall = i.next()

      if (runningAsyncCalls.size >= this.maxRequests) break // When the maximum number of requests is exceeded, the loop is broken
      if (asyncCall.callsPerHost.get() > =this.maxRequestsPerHost) continue // The maximum number of single host requests is skipped

      i.remove()
      asyncCall.callsPerHost.incrementAndGet()/ / cas count
      executableCalls.add(asyncCall)// Join the executable queue
      runningAsyncCalls.add(asyncCall)// Join the executing queue
    }
    isRunning = runningCallsCount() > 0// Whether the flag is being executed
  }

  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)// Execute the request
  }

  return isRunning
}
Copy the code

The promoteAndExecute method iterates through the asynchronous request queue, and if the current maximum number of concurrent requests is reached, it jumps out without executing any requests. If the number of concurrent requests to a host reaches the upper limit, the request is skipped. Finally, the call is made for the request that can be executed. If the user does not set up a thread pool, the Dispatcher creates a thread pool internally to perform asynchronous network requests.

fun executeOn(executorService: ExecutorService) {
  client.dispatcher.assertThreadDoesntHoldLock()

  var success = false
  try {
    // Execute using the thread pool passed in
    executorService.execute(this)
    success = true
  } catch (e: RejectedExecutionException) {
    val ioException = InterruptedIOException("executor rejected")
    ioException.initCause(e)
    noMoreExchanges(ioException)
    responseCallback.onFailure(this@RealCall, ioException)
  } finally {
    if(! success) {// Notify dispatcher when a request fails
      client.dispatcher.finished(this) // This call is no longer running!}}}Copy the code

As mentioned above, AsyncCall implements the Runable interface itself. After being executed, the run method is called to execute the internal logic, which is basically the same as the synchronous request logic, so I won’t go into details here. After the request is completed, the Dispatcher finished method is called regardless of whether the result succeeds or fails.

internal fun finished(call: AsyncCall) {
  call.callsPerHost.decrementAndGet()/ / cas count
  finished(runningAsyncCalls, call)
}

private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      // Remove the current task from the queue
      if(! calls.remove(call))throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }
		// Try to perform other tasks
    val isRunning = promoteAndExecute()

    if(! isRunning && idleCallback ! =null) {
      idleCallback.run()// If it is currently idle for notification}}Copy the code

After the FINISHED method is invoked, the finished method removes the current request from the request queue and attempts to execute the remaining requests. The Dispatcher also maintains a synchronous request queue internally and follows similar logic when a synchronous request is completed.

RetryAndFollowUpInterceptor

This interceptor is used for error retries and redirects. Inside the interceptor is an endless loop.

try {
  response = realChain.proceed(request)
  newExchangeFinder = true
} catch (e: RouteException) {
  // The attempt to connect via a route failed. The request will not have been sent.
  if(! recover(e.lastConnectException, call, request, requestSendStarted =false)) {
    throw e.firstConnectException.withSuppressed(recoveredFailures)
  } else {
    recoveredFailures += e.firstConnectException
  }
  newExchangeFinder = false
  continue
} catch (e: IOException) {
  // An attempt to communicate with a server failed. The request may have been sent.
  if(! recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
    throw e.withSuppressed(recoveredFailures)
  } else {
    recoveredFailures += e
  }
  newExchangeFinder = false
  continue
}
Copy the code

The exception of the network request is caught, and then it decides whether to retry the request. If it works, the redirection correlation is evaluated and the corresponding request is created.

ExchangeFinder

This class in RetryAndFollowUpInterceptor call call. EnterNetworkInterceptorExchange (after request, newExchangeFinder) is created. This class is used to find a RealConnection available for the current request in the RealConnectionPool connection pool, and then open the connection for the following IO operations.

ConnectInterceptor

This interceptor opens the connection to the specified server and then executes the other interceptors

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  val realChain = chain as RealInterceptorChain
  val exchange = realChain.call.initExchange(chain)// Initialize Exchange
  val connectedChain = realChain.copy(exchange = exchange)// Pass Exchange for subsequent responsibility chains
  return connectedChain.proceed(realChain.request)
}
Copy the code

The interceptor calls the initExchange method of RealCall and passes the current chain of responsibilities through.

internal fun initExchange(chain: RealInterceptorChain): Exchange {
  synchronized(this) {
    check(expectMoreExchanges) { "released"} check(! responseBodyOpen) check(! requestBodyOpen) }val exchangeFinder = this.exchangeFinder!!
  / / before RetryAndFollowUpInterceptor incoming finder to find the encoder
  val codec = exchangeFinder.find(client, chain)
  // Create Exchange using the corresponding encoder
  val result = Exchange(this, eventListener, exchangeFinder, codec)
  this.interceptorScopedExchange = result
  this.exchange = result
  synchronized(this) {
    this.requestBodyOpen = true
    this.responseBodyOpen = true
  }

  if (canceled) throw IOException("Canceled")
  return result
}
Copy the code

initExchangeWill be used inExchangeFinderIn search of aExchangeCodecThis is an encoder for network requests, which are encoded in different ways for different protocols.

fun find(
  client: OkHttpClient,
  chain: RealInterceptorChain
): ExchangeCodec {
  try {
    // Look for a healthy connection
    valresultConnection = findHealthyConnection( connectTimeout = chain.connectTimeoutMillis, readTimeout = chain.readTimeoutMillis, writeTimeout = chain.writeTimeoutMillis, pingIntervalMillis = client.pingIntervalMillis, connectionRetryEnabled = client.retryOnConnectionFailure, doExtensiveHealthChecks = chain.request.method ! ="GET"
    )
    // Create the corresponding encoder
    return resultConnection.newCodec(client, chain)
  } catch (e: RouteException) {
    trackFailure(e.lastConnectException)
    throw e
  } catch (e: IOException) {
    trackFailure(e)
    throw RouteException(e)
  }
}
Copy the code

ExchangeFinder’s find method tries to find a connection to the server. If we trace the findHealthyConnection code, we see an endless loop of calls to the findConnection method to find an available connection. The findConnection code is longer, so I won’t post it here. The general logic is to find connections from the connection pool first, and if none is available, a RealConnection object is created and stored in the cache pool.

RealConnection

RealConnection is where OkHttp actually establishes the connection. Establish a link to the server through the CONNECT method. Through tracing the source code, we can find that RealConnection is still established through sockets at the bottom.

@Throws(IOException::class)
private fun connectSocket(
  connectTimeout: Int,
  readTimeout: Int,
  call: Call,
  eventListener: EventListener
) {
  val proxy = route.proxy
  val address = route.address

  val rawSocket = when (proxy.type()) {
    Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
    else -> Socket(proxy)
  }
  this.rawSocket = rawSocket

  eventListener.connectStart(call, route.socketAddress, proxy)
  rawSocket.soTimeout = readTimeout
  try {
    // Adapt for different platforms
    Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
  } catch (e: ConnectException) {
    throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
      initCause(e)
    }
  }
  try {
    // Start IO with OkIO
    source = rawSocket.source().buffer()
    sink = rawSocket.sink().buffer()
  } catch (npe: NullPointerException) {
    if (npe.message == NPE_THROW_WITH_NULL) {
      throw IOException(npe)
    }
  }
}
Copy the code

CallServerInterceptor

This is the last interceptor of all interceptors. In this interceptor IO operations are performed to interact with the server. OkHttp uses OkIO underneath to perform IO operations.

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  val realChain = chain as RealInterceptorChain
  val exchange = realChain.exchange!!// A bridge to exchange data, specifically to distribute the specific exchange logic to different implementations
  val request = realChain.request
  val requestBody = request.body
  val sentRequestMillis = System.currentTimeMillis()

  var invokeStartEvent = true
  var responseBuilder: Response.Builder? = null
  var sendRequestException: IOException? = null
  try {
    // Writing the request header is eventually done by calling the concrete ExchangeCodec
    exchange.writeRequestHeaders(request)

    if(HttpMethod.permitsRequestBody(request.method) && requestBody ! =null) {
      // When HTTP/1.1 expects: 100-continue, the request is initiated directly
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        exchange.flushRequest()// Send a request packet
        responseBuilder = exchange.readResponseHeaders(expectContinue = true)// Read the response header
        exchange.responseHeadersStart()// Event notification
        invokeStartEvent = false
      }
      if (responseBuilder == null) {
        if (requestBody.isDuplex()) {
         	// For the HTTP2 protocol, the request header is sent first
          exchange.flushRequest()
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)// Write the request body
        } else {
          val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
          requestBody.writeTo(bufferedRequestBody)// Write the request body
          bufferedRequestBody.close()
        }
      } else {
        // No request body
        exchange.noRequestBody()
        if(! exchange.connection.isMultiplexed) { exchange.noNewExchangesOnConnection() } } }else {
      // No request body
      exchange.noRequestBody()
    }

    if (requestBody == null| |! requestBody.isDuplex()) { exchange.finishRequest()// End the request to write and send}}catch (e: IOException) {
    if (e is ConnectionShutdownException) {
      throw e // No request was sent so there's no response to read.
    }
    if(! exchange.hasFailure) {throw e // Don't attempt to read the response; we failed to send the request.
    }
    sendRequestException = e
  }

  try {
    if (responseBuilder == null) {
      // Read the response header
      responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!!!!if (invokeStartEvent) {
        // Event notification
        exchange.responseHeadersStart()
        invokeStartEvent = false}}/ / build the Response
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    var code = response.code
    if (code == 100) {
      // Server sent a 100-continue even though we did not request one. Try again to read the
      // actual response status.
      responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!!!!if (invokeStartEvent) {
        exchange.responseHeadersStart()
      }
      // re-read if code is 100
      response = responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      code = response.code
    }
 	 // Event notification
    exchange.responseHeadersEnd(response)

    response = if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      / / write ResponseBody
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
    // A Header is received to close the connection
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) | |"close".equals(response.header("Connection"), ignoreCase = true)) {
      exchange.noNewExchangesOnConnection()
    }
    if ((code == 204 || code == 205) && response.body? .contentLength() ? : -1L > 0L) {
      throw ProtocolException(
          "HTTP $code had non-zero Content-Length: ${response.body? .contentLength()}")}/ / returns the Response
    return response
  } catch (e: IOException) {
    if(sendRequestException ! =null) {
      sendRequestException.addSuppressed(e)
      throw sendRequestException
    }
    throw e
  }
}
Copy the code

The CallServerInterceptor intercept method is long, but the logic isn’t complicated. The general process is as follows:

  • 1. Write the Request line and Request header according to the Request configuration.
  • 2. Determine whether the request body is supported according to Method. If yes, try to write the request body and send the request message; otherwise, send the request message directly
  • 3. Read the Response message and construct Response
  • 4, Read the Response body and write ResponseBody for Response
  • 5. Determine whether to close the connection
  • 6. Return Response

Because the CallServerInterceptor is the last Interceptor, the Response returned will be passed up level by level, and finally the user can get the wrapped Response.

BridgeInterceptor

The interceptor serves as a bridge between the application and the network. First, it retrieves information from the Request and adds headers to the Request based on the content of the Request, which the user is unaware of. The interceptor also reads the Cookie configuration and, if there is Cookie information, carries it to the server via the request header.

After the Request information is completed, the subsequent responsibility chain will be called to process the completed Request and wait for the subsequent return.

		val networkResponse = chain.proceed(requestBuilder.build())// Wait for the response

    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)/ / processing cookies

    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)// Put the original request information into Response

    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
      val responseBody = networkResponse.body
      if(responseBody ! =null) {
        val gzipSource = GzipSource(responseBody.source())
        val strippedHeaders = networkResponse.headers.newBuilder()
            .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build()// Streamline the response header
        responseBuilder.headers(strippedHeaders)
        val contentType = networkResponse.header("Content-Type")
        / / ResponseBody processing
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }
		// Return a complete Response
    return responseBuilder.build()
Copy the code

When subsequent returns are received, the Response is processed and some of the Response headers are condensed. The completed Response is eventually passed back to the user. A lot of operations are completed without the user’s awareness, and have deep merit and reputation.

Response

There’s nothing left to say, is there? After some chain calls and processing, the end user can get a Response object. This is where the user gets the requested information and answers, and the rest is up to the user. After the user gets the Response, a network request is completed!

conclusion

Without further ado, picture above

To recap, the OkHttpClinent class is a configuration class for network requests. We describe our Request information by building a Request, and then create a RealCall instance using the newCall method.

RealCall can be executed synchronously or asynchronously and is a bridge between us and the network. If it is a synchronous request direct call gerResponseWithInterceptorChain () method to create interceptor chain of responsibility. If it is an asynchronous request, will be distributed to the Dispatcher, the Dispatcher will eventually use the thread pool to carry out the request, will eventually go gerResponseWithInterceptorChain () method.

In gerResponseWithInterceptorChain () method, all the interceptors will be encapsulated in sequence into a list, build a RealInterceptorChain. The PROCEED method calls the Intercept method of each interceptor, and the interceptor continues to call the PROCEED method after processing has done its job.

RetryAndFollowUpInterceptor handles error retry and redirect, BridgeInterceptor responsible for packing requests and return data, CacheInterceptor is responsible for the processing of the cache, The ConnectInterceptor actually opens the connection, and finally uses the CallServerInterceptor for network IO, sending and processing packets.

OkHttp uses Socket for network connection and OkIO for network IO. It has connection pool logic and stores RealConnection instances to reduce the overhead caused by too many connections.

Reading source code is not difficult, the difficult thing is to write source code should have the idea. Reading OkHttp’s source code allows us to learn many design ideas: building complex objects through Builder mode, distributing tasks and sending back in order through chain of responsibility mode, abstraction of functions, and so on. Good source code reading of open source framework can really let us gain a lot, we should learn to apply, so that one day when someone asks you to do a similar feature, you will not have a clue. I hope this article can make you gain, of course, I suggest you read the source code, perhaps there will be different harvest.

It’s not easy to see here, so feel free to give a thumbs up and share your different opinions (#^.