一篇文章搞定《网络框架OkHttp源码解析》

前言

对于OkHttp我们需要了解一下相关网络的知识哦,所以还不了解的同学们建议先去补充一下网络相关的HTTP、TCP、UDP等知识。(要不我们来更新一篇网络的知识?)
作为我们的主流框架OkHttp的解析,我们准备分以下几个部分来讲述:(基于4.11版本)

  • 构建与初始化
  • 请求的发起过程
  • 核心部分:责任链拦截器
  • 整两个小问题聊一聊

构建与初始化

首先我们经常用的两种请求方式:
同步请求:

//1、构建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、构建一个Request对象
Request request = new Request.Builder()
        .url(url)
        .build();
//3、同步请求
Response response = client.newCall(request).execute();

异步请求:

//1、构建OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//2、构建一个Request对象
Request request = new Request.Builder()
        .url(url)
        .build();
//3、异步请求
client.newCall(request).enqueue(new Callback() {
    
    
      @Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {
    
    
           //请求的失败回调
      }

      @Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
    
    
          //成功的Response处理
      }
    });      

其实可以看到,我们不管是同步请求,还是异步的请求。
都需要:

  • 创建OKHttpClient客户端。
  • 创建一个Request对象。
  • 通过newCall创建的Call对象处理请求。

那么,构建与初始化,也就是说的是上面这三步了,那就逐个来看看吧。

构建OKHttpClient对象

//1、创建OKHttpClient客户端
OkHttpClient client = new OkHttpClient();

点进去看看(无用代码省略,4.11版本是kotlin哦)

open class OkHttpClient internal constructor(
  builder: Builder
) 

constructor() : this(Builder())

class Builder constructor() {
    
    
  internal var dispatcher: Dispatcher = Dispatcher()
  internal var connectionPool: ConnectionPool = ConnectionPool()
  .......
  .......

靠!!靠!!有是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 eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
  //连接失败时是否重连
  internal var retryOnConnectionFailure = true
  //是否重定向
  internal var followRedirects = true
  //缓存设置
  internal var cache: Cache? = null
  //代理设置
  internal var proxy: Proxy? = null
  //请求超时
  internal var callTimeout = 0
  //连接超时
  internal var connectTimeout = 10_000
  //读取超时
  internal var readTimeout = 10_000
  //写入超时
  internal var writeTimeout = 10_000

当然,既然构建者中创建了这些元素,当然也就意味着我们可以对他们进行自定义的构建。
例如:来设置我们的连接超时时间和请求超时时间。

OkHttpClient okHttpClient = new OkHttpClient()
        .newBuilder()
        .connectTimeout(Duration.ofDays(60000))
        .callTimeout(Duration.ofDays(60000))
        .build();

构建一个Request对象

//2、构建一个Request对象
Request request = new Request.Builder()
        .url(url)
        .build();

其实我们从,调用就可以看的出来,他就是构建了一个请求嘛
来点进去看看

class Request internal constructor(
    @get:JvmName("url") val url: HttpUrl,    //url
    @get:JvmName("method") val method: String,     //请求方法,GET/POST
    @get:JvmName("headers") val headers: Headers,    //请求头
    @get:JvmName("body") val body: 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
         ....
}

真就没什么,只是利用构建者模式,初始化了一些网络请求的重要信息。

构建Call对象

Call call = okHttpClient.newCall(request);

首先点进去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
    }
}

其实就是一个面向接口编程的接口类而已。具体的接口功能也在注释中解释了。
其次就是我们通过OkHttpClient对象进行的newCall了。
点进去看看:

override fun newCall(request: Request): Call = 
        RealCall(this, request, forWebSocket = false)
        
//RealCall.kt
class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
    
    

可以看到在OkHttpClient中我们利用前面构建的OkHttpClient对象和Request对象创建了一个实现了Call接口的RealCall对象。
而从Call接口的功能中我们也看得出来,RealCall是我们将请求发出的重要的掌控者。
那么就继续:请求的发起。

请求的发起过程

发起请求主要为以下两种

  • 同步请求
  • 异步请求

同步请求

//3、同步请求
Response response = client.newCall(request).execute();

上面已经对OkHttpClient、Request、Call进行了解析,那么我们直接看execute()做了什么呢?
RealCall.kt

override fun execute(): Response {
    
    
  // 这里用CAS思想,进行锁的保护,使一个call对象只能执行一次execute方法 
  check(executed.compareAndSet(false, true)) {
    
     "Already Executed" }

  timeout.enter()
  // 这里主要是个监听器,表示开始进行网络请求了
  callStart()
  // 重点关注这块
  try {
    
    
    // 利用调度器将当前请求加入到一个同步队列当中
    client.dispatcher.executed(this)
    // 通过 getResponseWithInterceptorChain() 获得相应结果
    return getResponseWithInterceptorChain()
  } finally {
    
    
    // 完成一些收尾工作,在同步请求中,几乎没什么用
    client.dispatcher.finished(this)
  }
}

异步请求

//3、异步请求
client.newCall(request).enqueue(new Callback() {
    
    
      @Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {
    
    
           //请求的失败回调
      }

      @Overridepublic void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
    
    
          //成功的Response处理
      }
    }); 

同样的我们来看一下enqueue中做了什么,因为是异步的所以需要有回调对象的传入。
RealCall.kt

override fun enqueue(responseCallback: Callback) {
    
    
  // 这里用CAS思想,进行锁的保护,使一个call对象只能执行一次execute方法 
  check(executed.compareAndSet(false, true)) {
    
     "Already Executed" }

  // 这里面依旧加上了监听
  callStart()
  
  // 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

可以看到都使用了dispatcher去进行了我们的请求的分发。也就是其实重点在这个dispatcher调度器上。那我们来看看分发器具体做了哪些内容呢?

调度器Dispatcher

作为发送请求的第一把手:Dispatcher做了些什么呢?
Dispatcher对于整个发送请求十分重要(特别是对异步请求,因为异步请求的并发还是高的)。
来将Dispatcher切割来看(只看关键的地方)首先是管理的一些重要成员:

  • 同步请求的队列
//同步运行时调用、同步请求的管理队列
private val runningSyncCalls = ArrayDeque<RealCall>()
  • 异步请求限制条件
//异步请求的最大数量
var maxRequests = 64
//每个主机同时请求的最大数量
var maxRequestsPerHost = 5
  • 异步请求管理对列
//异步请求等待队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
//异步请求执行队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  • 异步请求线程池
//异步请求线程池
private var executorServiceOrNull: ExecutorService? = null
val executorService: ExecutorService

上面我们对同步请求、异步请求只说到了Dispatcher的调度。
那接着来看看Dispatcher中是如何操作的

executed

// 利用调度器将当前请求加入到一个同步队列当中
client.dispatcher.executed(this)
// 通过 getResponseWithInterceptorChain() 获得相应结果
return getResponseWithInterceptorChain()

点进去看看
Dispatcher.kt

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

可以看到,同步请求executed中也没有做什么,只是将请求通过Dispatch加入到了Dispatch维护的同步请求队列中。
正在的请求发生在后面的return getResponseWithInterceptorChain()。也就是拦截器去处理这个同步请求队列。并返回相应的结果。

enqueue

异步请求相对于同步请求还是比较复杂的。其中Dispatch也发挥着大作用。

// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher.enqueue(AsyncCall(responseCallback))

这里先不看AsyncCall。先看一下Dispatcher中对enqueue做了什么
Dispatcher.kt

internal fun enqueue(call: AsyncCall) {
    
    
  //线程同步
  synchronized(this) {
    
    
    //加到异步的网络请求准备队列中
    readyAsyncCalls.add(call)

    //复用之前的Call
    if (!call.call.forWebSocket) {
    
    
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  //dispatcher进行分发call任务的主要方法
  promoteAndExecute()
}

先看下为什么要复用之前的Call呢?是怎么复用的呢?

  • 复用操作的核心是调用findExistingCallWithHost(call.host)方法来查找是否已经存在与当前AsyncCall相同的host的Call。
  • 如果存在,就调用call.reuseCallsPerHostFrom(existingCall)方法来共享这两个Call之间的用于控制并发请求的AtomicInteger。
  • 避免并发请求导致对同一host的网络连接过多的问题,通过共享控制并发请求的AtomicInteger,将两个请求视为一组,从而限制并发的连接数量。这种做法可以提高性能和减少网络连接的开销。

牛逼!!!!! 真是能省就省啊。
下面说到了Dispatcher的核心方法,也是异步请求超级重要的方法:promoteAndExecute()
Dispatcher.kt

//不仅在开始发起请求时被调用,在任务多的时候,其他任务结束时,也会被调用起来执行。
//纯纯苦力
private fun promoteAndExecute(): Boolean {
    
    
  this.assertThreadDoesntHoldLock()
  //需要开始执行的任务集合
  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    
    
    val i = readyAsyncCalls.iterator()
    //核心!!! 按行来看
    //迭代等待执行异步请求 
  while (i.hasNext()) {
    
    
      // 1、下一个
      val asyncCall = i.next()
      // 2、正在执行异步请求的总任务数不能大于64个 
      //否则直接退出这个循环,不再将请求加到异步请求队列中 
      //那么就还安静的在准备队列中了
      if (runningAsyncCalls.size >= this.maxRequests) break 
      // 同一个host的请求数不能大于5
      // 否则直接跳过此call对象的添加,去遍历下一个asyncCall对象     
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue

      i.remove()
      // 3、如果拿到了符合条件的asyncCall对象,就将其callPerHost值加1(用于上面的Host判断)
      // callPerHost代表了连接同一个host的数量
      asyncCall.callsPerHost.incrementAndGet()
      // 加到需要开始执行的任务集合中
      executableCalls.add(asyncCall)
      // 将当前call加到正在执行的异步队列当中
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }

  for (i in 0 until executableCalls.size) {
    
    
    // 4、遍历每一个集合中的asyncCall对象
    // 将其添加到线程池中,执行它的run方法
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }

  return isRunning
}

总结核心

  • 迭代异步准备队列
  • 满足条件加入异步运行队列:异步请求总任务数小于64并且同一个host的请求数不能大于5
  • 将当前Call请求加入到任务集合中
  • 遍历任务集合加入请求线程池,并执行他

(执行的话,那会执行什么呢?)
答:呸!! asyncCall的run方法呗,那还用问?

AsyncCall

好嘞,那让我们来看看我们在异步请求执行时添加的AsyncCall中的Run中有什么呢?

// 构建一个 AsyncCall对象,再交给dispatcher进行分发流程
client.dispatcher.enqueue(AsyncCall(responseCallback))

AsyncCall 是 RealCall 的内部类, 它实现了 Runnable 接口,主要是为了能在线程池中去执行它的 run() 方法。
RealCall.kt

//内部类 AsyncCall
internal inner class AsyncCall(
  private val responseCallback: Callback
) : Runnable {
    
    
  .....
  .....
  // 1、将AsyncCall任务添加到线程池
  fun executeOn(executorService: ExecutorService) {
    
    
    client.dispatcher.assertThreadDoesntHoldLock()
    
    var success = false
    try {
    
    
      // 2、执行线程池。那就到run函数了
      executorService.execute(this)
      success = true
    } catch (e: RejectedExecutionException) {
    
    
      val ioException = InterruptedIOException("executor rejected")
      ioException.initCause(e)
      noMoreExchanges(ioException)
      // 2.1、请求失败的回调
      responseCallback.onFailure(this@RealCall, ioException)
    } finally {
    
    
      if (!success) {
    
    
        //收尾工作,finished内部还是调用的promoteAndExecute方法。继续下一个任务
        client.dispatcher.finished(this) 
      }
    }
  }

  override fun run() {
    
    
    threadName("OkHttp ${redactedUrl()}") {
    
    
      var signalledCallback = false
      timeout.enter()
      try {
    
    
        // 3、和同步方法一样,通过链式的拦截器,去处理请求返回结果
        val response = getResponseWithInterceptorChain()
        signalledCallback = true
        // 3.1、请求成功的回调
        responseCallback.onResponse(this@RealCall, response)
      } catch (e: IOException) {
    
    
        if (signalledCallback) {
    
    
          
          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)
          // 3.2、请求失败的回调
          responseCallback.onFailure(this@RealCall, canceledException)
        }
        throw t
      } finally {
    
    
        // 4、收尾工作,finished内部还是调用的promoteAndExecute方法。继续下一个任务
        client.dispatcher.finished(this)
      }
    }
  }
}

好了以上就是异步请求的请求流程了,可以看到和同步请求一样,最后还是调用了getResponseWithInterceptorChain去通过链式的拦截器去处理了我们的请求,所以拦截器才是OkHttp的重中之重!!

executorService

在说getResponseWithInterceptorChain之前,上面不止一次提及到了这个异步处理的请求池,大家肯定知道,不就是自定义的线程池吗!!!
是啊,你猜对了,但是看看他自定义的几个参数还是挺关键的。来看看:

@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!!
  }
  • 参数一:corePoolSize:核心线程数:表示线程池中始终保持的活动线程数量,即使它们处于空闲状态也不会被销毁,默认情况下线程池是空闲的。
    • corePoolSize的值为0,表示线程池不保持任何活动线程。
  • 参数二:maximumPoolSize:最大线程数:表示线程池中允许的最大线程数量。当队列满了并且活动线程数小于maximumPoolSize时,线程池会创建新的线程来处理任务。
    • maximumPoolSize的值为Int.MAX_VALUE,即最大整数值,表示线程池可以创建任意数量的线程。
  • 参数三:keepAliveTime:线程空闲时间:表示非核心线程的空闲时间超过该值时,线程会被销毁,直到线程池中的线程数等于corePoolSize。
    • keepAliveTime的值为60,表示非核心线程的空闲时间为60秒。(你可以认为缓存是60秒)
  • 参数四: unit:时间单位:表示keepAliveTime的单位。
    • TimeUnit.SECONDS这里使用的是秒。
  • 参数五:workQueue:任务队列:表示存储待执行任务的阻塞队列。
    • SynchronousQueue,它是一个没有容量的阻塞队列,主要用于同步线程之间的数据交换。
  • 参数六:threadFactory:线程工厂:用于创建新线程的工厂类。
    • 自定义的threadFactory方法创建线程,并命名为"$okHttpName Dispatcher"。

总结:以上就是同步请求和异步请求发起的全部内容了。最终也是都通过链式的拦截器去发送了出去。那么就继续吧,看看拦截器都做了什么。

核心部分:责任链拦截器

顾名思义,拦截器,就是起到了一层层拦截的作用。也就是合格的过、不合格的留下。
这里拦截器也是用到了一种设计模式去设计,也就是责任链模式,他将多个拦截器进行链式的调用。
他将请求一层层分发下去,最后再将结果再一层层返回上来。

入口:getResponseWithInterceptorChain

通过上面的解析我们知道不管是同步还是异步最后都通过getResponseWithInterceptorChain()来获取结果。
那么我们第一步先来看看getResponseWithInterceptorChain作为拦截器的入口都有什么吧!
RealCall.kt

@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
    
    
  // 保存所有拦截器的集合
  val interceptors = mutableListOf<Interceptor>()
  // 第一个可以说是自定义拦截器吧,做些日志之类的
  interceptors += client.interceptors
  // 重试重定向拦截器
  interceptors += RetryAndFollowUpInterceptor(client)
  // 桥接拦截器,补全网络请求
  interceptors += BridgeInterceptor(client.cookieJar)
  // 缓存拦截器,缓存结果的
  interceptors += CacheInterceptor(client.cache)
  // 链接拦截器,建立Socket连接,并缓存链接复用
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    
    
    // 用户定义的网络拦截器,可以用来获取一些请求信息
    interceptors += client.networkInterceptors
  }
  // 请求服务拦截器,与服务器进行请求,返回结果的
  interceptors += CallServerInterceptor(forWebSocket)

  // 1、责任链对象
  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 {
    
    
    // 2、调用RealInterceptorChain的proceed()方法
    // 将请求向下一个连接器传递
    val response = chain.proceed(originalRequest)
    if (isCanceled()) {
    
    
      response.closeQuietly()
      throw IOException("Canceled")
    }
    // 3、责任链的拦截器全部通过,并返回结果
    return response
  } catch (e: IOException) {
    
    
    calledNoMoreExchanges = true
    throw noMoreExchanges(e) as Throwable
  } finally {
    
    
    if (!calledNoMoreExchanges) {
    
    
      noMoreExchanges(null)
    }
  }
}

总结以下几点内容:

  • OkHttp默认是五个拦截器
  • 可以进行自定义拦截器
  • 通过RealInterceptorChain进行责任链的层次调用

重试重定向拦截器:RetryAndFollowUpInterceptor

主要内容:负责重试和请求重定向的一个拦截器
次要内容:创建了一个ExchangeFinder给ConnectInterceptor使用

class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
    
    

先来看一下Interceptor接口:可以看到就一个方法intercept并且提供给我们的五大拦截器去实现。
因此,五大拦截器的主要内容、核心内容也都在intercept中被实现。后面也是主讲intercept方法中的内容。
在这里插入图片描述
RetryAndFollowUpInterceptor.kt

class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
    
    

  companion object {
    
    
    //最大重定向次数
    private const val MAX_FOLLOW_UPS = 20
  }

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    
    
    ....
    ....
    while (true) {
    
    
      // 1、创建了ExchangeFinder对象,给后续的链接拦截器使用
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)
      // 请求结果
      var response: Response
      var closeActiveExchange = true
      try {
    
    
        .....
        try {
    
    
          // 2.1、正常情况:下调用下一个拦截器,即 BridgeInterceptor
          // 2.2、请求错误的情况:捕获然后重试重定向
          response = realChain.proceed(request)
          newExchangeFinder = true
        } catch (e: RouteException) {
    
    
          // 2.3、路线异常:检查是否重试 并进行重试
          if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
    
    
            throw e.firstConnectException.withSuppressed(recoveredFailures)
          } else {
    
    
            recoveredFailures += e.firstConnectException
          }
          newExchangeFinder = false
          continue
        } catch (e: IOException) {
    
    
          // 2.4、IO异常: 检查是否重试 并进行重试
          if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
    
    
            throw e.withSuppressed(recoveredFailures)
          } else {
    
    
            recoveredFailures += e
          }
          newExchangeFinder = false
          continue
        }

        // 3、请求的结果(没被try catch 拦截说明当前请求成功了,但是可能会被重定向)
        if (priorResponse != null) {
    
    
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
              .build()
        }
        // 4、 根据返回的response来判断需不需要重定向
        val exchange = call.interceptorScopedExchange
        val followUp = followUpRequest(response, exchange)
        // 5、followUp为空,代表没有重定向,直接返回结果response
        if (followUp == null) {
    
    
          if (exchange != null && exchange.isDuplex) {
    
    
            call.timeoutEarlyExit()
          }
          closeActiveExchange = false
          return response
        }
        // 6、如果RequestBody有值且只许被调用一次,
        // 也就是不许重定向,那么就直接返回response,别墨迹!!
        val followUpBody = followUp.body
        if (followUpBody != null && followUpBody.isOneShot()) {
    
    
          closeActiveExchange = false
          return response
        }

        response.body?.closeQuietly()
        //7、超过重定向最大次数抛出异常(20次)
        if (++followUpCount > MAX_FOLLOW_UPS) {
    
    
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }
        
        //8、将新的请求赋值给request,继续循环,也就是重试了
        request = followUp
        priorResponse = response
      } finally {
    
    
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

网络桥接拦截器:BridgeInterceptor

主要内容:用来补全请求头的内容的,比如:(只列举部分,详细的大家自己去看一下Http相关知识)
Content-Type:用于指定请求体的类型
Content-Length:用于指定响应体的长度,表示我这次请求需要返回多少字节的内容,多用于分块传输;
Host:用于指定请求的目标主机,比如www.baidu.com。
User-Agent:用于向服务端表明身份信息,表示我是来自手机客户端的请求,还是来自某个浏览器的请求;
Cookie:用于在请求中携带之前存储的cookie数据,以识别客户端并提供个性化服务。
Content-Encoding:服务器告诉客户端自己使用了什么压缩方法,如gzip,deflate等;

如果响应头中有Content-Encoding: gzip,则会用 GzipSource 进行解析。

接下来往下看看,网络拦截器中内容

class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
    
    

BridgeInterceptor.kt

class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
    
    

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    
    
      //1、创建请求对象,为了后面的内容构建
      val userRequest = chain.request()
      val requestBuilder = userRequest.newBuilder()
      //2、补全请求头(if判断就省略了)
      requestBuilder.header("Content-Type", contentType.toString())
      requestBuilder.header("Content-Length", contentLength.toString())
      requestBuilder.header("Host", userRequest.url.toHostHeader())
      requestBuilder.header("Connection", "Keep-Alive")
      requestBuilder.header("Accept-Encoding", "gzip")
      requestBuilder.header("Cookie", cookieHeader(cookies))
  
      //3、交给下一个拦截器继续操作
      val networkResponse = chain.proceed(requestBuilder.build())
      cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
      
      //4、如果Content-Encoding: gzip。就西药gzip解析。
      if (transparentGzip &&
            "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
            networkResponse.promisesBody()) {
    
    
            val responseBody = networkResponse.body
            .....
            val gzipSource = GzipSource(responseBody.source())
            ....
            responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
  }
}

总结:桥接拦截器就是这么简单了,就是在请求之前,补充好请求头而已。之后根据Content-Encoding来确定是否需要gzip解压。

缓存拦截器:CacheInterceptor

主要内容:众所周知OkHttp是有一套缓存机制的,CacheInterceptor就是负责读取缓存以及更新缓存的。内部是用OKio来实现读写的,所以大家在导入OkHttp时,会发现他会自动的导入OKio。
HTTP的缓存规则

  • 强缓存:强缓存是在客户端(浏览器)中进行的缓存策略,服务端通过设置HTTP响应头中的Cache-Control和Expires字段来控制。
    • Cache-Control字段:可以设置多个指令,如max-age、no-cache、no-store等。其中max-age代表缓存的最长有效时间(秒),当请求再次发生时,如果缓存未过期,则直接从缓存中获取数据。
    • 举例:如果服务器设置了Cache-Control: max-age=3600,意味着资源在一小时内都是有效的。客户端再次请求相同资源时,可以直接从缓存中读取而不需要再次向服务器发起请求。
  • 协商缓存:协商缓存是在服务器端进行的缓存策略,服务端通过设置HTTP响应头中的ETag和Last-Modified字段来控制。
    • 举例:当客户端再次请求某个资源时,携带上次服务器返回的ETag和Last-Modified字段值,如果服务器判断资源未发生变化,则返回304状态码,告知客户端可以使用缓存的资源。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
    
    

CacheInterceptor.kt

class CacheInterceptor(internal val cache: Cache?) : Interceptor {
    
    

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    
    
    ......
    // 1、获取缓存策略;
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    // 1.1、网络请求对象(说明要网络请求服务器)
    val networkRequest = strategy.networkRequest
    // 1.2、缓存请求对象(缓存获取,有本地还有code = 304网路中的)
    val cacheResponse = strategy.cacheResponse
    .........
    .........
    // 2、如果networkRequest 和 cacheResponse 都是null 
    // 说明不想网络请求,又没有本地缓存。直接进行错误返回504
    if (networkRequest == null && cacheResponse == null) {
    
    
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
    
    
            listener.satisfactionFailure(call, it)
          }
    }

    // 3、不请求网络、直接返回本地缓存
    if (networkRequest == null) {
    
    
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
    
    
            listener.cacheHit(call, it)
          }
    }

    if (cacheResponse != null) {
    
    
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
    
    
      listener.cacheMiss(call)
    }

    var networkResponse: Response? = null
    try {
    
    
      // 4、说明需要继续进行请求,来确定是协商缓存还是重新去服务器上获取资源
      // 也就是继续调用下一个拦截器,链接拦截器
      networkResponse = chain.proceed(networkRequest)
    } finally {
    
    
      if (networkResponse == null && cacheCandidate != null) {
    
    
        cacheCandidate.body?.closeQuietly()
      }
    }

    // 5、协商缓存
    if (cacheResponse != null) {
    
    
      // 5.1、并且服务器放回给我们304响应码,直接使用协商缓存中的缓存
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
    
    
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()

        networkResponse.body!!.close()

        cache!!.trackConditionalCacheHit()
        cache.update(cacheResponse, response)
        return response.also {
    
    
          listener.cacheHit(call, it)
        }
      } else {
    
    
        cacheResponse.body?.closeQuietly()
      }
    }

    // 6、到这里,说明缓存行不通了,是新的请求的资源
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    // 7、将本次最新得到的响应存到cache中去
    if (cache != null) {
    
    
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
    
    
        val cacheRequest = cache.put(response)
        // 8、用OKio来进行缓存的写入
        return cacheWritingResponse(cacheRequest, response).also {
    
    
          if (cacheResponse != null) {
    
    
            listener.cacheMiss(call)
          }
        }
      }
    // 8、让返回结果返回上一层拦截器
    return response
  }
  

总结:那就总结一下流程吧:

  • 1、获取缓存策略
  • 2、根据策略,不使用网络,又没有缓存的直接报错,并返回错误码504。
  • 3、根据策略,不使用网络,有缓存的直接返回。
  • 4、前面两个都没有返回,继续执行下一个Interceptor,即ConnectInterceptor。
  • 5、接收到网络结果,如果响应code式304,则使用协商缓存,返回缓存结果。
  • 6、读取新请求的网络结果
  • 7、对新请求的数据内容进行缓存
  • 8、返回数据结果给上层拦截器

链接拦截器:ConnectInterceptor

主要内容:

  • 负责建立连接工作:封装了socket连接和TLS握手等逻辑
  • 连接复用的工作:它会从连接池中取出符合条件的连接,以免重复创建,从而提升请求效率。
object ConnectInterceptor : Interceptor {
    
    
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    
    
    val realChain = chain as RealInterceptorChain
    // 1、建立连接工作
    val exchange = realChain.call.initExchange(chain)
    // 2、copyexchange对象,到下一拦截器
    val connectedChain = realChain.copy(exchange = exchange)
    //3、调用下一拦截器
    return connectedChain.proceed(realChain.request)
  }
}

完事了!!!
呸!想得美,主要内容都在initExchange(chain)建立连接中。

internal fun initExchange(chain: RealInterceptorChain): Exchange {
    
    
  synchronized(this) {
    
    
    check(expectMoreExchanges) {
    
     "released" }
    check(!responseBodyOpen)
    check(!requestBodyOpen)
  }
  // 1、这是前面重试重定向拦截器,创建的exchangeFinder(一个连接复用的缓存对象)
  val exchangeFinder = this.exchangeFinder!!
  // 2、查找连接,返回的是ExchangeCodec对象
  val codec = exchangeFinder.find(client, chain)
  // 3、创建Exchange用来发送和接受请求
  val result = Exchange(this, eventListener, exchangeFinder, codec)
  .....
  .....
  return result
}

我们从exchangeFinder.find点进去,一路点到findConnection是他连接的主方法。

@Throws(IOException::class)
        private fun findConnection(
                connectTimeout: Int,
                readTimeout: Int,
                writeTimeout: Int,
                pingIntervalMillis: Int,
                connectionRetryEnabled: Boolean
):RealConnection {
    
    
            if (call.isCanceled()) throw IOException("Canceled")

            // 1、尝试重用连接
            val callConnection = call.connection 
            if (callConnection != null) {
    
    
              ...
            }
            // 2、尝试从池中获取连接
            if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
    
    
            ...
            }
            ...
            // 3、创建一个新的连接(也就是上面没有获取到合适的链接去复用)
            val newConnection = RealConnection(connectionPool, route)
            call.connectionToCancel = newConnection
            try {
    
    
                //4、连接服务器 (创建Socket对象、TCP握手和TSL握手)
                newConnection.connect(
                        connectTimeout,
                        readTimeout,
                        writeTimeout,
                        pingIntervalMillis,
                        connectionRetryEnabled,
                        call,
                        eventListener
                )
            } finally {
    
    
                call.connectionToCancel = null
            }
            ...
            synchronized(newConnection) {
    
    
                //5、添加到连接池中  
                connectionPool.put(newConnection)
                call.acquireConnectionNoEvents(newConnection)
            }
            return newConnection
        }

先总结一下流程:

  • 1、尝试重用连接
  • 2、尝试从池中获取连接
  • 3、创建一个新的连接:(也就是上面没有获取到合适的链接去复用)
  • 4、连接服务器:通过创建Socket对象、TCP握手和TSL握手也、使用OKio中的接口获得输入/输出流。
  • 5、添加到连接池中

这里面有两个比较重要的内容,那就是连接的具体内容、还有连接的线程池了
连接池的内容过多给大家总结一下:
连接池:

  • 创建添加:连接池默认的空闲连接数是5个,空闲的连接时间也是5分钟,如果空闲连接数超出了5个,那么就清理掉最近最久未使用的连接数,直到连接池中的连接数小于等于5个。(是不是想起了LRU缓存)
  • 寻找复用:每次去复用连接池,需要去遍历比较当前连接的 Host, url, ip 等是否完全一样,满足条件就取出这个连接。
  • 清除清理:另外对连接进行缓存时,如果当前的连接数超过了5个,那么会将最近最久未使用的连接进行清除。cleanup() 方法会每过一个清理周期自动做一次清理工作。

那也简单的来看一下连接吧:
RealConnection.connectSocket(连接)

@Throws(IOException::class)
        private fun connectSocket(
                connectTimeout: Int,
                readTimeout: Int,
                call: Call,
                eventListener: EventListener
) {
    
    
            ...
            //1、创建Socket对象
            val rawSocket = when (proxy.type()) {
    
    
                Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
    else -> Socket(proxy)
            }
            this.rawSocket = rawSocket
            try {
    
    
                //2、进行Socket连接  
                Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
            } catch (e:ConnectException) {
    
    
                throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
    
    
                    initCause(e)
                }
            }
            try {
    
    
                //3、okio中的接口,用来输入,类似于  InputStream
                source = rawSocket.source().buffer()
                //4、okio中的接口 ,用来输出,类似于OutputStream 
                sink = rawSocket.sink().buffer()
            } catch (npe: NullPointerException) {
    
    
                if (npe.message == NPE_THROW_WITH_NULL) {
    
    
                    throw IOException(npe)
                }
            }
        }

可以看到,是通过创建Socket对象使用OKio中的接口获得输入/输出流。
总结:ConnectInterceptor的内容就是这些了,可以看得出来ConnectInterceptor是一个特别重要的拦截器,在这个拦截器中真正的建立了连接,并且获得了输入输出流,为将来的输入输出进行了准备。
内容过程如下:

  • 1、首先查找是否有可用的连接:先去缓存池中找,没有的话重新创建
  • 2、建立链接:创建Socket对象、TCP握手和TSL握手也、使用OKio中的接口获得输入/输出流。
  • 3、加入链接到连接池
  • 4、返回Exchange对象给下一个对象

请求服务拦截器:CallServerInterceptor

终于到了最后一个拦截器:服务请求拦截器。
上面已经通过链接拦截器,建立起了客户端和服务端的联系。
那么接下来就是和服务器发送header和body以及接收服务器返回的数据了,这些逻辑都在这个拦截器中。

class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
    
    

直接来看,请求服务拦截器中的intercept中都做了些什么吧(主要内容都是用OKio做的,所以直接简略的看主要部分吧)

class CallServerInterceptor(private val forWebSocket: Boolean) :Interceptor {
    
    

        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
    
    
            val exchange = realChain.exchange!!
                    val request = realChain.request
            val requestBody = request.body
            // 1、将请求头写入到socket中,如果是GET那么请求已经结束
            exchange.writeRequestHeaders(request)
            var responseBuilder: Response.Builder? = null
            if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
    
    
                // 2、在HTTP中POST请求会发送两个包,
                // 先发送请求头,获得相应为100后再发送请求体。
                if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
    
    
                    exchange.flushRequest()
                    responseBuilder = exchange.readResponseHeaders(expectContinue = true)
                    exchange.responseHeadersStart()
                }
                // 3、写入请求体 通过Okio将body写入到socket中,用于发送给服务器
                requestBody.writeTo(bufferedRequestBody)
                // 4、读取响应头(通过ExchangeCodec协议类)
                responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
                        var response = responseBuilder
                        .request(request)
                        .handshake(exchange.connection.handshake())
                        .sentRequestAtMillis(sentRequestMillis)
                        .receivedResponseAtMillis(System.currentTimeMillis())
                        .build()
                var code = response.code
                response = if (forWebSocket && code == 101) {
    
    
                ...
                } else {
    
    
                    // 5、读取响应体(通过ExchangeCodec协议类)
                    response.newBuilder()
                            .body(exchange.openResponseBody(response))
                            .build()
                }
                ...
                return response
            }
        }

了解HTTP的同学们,应该很容易就知道CallServerInterceptor是在干什么了,就是在做客户端与服务端的请求的数据的交互而已。
总结:

  • 1、先写入请求头,如果是GET请求的话就已经请求完毕,POST请求的话是先发送请求头再发送请求体,会发送两个TCP包
  • 2、然后读取响应头、读取响应体。利用ExchangeCodec协议类
  • 3、最终将响应的结果返回,经过层层拦截器。

拦截器总结

作为重中之重的拦截器,采用了责任链的模式,层层的请求,又层层的将结果返回回去。
我们用一个流程图来简述这5个拦截器吧:(说句实话,如果真是认真看了,并且对照了真正的源码去走流程,根本不需要流程图去辅助,真的通透,因为OkHttp本身设计就很通透)
在这里插入图片描述

整两个小问题聊一聊

Application Interceptors与Network Interceptors

还几个拦截器的入口吗?
Application Interceptors在我们的第一个拦截器中
Network Interceptors在我们的倒数第二个拦截器中

internal fun getResponseWithInterceptorChain(): Response {
    
    
  // 保存所有拦截器的集合
  val interceptors = mutableListOf<Interceptor>()
  // 第一个可以说是自定义拦截器吧,做些日志之类的
  interceptors += client.interceptors
  // 重试重定向拦截器
  interceptors += RetryAndFollowUpInterceptor(client)
  // 桥接拦截器,补全网络请求
  interceptors += BridgeInterceptor(client.cookieJar)
  // 缓存拦截器,缓存结果的
  interceptors += CacheInterceptor(client.cache)
  // 链接拦截器,建立Socket连接,并缓存链接复用
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    
    
    // 用户定义的网络拦截器,可以用来获取一些请求信息
    interceptors += client.networkInterceptors
  }
  // 请求服务拦截器,与服务器进行请求,返回结果的
  interceptors += CallServerInterceptor(forWebSocket)

不同点:他俩的主要区别是他的拦截的时机不同,在项目中使用也是看他的拦截时机,去确定使用哪个拦截器进行自定义。
相同点:都提供给我们进行自定义使用

  • Application Interceptors
    • 发生时机:请求发送前和网络响应后
    • 特点:不需要担心中间过程的响应,如重定向和重试
    • 调用次数:只被调用一次
  • Network Interceptors
    • 发生时机:在与服务器进行交互的过程中
    • 特点:能够操作中间过程的响应,如重定向和重试
    • 调用次数:可能被调用多次,因为有重定向和重试的情况

怎样自定义拦截器,用来干嘛

先来定义一个拦截器(先来一个Application Interceptors的)
我们可以利用Application Interceptors去做一些对整个请求过程的日志
需求:记录一下整个请求的时间

class MyInterceptor : Interceptor {
    
    
    private val TAG = "MyInterceptor"
    override fun intercept(chain: Interceptor.Chain): Response {
    
    
        // 1、获得此次请求的request,拿到以后可以根据需求进行自定义
        val request = chain.request()
        // 开始时间
        val prevCallTime = System.nanoTime()
        // 2、将任务交给下一个拦截器
        val response = chain.proceed(request)
        // response 返回时间
        val laterCallTime = System.nanoTime()
        // 打印本次响应结果返回到本层拦截器的时间
        Log.v(TAG, "consume time: ${laterCallTime - prevCallTime}")
        // 3、返回此次请求的结果
        return response
    }
}

其中我们需要注意的点:

  • 继承Interceptor ,重写intercept方法
  • 获取request ,通过chain.proceed责任链去将请求送给下一个拦截器处理,也就是重试重定向拦截器。
  • 返回此次请求的结果response

在自定义拦截器中我们监听本次请求的时间。
怎么使用呢?
只需要在构建 OkHttpClient 的时候将拦截器添加进去就可以了。

val client = OkHttpClient().newBuilder().apply {
    
    
    .....
    .....
    // Application拦截器
    addInterceptor(MyInterceptor())
    ....
}.build()

再演示一下利用Network Interceptors的自定义拦截器使用
需求:我们通过拦截器去打印一下重定向的次数和重定向的URL

class CustomInterceptor : Interceptor {
    
    
    private val TAG = "MyInterceptor"
    override fun intercept(chain: Chain): Response {
    
    
        // 1、获取当前的request
        val request: Request = chain.request()

        // 2、执行request,获取response
        val response: Response = chain.proceed(request)

        // 获取重定向的次数
        val redirectCount =
            if (response.networkResponse() != null) response.networkResponse()
                .priorResponseCount() else 0

        // 打印重定向的URL和次数
        Log.v(TAG, "URL: ${request.url}")
        Log.v(TAG, "Redirect count: ${redirectCount}")
        //3、返回此次请求的结果
        return response
    }
}

需要注意的和上面一样都是一个套路。
怎么去使用呢?

val client = OkHttpClient().newBuilder().apply {
    
    
    .....
    .....
    // Network拦截器
    addNetworkInterceptor(MyInterceptor())
    ....
}.build()

总结

OkHttp的源码呢,相对Glide来说真是舒服多了,一是分支少,二是流程不拐弯。
这都得力于他的责任链、构建者、适配器等设计模式。让整个源码看着十分的舒服。

加油兄弟们!!!!

猜你喜欢

转载自blog.csdn.net/weixin_45112340/article/details/132438706