okhttp源码学习

okhttp学习路线.png

本篇只是个人的学习笔记,建议移步面试官:听说你熟悉OkHttp原理?看大佬分析

源码分析

okhttp源码是4.10.0版本

我们先看看一次最简单的网络请求

val client = OkHttpClient()

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

thread {
  val response = client.newCall(request).execute()
  Log.e(TAG, "onCreate: ${response.body}")
}

本文主要对response如何生成来解析,看看RealCall的execute方法

  override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }

直接看重点,也就是 client.dispatcher.executed(this)以及之后的,先看看dispatcher的executed方法,里面只有一句代码

    runningSyncCalls.add(call)

这里的runningSyncCalls是什么呢,是一个双端数组队列,表示的是正在运行的同步调用,包括已经取消但是还在运行的(Running synchronous calls. Includes canceled calls that haven't finished yet.),同样的队列还有runningAsyncCalls(正在异步调用),readyAsyncCalls(准备异步调用)。那么这里的意思就很明显了,就是加入队列里。

现在再看看getResponseWithInterceptorChain这个方法,这个算是okhttp的核心了。

internal fun getResponseWithInterceptorChain(): Response {
  // Build a full stack of interceptors.
  val interceptors = mutableListOf<Interceptor>()
  //应用拦截器
  interceptors += client.interceptors
  //重试和重定向拦截器
  interceptors += RetryAndFollowUpInterceptor(client)
  //桥接拦截器
  interceptors += BridgeInterceptor(client.cookieJar)
  //缓存拦截器
  interceptors += CacheInterceptor(client.cache)
  //连接拦截器
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    //网络拦截器
    interceptors += client.networkInterceptors
  }
  //回调拦截器
  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 {
    //传入request启动责任链
    val response = chain.proceed(originalRequest)
    if (isCanceled()) {
      response.closeQuietly()
      throw IOException("Canceled")
    }
    return response
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw noMoreExchanges(e) as Throwable
  } finally {
    if (!calledNoMoreExchanges) {
      noMoreExchanges(null)
    }
  }
}

暂时跳过前半部分的拦截器,这个我们在后面具体分析每一个拦截器的作用,可以看到生成response的是chain.proceed(originalRequest),那么先进去看一下具体怎么做到的

override fun proceed(request: Request): Response {
  //必须小于拦截器的数量
  check(index < interceptors.size)
  //当前拦截器调用proceed的次数
  calls++

  //exchage是对请求流的封装,在执行ConnectInterceptor前为空
  if (exchange != null) {
    //保证host和port没有被修改
    check(exchange.finder.sameHostAndPort(request.url)) {
      "network interceptor ${interceptors[index - 1]} must retain the same host and port"
    }
    //ConnectInterceptor之后每个拦截器只能调用proceed一次
    check(calls == 1) {
      "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
    }
  }

  // 创建下一个拦截器的责任链
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]
  //调用intercept方法传入新建的责任链
  @Suppress("USELESS_ELVIS")
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")

  if (exchange != null) {
    //保证ConnectInterceptor之后的拦截器都运行过proceed方法一次
    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
}

这里的check就是判断里面的条件,如果为false就会抛出错误。calls则是每个拦截器调用proceed方法的次数,需要注意的是exchange是ConnectInterceptor才不为空的,所以在ConnectInterceptor之前的拦截器都有可能走多次proceed方法,而在ConnectInterceptor之后的只有networkInterceptors和CallServerInterceptor,CallServerInterceptor是不会允许proceed方法的。所以这里的报错消息是network interceptor must call proceed() exactly once。
再看回copy方法,就是创建下一个拦截器的责任链,所以index要+1,然后再拿到下一个拦截器,调用他的intercept方法,这里就是每一个拦截器里面要具体实现的方法了,这保证了除了最后一个拦截器,前面的拦截器都会至少执行一次proceed方法(实际上第一个应用拦截器也可以不执行proceed方法)。
可以看到核心就是责任链模式,去调用每一个拦截器,最后会返回response,不用关心每个拦截器具体做了什么,只用把request发到责任链,然后得到需要的response就可以了。

那么拿到了response后,就只有最后一步了

      client.dispatcher.finished(this)

进去里面看一下,主要就是promoteAndExecute方法,以及会调用idleCallback,这里的idleCallback就是当dispatcher空闲下来之后会执行的回调

//调用的finished(runningSyncCalls, call)
private fun <T> finished(calls: Deque<T>, call: T) {
  val idleCallback: Runnable?
  synchronized(this) {
    if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
    //每次dispatcher变空闲的时候会进行的回调
    idleCallback = this.idleCallback
  }

  val isRunning = promoteAndExecute()

  if (!isRunning && idleCallback != null) {
    idleCallback.run()
  }
}

再看看promoteAndExecute方法

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

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

      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
}

这里就是循环把readyAsyncCalls里面的任务取出来,然后放进线程池以及运行中的异步队列,这个方法在enqueue和finish方法中都会调用,所以有新的请求入队和当前请求完成后,都要重新把任务提交到线程池。

  • 题外话:为什么双端队列这里用数组而不是链表,因为每次有新的请求或者完成请求都会把readyAsyncCalls里面的转换成runningAsyncCalls,而如果用链表的话会分散在内存各个地方,cpu缓存无法带来便利,并且在垃圾回收上也没有数组好。

各级拦截器作用

  1. interceptors
    用户自定义的拦截器,主要就是添加自定义的header,参数。且这个拦截器只会调用一次,所以可以用来统计网络请求发起情况,但是他可以因为本地异常重试调用多次proceed方法,或者因为中断不调用proceed方法。
  2. RetryAndFollowUpInterceptor
    重定向拦截器,这个拦截器就是用来处理错误重试和重定向的,内部会开启一个循环,如果路由连接失败或者服务器连接失败就会重试,所以也会多次调用proceed方法。
  3. BridgeInterceptor
    应用层和网络层的桥梁,主要就是为你的请求头添加各种固定信息,然后如果请求头用了头部压缩算法的话(gzip),就会在这一层进行解压。
  4. CacheInterceptor
    缓存拦截器,里面有一个缓存策略,返回networkRequest和cacheResponse,如果两者都为空,就意味着禁止使用网络并且缓存不够充足,就会失败。如果命中缓存了,就直接返回缓存里面的,没有缓存才会继续往下走,执行proceed方法。需要注意okhttp只有get方法的缓存。
  5. ConnectInterceptor
    连接拦截器,这里主要就是初始化了之前提到的exchange,这里主要有一个连接池来管理我们的连接,不然每次走网络申请都要建立一次连接,太耗费资源了。像http后面就提出长连接和多路复用这些来优化,这里有五种方法来获取连接。

1.先会查询call中是否已经有连接
2.从连接池中找
3.计算路径,再次从连接池中寻找
4.自己建立一个新的连接,如果第五种方法没有成功就会把这个新连接放到连接池中
5.从连接池中找是否有多路复用的连接,如果找到那么第四种自己建立的连接就会关闭,返回多路复用的连接

  1. networkInterceptors
    自定义的网络拦截器,因为在RetryAndFollowUpInterceptor后面,所以如果网络发生错误或者重定向该拦截器有可能多次被调用,但是只能调用一次proceed方法,通常用于监控网络层的数据传输。
  2. CallServerInterceptor
    请求拦截器,最后一个拦截器,就是对服务器发起了网络请求,没有运行proceed方法。

addInterceptor与addNetworkInterceptor的区别

从调用顺序来看,addInterceptor在责任链的第一个,addNetworkInterceptor在倒数第二个。addInterceptor在应用层中,一般只能被调用一次,允许多次调用proceed或者不调用proceed,多用于统计网络请求发起情况。而addNetworkInterceptor则可以因为重定向的原因调用多次,但是只能走一次proceed方法。多用于统计网络传输的数据。
参考资料:
面试官:听说你熟悉OkHttp原理?

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容