This analysis is based on OKHTTP 4.9.3.

Okhttp project address

preface

Android projects are built around web requests, and when you think of web requests, okHTTP comes to mind.

How okHTTP completes a request is explained step by step.

First, take a look at the full flow chart, directly using the Piasy diagram

How to initiate a request

1. Use configuration in Android project

Add network request permissions < USES – permission android: name = “android. Permission. INTERNET” / >

Add depend on implementation “. Com. Squareup okhttp3: okhttp: 4.9.3.”

2. Initiate a request

Take sending a GET request as an example. Other types of requests are also set when the ReqUST object is created.

// Create an OkHttpClient instance object with the timeout set
val client = OkHttpClient.Builder()
    .callTimeout(10, TimeUnit.SECONDS)
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .build()

// Create a requset object and set the URL, request mode, header and other information
val request = Request.Builder().url(url).build()

// Execute synchronously in a child thread
GlobalScope.launch(Dispatchers.IO) {
    valresponse = client.newCall(request).execute().body? .string() }// Execute asynchronously
client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException){ Log.d(TAG, e.message ? :"have IOException")}override fun onResponse(call: Call, response: Response) {
        valresponse = response.body? .string() Log.d(TAG,"enqueue() response = $response")}})Copy the code

2. What happened when you initiated a request

Analyzing the objects used in the above GET request, the source code only preserves the important parts of the process.

1. OkHttpClient

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

    ...
    
    // OkHttpClient can build Call because call.factory is implemented
    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)...// Default build parameters
    class Builder constructor() {
      internal var dispatcher: Dispatcher = Dispatcher() / / scheduler
      internal var connectionPool: ConnectionPool = ConnectionPool() / / the connection pool
      internal val interceptors: MutableList<Interceptor> = mutableListOf() / / the interceptor
      internal val networkInterceptors: MutableList<Interceptor> = mutableListOf() // Network interceptor
      internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
      internal var retryOnConnectionFailure = true // Try again if the connection fails
      internal var authenticator: Authenticator = Authenticator.NONE / / the validator
      internal var followRedirects = true // Allow redirection
      internal var followSslRedirects = true // Allow redirection between HTTPS and HTTP
      internal var cookieJar: CookieJar = CookieJar.NO_COOKIES // cookie
      internal var cache: Cache? = null / / cache
      internal var dns: Dns = Dns.SYSTEM
      internal var proxy: Proxy? = null
      internal var proxySelector: ProxySelector? = null
      internal var proxyAuthenticator: Authenticator = Authenticator.NONE
      internal var socketFactory: SocketFactory = SocketFactory.getDefault()
      internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
      internal var x509TrustManagerOrNull: X509TrustManager? = null
      internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
      internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
      internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
      internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
      internal var certificateChainCleaner: CertificateChainCleaner? = null
      internal var callTimeout = 0
      internal var connectTimeout = 10 _000
      internal var readTimeout = 10 _000
      internal var writeTimeout = 10 _000
      internal var pingInterval = 0
      internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
      internal var routeDatabase: RouteDatabase? = null
      
      // Pass in the parameters you build
      internal constructor(okHttpClient: OkHttpClient) : this() {
        this.dispatcher = okHttpClient.dispatcher
        this.connectionPool = okHttpClient.connectionPool
        this.interceptors += okHttpClient.interceptors
        this.networkInterceptors += okHttpClient.networkInterceptors
        this.eventListenerFactory = okHttpClient.eventListenerFactory
        this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
        this.authenticator = okHttpClient.authenticator
        this.followRedirects = okHttpClient.followRedirects
        this.followSslRedirects = okHttpClient.followSslRedirects
        this.cookieJar = okHttpClient.cookieJar
        this.cache = okHttpClient.cache
        this.dns = okHttpClient.dns
        this.proxy = okHttpClient.proxy
        this.proxySelector = okHttpClient.proxySelector
        this.proxyAuthenticator = okHttpClient.proxyAuthenticator
        this.socketFactory = okHttpClient.socketFactory
        this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
        this.x509TrustManagerOrNull = okHttpClient.x509TrustManager
        this.connectionSpecs = okHttpClient.connectionSpecs
        this.protocols = okHttpClient.protocols
        this.hostnameVerifier = okHttpClient.hostnameVerifier
        this.certificatePinner = okHttpClient.certificatePinner
        this.certificateChainCleaner = okHttpClient.certificateChainCleaner
        this.callTimeout = okHttpClient.callTimeoutMillis
        this.connectTimeout = okHttpClient.connectTimeoutMillis
        this.readTimeout = okHttpClient.readTimeoutMillis
        this.writeTimeout = okHttpClient.writeTimeoutMillis
        this.pingInterval = okHttpClient.pingIntervalMillis
        this.minWebSocketMessageToCompress = okHttpClient.minWebSocketMessageToCompress
        this.routeDatabase = okHttpClient.routeDatabase
      }
      
      ...
}
Copy the code

Use builder mode to set interceptor, timeout, cache, redirection, and other properties.

2. Request

class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,
  @get:JvmName("method") val method: String,
  @get:JvmName("headers") val headers: Headers,
  @get:JvmName("body") valbody: RequestBody? .internal val tags: Map<Class<*>, Any>
) {
    
    ...
    
    
    open class Builder {
      internal var url: HttpUrl? = null
      internal var method: String
      internal var headers: Headers.Builder
      internal var body: RequestBody? = null

      internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()

      constructor() {
        this.method = "GET"
        this.headers = Headers.Builder()
      }

      internal constructor(request: Request) {
        this.url = request.url
        this.method = request.method
        this.body = request.body
        this.tags = if (request.tags.isEmpty()) {
          mutableMapOf()
        } else {
          request.tags.toMutableMap()
        }
        this.headers = request.headers.newBuilder()
      }

      open fun url(url: HttpUrl): Builder = apply {
        this.url = url
      }
      
      ...
}
Copy the code

Also use builder mode to set request method, URL, request body, and header information.

3. Call

interface Call : Cloneable {
  // Return the call request object
  fun request(a): Request

  // Perform a synchronization request
  @Throws(IOException::class)
  fun execute(a): Response

  // Perform an asynchronous request
  fun enqueue(responseCallback: Callback)
    
  / / cancel
  fun cancel(a)
  
  // Whether the request has been executed
  fun isExecuted(a): Boolean

  // Whether it has been cancelled
  fun isCanceled(a): Boolean
  
  // Timeout information
  fun timeout(a): Timeout

  // Clone the current call and return the new object to execute the request again. The same call can only be executed once
  public override fun clone(a): Call

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

Interface class, which defines the methods that may be used in a request.

4. RealCall

The Call interface, which implements the Call interface, is the ultimate request executor, which is divided into synchronous and asynchronous requests.

A. Synchronous request part
// RealCall.execute
override fun execute(a): Response {
  // Each call can be requested only once
  check(executed.compareAndSet(false.true)) { "Already Executed" }

  timeout.enter()
  callStart()
  try {
    // Add the current call to the runningSyncCalls list of the Dispatcher class
    client.dispatcher.executed(this)
    // Get the result of the request through a series of interceptors
    return getResponseWithInterceptorChain()
  } finally {
    // Remove the current call from the runningSyncCalls list of the Dispatcher class
    client.dispatcher.finished(this)}}Copy the code
// RealCall.getResponseWithInterceptorChain
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(a): Response {
  // Build a full stack of interceptors.
  val interceptors = mutableListOf<Interceptor>()
  // Start adding interceptors
  // Interceptor set in OkHttpClient
  interceptors += client.interceptors
  // An interceptor that handles retry and redirection
  interceptors += RetryAndFollowUpInterceptor(client)
  // An interceptor that converts application requests into network requests and request results into user-friendly responses
  interceptors += BridgeInterceptor(client.cookieJar)
  // Read the cache, update the cache interceptor
  interceptors += CacheInterceptor(client.cache)
  // Interceptor that connects to the server
  interceptors += ConnectInterceptor
  if(! forWebSocket) {// networkInterceptors set in OkHttpClient
    interceptors += client.networkInterceptors
  }
  // The interceptor that requests the server and reads the response
  interceptors += CallServerInterceptor(forWebSocket)

  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 {
    // Start chain-calling the processing in the interceptor
    val response = chain.proceed(originalRequest)
    if (isCanceled()) {
      response.closeQuietly()
      throw IOException("Canceled")}// Return the result
    return response
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw noMoreExchanges(e) as Throwable
  } finally {
    if(! calledNoMoreExchanges) { noMoreExchanges(null)}}}Copy the code

Main processing logic in getResponseWithInterceptorChain () in the method, the dispatcher to change the execution status.

B. Asynchronous request
// RealCall.enqueue
override fun enqueue(responseCallback: Callback) {
  // Each call can be requested only once
  check(executed.compareAndSet(false.true)) { "Already Executed" }

  callStart()
  / / add AsyncCall to the Dispatcher. ReadyAsyncCalls
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code

Next, look at the enqueue() method

// Dispatcher.enqueue
internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    // Add to readyAsyncCalls
    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) {val existingCall = findExistingCallWithHost(call.host)
      if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()
}
Copy the code

Next, the promoteAndExecute() method

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

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    val i = readyAsyncCalls.iterator()
    / / traverse readyAsyncCalls
    while (i.hasNext()) {
      val asyncCall = i.next()

      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      if (asyncCall.callsPerHost.get() > =this.maxRequestsPerHost) continue // Host max capacity.
      // Remove yourself
      i.remove()
      asyncCall.callsPerHost.incrementAndGet()
      // Add to executableCalls
      executableCalls.add(asyncCall)
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
  ExecutableCalls start executing
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }

  return isRunning
}
Copy the code

The run method executed

// RealCall#AsyncCall.run
override fun run(a) {
  threadName("OkHttp ${redactedUrl()}") {
    var signalledCallback = false
    timeout.enter()
    try {
      // The method parsed when synchronizing requests
      val response = getResponseWithInterceptorChain()
      signalledCallback = true
      // Call the callback method to get the result of the request
      responseCallback.onResponse(this@RealCall, response)
    } catch (e: IOException) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
      } else {
        responseCallback.onFailure(this@RealCall, e)
      }
    } catch (t: Throwable) {
      cancel()
      if(! signalledCallback) {val canceledException = IOException("canceled due to $t")
        canceledException.addSuppressed(t)
        responseCallback.onFailure(this@RealCall, canceledException)
      }
      throw t
    } finally {
      client.dispatcher.finished(this)}}}Copy the code

It’s easy to look at the flowchart at the beginning

The general process of the request is analyzed, and the detailed analysis remains to be improved.

reference

Detaching wheel series: Detaching OkHttp

AtomicBoolean introduction and use