1 OkHttp简介

目前主流的Android网络请求框架有OkHttpRetrofit,不过,Retrofit底层使用的是OkHttp,其自身是不具备网络请求能力的。

OkHttp是由Square公司开发并共享开源的高效网络访问框架,使用简单,它替代了HttpUrlConnectionApacheHttpClient谷歌官方在Android 6.0 (API 23)里已移除HttpClient,加入了OkHttp

默认情况下OkHttp具备以下特性:

  • 支持HTTP/2.0HTTP/2.0是持久化连接,支持多路复用(客户端和服务端只有一个连接,通过这一个连接可以发起多重请求);
  • 连接池减少请求延时(如果HTTP/2.0不可用);
  • 透明的GZIP压缩下载大小;
  • 缓存响应内容,避免一些完全重复的网络请求;
  • 网络出现问题后,OkHttp能自动中恢复。如果服务器有多个IP地址,一个失败后,OkHttp会自动尝试连接其他的地址;

OkHttp/2.0是基于SPDYSPeeDY)设计的。SPDY是谷歌开发的基于TCP的会话层协议,用于最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%

speedy [ˈspiːdi] 迅速发生的;高速的,快速移动的

下面是Http常见的一些状态码:

  • 100~199:指示信息,表示请求已接收,继续处理;
  • 200~299:请求成功,表示请求已被成功接收、理解;
  • 300~399:重定向,要完成请求必须进行更进一步的操作;
  • 400~499:客户端错误,请求有语法错误或请求无法实现;
  • 500~599:服务器端错误,服务器未能实现合法的请求;

2 OkHttp的使用流程

添加依赖:

dependencies {
    ...
    implementation("com.squareup.okhttp3:okhttp:4.9.3")
}

在使用OkHttp进行请求时,首先要创建一个OkHttpClient的实例:

val client = OkHttpClient()

如果想要发起一条HTTP请求,就需要创建一个Request对象(HttpClientRequest都用了建造者模式):

val request = Request.Builder().url(url).build()

之后调用OkHttpClientnewCall方法来创建一个Call对象,并调用它的execute/enqueue方法来发送请求并获取服务器返回的数据,其中,execute()方法是同步方法,enqueue()方法是异步方法:

val response = client.newCall(request).execute()

response对象就是服务器返回的数据,可以使用如下写法来的到返回的具体内容:

val responseData = response.body?.string()

下面是OkHttp进行GET请求/POST请求的代码:

private val client = OkHttpClient() // 新建OkHttpClient客户端

// GET请求(同步)
fun getMethod(url: String): String {
    val request = Request.Builder().url(url).build() // 新建Request对象
    val response = client.newCall(request).execute() // Response为OkHttp中的响应
    return response.body?.string() ?: ""
}

// POST请求(同步)
val JSON = "application/json; charset=utf-8".toMediaType()
fun postMethod(url: String, json: String): String {
   val body = RequestBody.create(JSON, json)
   val request = Request.Builder().url(url).post(body).build()
   val response = client.newCall(request).execute()
   return response.body?.string() ?: ""
}

// GET请求(异步)
fun run() {
  val request = Request.Builder()
  .url("http://publicobject.com/helloworld.txt")
  .build()

  client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
      e.printStackTrace()
    }

    override fun onResponse(call: Call, response: Response) {
      response.use {
        if (!response.isSuccessful) throw IOException("Unexpected code $response")

        for ((name, value) in response.headers) {
          println("$name: $value")
        }

        println(response.body!!.string())
      }
    }
  })
}

// POST请求(异步)
fun run() {
  val formBody = FormBody.Builder()
  .add("search", "Jurassic Park")
  .build()
  val request = Request.Builder()
  .url("https://en.wikipedia.org/w/index.php")
  .post(formBody)
  .build()

  client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
      e.printStackTrace()
    }

    override fun onResponse(call: Call, response: Response) {
      response.use {
        if (!response.isSuccessful) throw IOException("Unexpected code $response")

        for ((name, value) in response.headers) {
          println("$name: $value")
        }

        println(response.body!!.string())
      }
    }
  })
}

更多使用方式:https://square.github.io/okhttp/recipes/

基本的步骤就是创建 OkHttpClientRequestCall/RealCall,之后调用 Call/RealCall.execute()/enqueue(...) 方法。RealCall 实现了接口 Call

interface Call : Cloneable {
    fun request(): Request
    
    @Throws(IOException::class)
    fun execute(): Response
   
    fun enqueue(responseCallback: Callback)
   
    fun cancel()
    
    fun isExecuted(): Boolean

    fun isCanceled(): Boolean
    
    fun timeout(): Timeout
    
    public override fun clone(): Call

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

class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
 	...   
}

3 OkHttp的请求流程

以下是OkHttp发起请求的大致流程:

OkHttp
OkHttp

3.1 OkHttpClient

在使用OkHttpClient之前,需要先创建一个OkHttpClient客户端,OkHttpClient的构造方法如下:

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

除了直接创建 OkHttpClient 实例之外,还可以使用建造者模式。OkHttpClient.Builder里面的可配置参数如下:

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {  
  constructor() : this(Builder())
  
  class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher() // 分发器
    internal var connectionPool: ConnectionPool = ConnectionPool() // 连接池
    internal val interceptors: MutableList<Interceptor> = mutableListOf() // 拦截器
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf() 
    internal var retryOnConnectionFailure = true // 重试连接失败
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    internal var cache: Cache? = null
    internal var callTimeout = 0
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000

    internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      this.connectionPool = okHttpClient.connectionPool
      this.interceptors += okHttpClient.interceptors
      this.networkInterceptors += okHttpClient.networkInterceptors
      this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
      this.cookieJar = okHttpClient.cookieJar
      this.cache = okHttpClient.cache
      this.callTimeout = okHttpClient.callTimeoutMillis
      this.connectTimeout = okHttpClient.connectTimeoutMillis
      this.readTimeout = okHttpClient.readTimeoutMillis
      this.writeTimeout = okHttpClient.writeTimeoutMillis
    }
    
    fun callTimeout(timeout: Long, unit: TimeUnit) = apply {
      callTimeout = checkDuration("timeout", timeout, unit)
    }
    
    fun addInterceptor(interceptor: Interceptor) = apply {
      interceptors += interceptor
    }
    
    fun build(): OkHttpClient = OkHttpClient(this)
  }
}

获取 OkHttpClientRequest 实例之后,会调用 OkHttpClient.newCall(Reuqest) 方法,得到 Call/RealCall 对象。对于HttpClient.newCall方法,RealCall才是真正的执行者:

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
  
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)  
}
3.2 同步请求

同步请求是指发出网络请求之后当前线程被阻塞,直到请求的结果(成功或者失败)到来,才继续向下执行。同步请求使用的是execute方法,如下所示:

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

    @get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher

    class Builder constructor() {
        internal var dispatcher: Dispatcher = Dispatcher()
    }

    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)  
}

class RealCall( // RealCall为真正的请求执行者
    val client: OkHttpClient,
    val originalRequest: Request,
    val forWebSocket: Boolean
) : Call {
    internal val eventListener: EventListener = client.eventListenerFactory.create(this)

    val call: RealCall
    get() = this@RealCall

    override fun execute(): Response {
        check(executed.compareAndSet(false, true)) { "Already Executed" } // 每个call只能执行一次

        timeout.enter()
        callStart()
        try {
            client.dispatcher.executed(this) // 通过dispatcher已经进入执行状态
            return getResponseWithInterceptorChain() // 通过一系列的拦截器请求处理和响应处理得到最终的返回结果
        } finally {
            client.dispatcher.finished(this) // 通知dispatcher,已经执行完毕
        }
    }

}

class Dispatcher constructor() {
    /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
    private val runningSyncCalls = ArrayDeque<RealCall>() // 运行中的同步请求队列

    @Synchronized internal fun executed(call: RealCall) {
        runningSyncCalls.add(call)
    }

}

async [əˈsɪŋk] 异步(asynchronous) sync [sɪŋk] 同步

同步请求是调度器dispatcher执行—client.dispatcher.executed(call: RealCall)来完成。

3.3 Dispatcher调度器

dispatcher [dɪˈspætʃər] [计] 调度程序;[计] 分配器

分发器主要是用来维护请求队列与线程池,完成请求调配。在创建HttpClient的时候,我们也可以传递自定义的线程池来创建分发器, 以下是Dispatcher的相关源码:

class Dispatcher constructor() {
  // 并发执行的最大请求数,超过了这个数量之后,请求在内存中排队,等待正在运行的调用完成
  @get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

  // 同一域名同时执行的最大请求数
  @get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      promoteAndExecute()
    }

  // 闲置任务
  @set:Synchronized
  @get:Synchronized
  var idleCallback: Runnable? = null
    
  // 异步请求等待执行队列
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
  // 异步请求正在执行队列
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  // 同步请求正在执行队列
  private val runningSyncCalls = ArrayDeque<RealCall>()

  // 异步请求使用的线程池
  private var executorServiceOrNull: ExecutorService? = null
  
  // 创建线程池(懒加载)
  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

  // 构造器,自定义线程池
  constructor(executorService: ExecutorService) : this() {
    this.executorServiceOrNull = executorService
  }
}

Dispatcher 有两个构造方法,可以使用设定的线程池。如果没有设定线程池,则会使用默认的线程池,这个线程池比较适合执行大量的消耗比较少的任务。Dispatcher中的默认线程池配置保证了新加入的任务可以被立即执行,避免阻塞。

同步请求不需要线程池,也不需要做任何限制,所以分发器只是做一下记录,后续按照加入队列的顺序同步请求即可。

3.4 异步请求

异步请求是指网络请求发出之后,不必等待请求结果的到来,就可以去做其他的事情,当请求结果到来时,在做处理结果的动作。异步请求使用的是enqueue方法,如下所示:

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

  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)  
}

class RealCall( // RealCall为真正的请求执行者
  val client: OkHttpClient,
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
  
  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
  
}

class Dispatcher constructor() {
  private val readyAsyncCalls = ArrayDeque<AsyncCall>() // 正在准备中的异步请求队列
  private val runningAsyncCalls = ArrayDeque<AsyncCall>() // 运行中的异步请求
  
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }
  
  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      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.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService) // 线程池执行任务
    }

    return isRunning
  }
  
  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
  }
}

异步请求是调度器dispatcher执行—client.dispatcher.enqueue(call: AsyncCall),并通过回调(Callback)获取服务器返回的结果。

Dispatchercall加入到队列中,然后通过线程池来执行callDispatcher是一个任务调度器,它内部维护了三个双端队列(新来请求放在队尾,执行请求从对头部取):

  • readyAsyncCalls:准备运行的异步请求;
  • runningAsyncCalls:正在运行的异步请求;
  • runningSyncCalls:正在运行的同步请求;

runningAsyncCalls 的数量小于 64 并且正在运行的请求主机小于 5 时,把请求添加到 runningAsyncCalls 中并在线程池中执行,否则就加入到 readyAsyncCalls 中进行等待执行。

双队列机制

4 OkHttp的拦截器

OkHttp拦截器就是基于责任链模式来实现的, 在请求到达时,拦截器会做一些处理(比如添加参数),然后传递给下一个拦截器处理:

OkHttp的请求流程

拦截器

OkHttp提供了一系列的拦截器来处理相应的业务。也可以通过自定义拦截器,来实现自己的拦截业务。下面是一些常用的拦截器:

  • retryAndFollowUpInterceptor:失败和重定向拦截器。 当请求内部抛出异常时,判定是否需要重试,当响应结果是3xx重定向时,构建新的请求并发送请求;
  • BridgeInterceptor:应用层和网络层的桥接拦截器。 主要工作是请求添加cookie,添加固定的header,比如HostContent-LengthContent-TypeUser-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要解压;
  • CacheInterceptor:缓存拦截器。如果命中缓存,则不发起网络请求;
  • ConnectInterceptor:连接拦截器,内部会维持一个连接池,主要负责TCP连接,包括连接复用、创建连接(三次握手)、释放连接以及创建连接上的socket流;
  • networkInterceptor:网络拦截器,通常用于监控网络层的数据传输;
  • CallServerInterceptor:请求拦截器,在前置准备工作完成后,真正发起网络请求;

OkHttp空闲连接如何清除?

  1. 在将连接加入连接池的时候就会启动定时任务;
  2. 有空闲连接的话,如果最长的空闲时间大于5分钟或空闲数大于5,就关闭移除这个最长空闲连接;如果空闲数不大于5且最长的空闲时间不大于5分钟,就在时间到5分钟的时候清理;
  3. 没有空闲连接,就等5分钟后再尝试清理;
  4. 没有连接不清理;

5 总结

OkHttp是一个网络请求框架,它是支持HTTP/2.0的,因此HTTP/2.0的一些优势也就体现在了这个框架上。比如说,HTTP/2.0是基于SPDY协议的,这个协议是用来减少网络延迟,提高网络速度的,再比如持久化连接、多路复用,客户端和服务端只有一个连接,可以通过这一个连接向服务端发起多重请求。如果HTTP/2.0不可以用,还可以使用连接池来减少延迟,连接池是HTTP/1.1提出的概念,连接池可以理解成是一个容器,我们创建的一些连接,在请求结束后,并不会断开,而是直接放到连接池中,下次直接拿来用就可以了。除了这些还有,比如缓存,如果命中缓存,可以避免重复的请求,如果网络出错,还可以自动恢复等等。

以下是OkHttp的请求流程:

  1. 通过建造者模式来创建OkHttpClientRequest对象(建造者模式就是用来创建灵活性和可扩展性强的复杂对象);
  2. 通过调用OkHttpClientnewCall方法来获取一个RealCall对象,通过RealCall来实现同步请求或者异步请求。在RealCallexecute方法和enqueue方法中,是使用调度器Dispatcher来完成的;
  3. Dispatcher中维护了3个双端队列——准备运行的异步请求队列(readyAsyncCalls)、正在运行的异步请求队列(runningAsyncCalls)和正在运行的同步队列(runningSyncCalls)。如果是同步请求,则直接添加到同步请求队列中,等待顺序调用即可。如果是异步请求,如果正在运行的异步队列中的任务数未超过64,且同一域名的请求没有超过5个,则加入到正在运行的异步请求队列中,同时添加到线程池,否则加入到准备运行的请求队列中;
  4. OkHttp的拦截器使用的是责任链模式(为同一请求的接收者创建一个链,请求沿着这条链传递,如果某个接收者需要处理这个请求,就交给它处理,处理完成后再继续向下传递),方法名为getResponseWithInterceptorChain()。在OkHttp中有六个拦截器,第一个是失败和重定向拦截器,如果内部请求出现异常,判断是否需要重试;第二个拦截器是应用层和网络层桥接拦截器,主要负责Cookie的处理和添加固定的请求头;第三个拦截器是缓存拦截器,如果命中缓存,则不再进行网络请求;第四个拦截器是连接拦截器,其内部维护着一个的连接池,负责TCP的连接,包括连接复用、创建连接、释放连接以及创建连接上的Socket流;第五个拦截器是网络拦截器,主要用来监听网络上的数据传输;第六个拦截器是请求拦截器,当前置工作全部完成后,发起网络请求。

参考

https://blog.csdn.net/OneDeveloper/article/details/88381817
https://blog.csdn.net/u012949047/article/details/52296137
https://square.github.io/okhttp/
https://blog.csdn.net/qq_29882585/article/details/111870887
https://www.jianshu.com/p/01a25bd98b1f
https://baijiahao.baidu.com/s?id=1716997541121502638&wfr=spider&for=pc
征服面试官:OkHttp原理篇掌握这篇面试题汇总,吊打面试官!
面试突击:OkHttp 原理八连问

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐