Android面试Android进阶(十七)-OkHttp相关问题

问:简述OkHttp简单使用流程(基本不会这么问,为了分析)

答:OkHttp使用流程基本分为四个步骤。
1、创建OkHttpClient实例
2、创建Request
3、OkHttpClient生成一个Call对象实例(RealCall)
4、发送请求(同步、异步)
如:

        //1、创建一个OkHttpClient 的实例
        val client = OkHttpClient.Builder().build()
        //2、创建一个request请求体
        val request = Request.Builder().url("http://www.baidu.com").get().build()
        //3、生成真实请求对象
        val newCall = client.newCall(request)
        //4、发送同步请求,请求同步的,需要在子线程中执行
        val response = newCall.execute()
        //发送异步请求,请求异步,请求结果自动Callback回调。
        newCall.enqueue(object : Callback{
            override fun onFailure(call: Call, e: IOException) {
                //请求失败(正常来讲是网络之类的问题,无法请求到服务端,否则服务端都有会response,不管是200还是302还是500之类的)
            }

            override fun onResponse(call: Call, response: Response) {
                //请求成功
            }
        })

1、创建OkHttpClient实例:
OkHttp中使用 建造者模式(将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。)来设计OkHttpClient,里面包含了如:调度器(Dispatcher)、连接池(ConnectionPool)、拦截器(Interceptor)等等,具体看一下源码,无需关注太多,后面会挑重点解析。

class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()      //调度器(用于分发)
    internal var connectionPool: ConnectionPool = ConnectionPool()      //连接池
    internal val interceptors: MutableList<Interceptor> = mutableListOf()        //拦截器list
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()      //网络拦截器list
    internal var retryOnConnectionFailure = true    //连接失败重试
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES      //cookies
    internal var cache: Cache? = null      //缓存
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS    //支持的协议列表
    internal var connectTimeout = 10_000      //连接超时时间默认10s
    internal var readTimeout = 10_000            //读取超时时间默认10s
    internal var writeTimeout = 10_000            //写入超时时间默认10s
    //...中间及后面省略一堆代码
}

2、创建一个request请求体:
也是使用建造者模式来设计Request,里面包含了:请求url(url)、请求方式(method)、header头(headers)、请求体(body),如:

open class Builder {
    internal var url: HttpUrl? = null                    //请求url
    internal var method: String                          //请求方式
    internal var headers: Headers.Builder        //header头
    internal var body: RequestBody? = null      //请求体
    //...省略一堆代码
}

3、OkHttpClient生成一个Call对象实例(RealCall)
client调用newCall方法,获得一个Call对象(Call是一个接口,具体的实现是RealCall类)。

//OkHttpClient.kt
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
//其中,this就是OkHttpClient的对象实例,request就是第二步生成的request对象,默认websocket为false

//RealCall
class RealCall( val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean) : Call {
  private val connectionPool: RealConnectionPool = client.connectionPool.delegate
  //省略一堆代码,其实就是通过client获取Client中的参数设置
}

4、发送请求(同步、异步):
通过第三步获取的call对象,调用其实现类RealCall的 同步方法execute()异步方法enqueue(callback)
接下来可能就问了:

问:OkHttpClient有几种发起请求的方式,有何不同?

答: 有两种,同步请求和异步请求。同步请求将请求(任务)加入到调度器(Dispatcher)的runningSyncCalls(正在执行)双端队列中,然后直接调用了getResponseWithInterceptorChain,而异步请求将请求加入到调度器(Dispatcher)中,经历两个阶段:readyAsyncCalls、runningAsyncCalls,之后调用
getResponseWithInterceptorChain。

  //同步:RealCall.kt
  override fun execute(): Response {
    //检查任务是否执行过
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    //开始超时计时
    timeout.enter()
    //加入事件监听
    callStart()
    try {
      //加入到正在运行同步双端队列中
      client.dispatcher.executed(this)
      //调用getResponseWithInterceptorChain方法获取Response
      return getResponseWithInterceptorChain()
    } finally {
      //无论是否成功,都将任务从双端队列中移除
      client.dispatcher.finished(this)
    }
  }

  //异步:RealCall.kt
  override fun enqueue(responseCallback: Callback) {
    //检查任务是否执行过
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    //加入事件监听
    callStart()
    //将任务加入到调度器中,调度器中将任务添加到readyAsyncCalls 准备执行的双端队列中
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

  //Dispatcher.kt
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      //加入到准备执行的双端队列中
      readyAsyncCalls.add(call)
      // 更改AsyncCall,以使其共享到同一主机的现有运行调用的AtomicInteger,Dispathcer中定义了同一主机同时执行的最大并发数为5个
      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 // 这里会判断正在执行的队列是否大于最大的数量  这里定义的最大数为64
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 同主机最大并行数  5
        //从准备执行队列中移除,使用迭代器不影响继续遍历。
        i.remove()
        //将要执行的任务的主机记录
        asyncCall.callsPerHost.incrementAndGet()
        //加入到要执行的队列中,后面遍历执行
        executableCalls.add(asyncCall)
        //加入到正在执行的队列中
        runningAsyncCalls.add(asyncCall)
      }
      //正在执行数是否大于0,没有执行的情况下,就空闲下来。
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //executorService是一个核心线程池为0的无界线程池,且60秒空闲线程回收
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

  //先看一下ExecutorService线程池:
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        //核心线程数为0,无界线程池,60秒空闲回收,并指定了线程名称
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

   //接着执行asyncCall.executeOn()方法:
   fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        //重点在这里,excute接收一个Runable对象,之后会执行run方法。
        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) {
          //如果不是异常,则不管请求成功与否都会从执行队列中清除。
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }
  
  //到这里就执行到了Runable对象的run方法:
  override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        //开始超时计时
        timeout.enter()
        try {
          //调用getResponseWithInterceptorChain方法获取Response
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          //回调结果
          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 {
          //这个方法会重新调用promoteAndExecute方法,这样就又开始了异步的下一轮的请求了。
          client.dispatcher.finished(this)
        }
      }
    }
  }

源码一堆,不给个总结太对不起大家了(上面源码都有注释,也可以看看注释):
1、异步和同步最后都会执行getResponseWithInterceptorChain()方法获取任务执行结果,这里说任务其实是因为这里暂时还没开始去进行网络请求,只能说是一个任务
2、异步执行这个任务时,只是很简单将任务加入到正在执行的同步队列runningSyncCalls中,而异步执行经历了两个队列:readyAsyncCalls、runningAsyncCalls
3、任务执行完成后,不管异步还是同步,都会将加入到队列中的任务清除
4、同步执行实际上并没有排队的概念,异步执行是需要进行排队的,并且同主机任务最大支持5个并发,同时执行任务不超过64个

到这里,经常出现的两个词:调度器是什么?线程池又是怎么实现的?

问:OkHttp中的Dispatcher调度器是什么,是怎么实现调度的

答:调度器就是对任务执行的调度,Dispatcher中维护了三个队列,分别是一个准备异步执行队列readyAsyncCalls,两个正在执行的队列(异步正在执行队列:runningAsyncCalls,同步正在执行队列:runningSyncCalls),在每个任务执行完成最后都会执行dispatch.finish()方法,之后会重新执行到promoteAndExecute()方法,意为:促进和执行。只要队列中有任务,就会一直重复执行。 同时,调度器中定义了两个并发执行数的变量,分别对并发做限制,最大同时执行数为64,同主机执行数为5。调度器构造方法中定义了一个 ExecutorService 线程池变量,利用线程池对任务进行执行操作。

问:OkHttp中的线程池是怎么实现的?

答:OkHttp的调度器中初始化了一个线程池,线程池没有核心线程,最大支持线程为Int.MAX_VALUE(无界线程池),同时定义了60秒空闲时间,超过60秒的空线程将会回收。并且通过ThreadFactory生成的线程定义了线程名称。其工作队列为SynchronousQueue同步队列。当一个任务到来时,会加入到同步队列中,如果有空闲线程则直接从同步队列中取出,在空闲线程中进行处理,当没有空闲线程时,则会先创建一个新的线程再接受任务进行执行。OkHttp线程池设计为核心线程为0是因为客户端可能在一段时间内不会有网络请求,为了避免浪费不必要的线程内存,所以不保留最低线程,同时最大线程设置为Int.MAX_VALUE为了防止同一时间有大量的请求进入,造成部分请求被抛弃的问题,设置60秒为线程空闲最大时间,在一段时间不使用的情况进行线程回收。

前面说了,不管同步还是异步,最终都会执行 getResponseWithInterceptorChain()方法获取网络请求结果。
问题又来了:

问:OkHttp有哪些拦截器,作用分别是什么?

答:总共有七个拦截器,五个内置拦截器,两个自己添加的拦截器:

1、addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
2、RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的重试工作,重定向的后续请求工作。
3、BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
4、CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
5、ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec。
6、networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
7、CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。

看看getResponseWithInterceptorChain()方法的源码

  //RealCall.kt
  internal fun getResponseWithInterceptorChain(): Response {
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors        //添加自己的拦截器,使用时通过addInterceptor()添加
    interceptors += RetryAndFollowUpInterceptor(client)    //添加重试跟踪拦截器
    interceptors += BridgeInterceptor(client.cookieJar)        //添加桥拦截器
    interceptors += CacheInterceptor(client.cache)        //添加缓存拦截器
    interceptors += ConnectInterceptor        //添加链接拦截器
    if (!forWebSocket) {
      //如果不是websocket,添加自己的network拦截器,通过addNetworkInterceptor()添加
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)    //添加呼叫服务器拦截器 
    //将拦截器封装到 RealInterceptorChain中
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )
     //...省略代码
    try {
      //通过RealInterceptorChain实例对象执行(实际是是对每个拦截器的实现调用)
      val response = chain.proceed(originalRequest)
       //...省略代码
      return response
    } catch (e: IOException) {
       //...省略代码
    } finally {
       //...省略代码
    }
  }

getResponseWithInterceptorChain()方法实际上是对几个Interceptors拦截器加到RealInterceptorChain类(拦截器链)中,之后通过proceed方法进行执行(proceed方法意为继续的意思,实际上每个拦截器都会执行这个),我们这里先不管每个拦截器的实现以及原理是什么,我们先看看是怎么执行的。

  //RealInterceptorChain.kt
  override fun proceed(request: Request): Response {
    //省略一堆的判断逻辑...
    // 在这个链中调用下一个拦截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]
    @Suppress("USELESS_ELVIS")
    //执行拦截器的 intercept方法
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
    //...省略一堆的代码逻辑
    //每个链都会返回一个response。
    return response
  }

到这里知道,其实Interceptor是一个接口,实现类就是上面的几个拦截器。这里其实就是使用了 责任链模式,每个拦截器中都会调用proceed方法,也都会返回response,调用流程如下:(偷来的图,里面缺了第一个自己添加的拦截器及自己添加的network拦截器)

image.png

看看每个拦截器的intercept,添加的两个自定义拦截器都需要实现Interceptor接口,实现intercept方法,并且最后需要调用proceed方法:chain.proceed()。 如:

      //添加一个拦截器
      val client = OkHttpClient.Builder().addInterceptor(object : Interceptor{
            override fun intercept(chain: Interceptor.Chain): Response {
                val builder = chain.request().newBuilder()
                //在自己添加的拦截器中可以添加header头等操作
                builder.addHeader("Content-Type", "application/json;charset=UTF-8")
                val request = builder.build()
                //1、需要调用proceed方法,继续调用后面的拦截器
                val response = chain.proceed(request)
                val mediaType = response.body!!.contentType()
                val content = response.body!!.string()
                //2、返回response,责任链模式的真实写照
                return response.newBuilder()
                    .body(ResponseBody.create(mediaType, content))
                    .build()
            }
        }).build()

回过头来,看看OkHttp内置的五个拦截器的实现:

一、RetryAndFollowUpInterceptor拦截器:该拦截器从故障中恢复,并根据需要进行重定向

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

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    while (true) {
      try {
        try {
          //调用链上后面的拦截器,获取结果
          response = realChain.proceed(request)
        } catch (e: RouteException) {
          //路由错误,退出本次循环,进入下次循环
          continue
        } catch (e: IOException) {
          // 请求错误,退出本次循环,进入下次循环
          continue
        }

        //获取响应码判断是否需要重定向
        val followUp = followUpRequest(response, exchange)
        if (followUp == null) {
          //没有重定向
          return response
        }
        if(followUp.body != null){
            //没有重定向
            return response
         }
        //赋予重定向请求,再次进入下一次循环
        request = followUp
      } 
    }
  }
}

1、重试和重定向的处理都是需要重新请求,所以这里用到了while循环。
2、当发生请求过程中错误的时候,也就是通过continue进入下一次循环进行 《重试》 ,重新走到realChain.proceed方法调用后面的链的拦截器,最后再进行网络请求。
3、当请求结果没有重定向,那么就直接返回response响应结果。
4、当请求结果需要重定向的时候,就赋予新的请求,并进入下一次循环,重新请求网络。

二、BridgeInterceptor桥拦截器:从应用程序代码到网络代码的桥梁。首先,它根据用户请求建立一个网络请求,然后,它继续呼叫网络,最后,它从网络响应建立用户响应。

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

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //添加各类Header头信息,省略了很多判断代码...
    requestBuilder.header("Content-Type", contentType.toString())
    requestBuilder.header("Host", userRequest.url.toHostHeader())
    requestBuilder.header("Connection", "Keep-Alive")
    requestBuilder.header("Accept-Encoding", "gzip")
    requestBuilder.header("Cookie", cookieHeader(cookies))
    requestBuilder.header("User-Agent", userAgent)
    //同样的,每个拦截器都会调用后面的拦截器,获取response
    val networkResponse = chain.proceed(requestBuilder.build())
    
    //如果我们添加“ Accept-Encoding:gzip”标头字段,我们还将负责解压缩//传输流。
    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)
    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
      val responseBody = networkResponse.body
      if (responseBody != null) {
        val gzipSource = GzipSource(responseBody.source())
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }
    //每个拦截器都会返回response
    return responseBuilder.build()
  }

桥拦截器的作用其实就是给网络请求包装一层header头信息,链接应用程序代码和网络请求代码的一个桥梁。使用gzip(一种压缩方式,OkHttp中使用okio框架中的GzipSource类实现)压缩数据,进行数据传输,提高网络传输效率。

三、CacheInterceptor缓存拦截器:服务于来自缓存的请求,并将响应写入缓存。

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

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //取缓存
    val cacheCandidate = cache?.get(chain.request())

    //缓存策略类
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    // 不允许使用网络并且缓存为空,直接返回504(504这里是前端的code)
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)//504
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // 如果不允许使用网络,但是有缓存,直接返回缓存。
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }
    //调用链后面的拦截器继续网络请求
    networkResponse = chain.proceed(networkRequest)

    // 如果缓存不为空
    if (cacheResponse != null) {
      //304,表示数据未修改,直接返回数据,更新缓存
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        cache.update(cacheResponse, response)
        return response
      } 
    }
    //获取真实网络请求数据
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()
    //如果开发者设置了缓存,则将响应数据缓存
    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        //缓存header
        val cacheRequest = cache.put(response)
        //缓存body
        return cacheWritingResponse(cacheRequest, response)
      }
    }
    return response
  }
}

缓存拦截器主要做了以下几个事情:
1、通过缓存策略(CacheStrategy)获取缓存
2、判断是否可以使用网络,如果不允许使用网络时:缓存为空,则直接返回504,缓存不为空直接返回缓存
3、可以使用网络时,调用后续的拦截器继续网络请求,获取网络请求结果
4、请求结果下,如果有缓存比,比对code是否为304,如果是则说明缓存还可以使用,直接返回缓存,且更新缓存
5、如果缓存不可用了,则新增/更新缓存,返回最新网络请求结果。

这里会有几个问题:(1)缓存是怎么存储和获取的?(2)每次请求都会去存储和获取缓存吗?(3)缓存策略(CacheStrategy)到底是怎么处理网络和缓存的?networkRequest什么时候为空?

(1)缓存是怎么存储和获取的?

//获取缓存代码 
val cacheCandidate = cache?.get(chain.request())
//具体的方法实现
internal fun get(request: Request): Response? {
    //使用Request的url作为缓存的key
    val key = key(request.url)
    //使用DiskLruCache算法存储缓存 
    val snapshot: DiskLruCache.Snapshot = try {
      cache[key] ?: return null
    } 

    val entry: Entry = try {
      Entry(snapshot.getSource(ENTRY_METADATA))
    } 
    val response = entry.response(snapshot)
    if (!entry.matches(request, response)) {
      response.body?.closeQuietly()
      return null
    }
    return response
  }

OkHttp的缓存通过Request的url作为key进行存储,使用DiskLruCache算法对缓存进行存储。

(2)每次请求都会去存储和获取缓存吗?
这里需要看一下cache是否为空?cache变量为空的话不会有缓存,所以这个变量什么时候初始化的?

image.png

上图说明,cache应该是在创建OkHttpClient时创建的:

 val client = OkHttpClient.Builder().cache(Cache(cacheDir, 10 * 1024 * 1024))

所以第二个问题应该是:当开发者设置了缓存以后才会去存储和获取缓存,并且缓存大小有限制。

(3)缓存策略(CacheStrategy)到底是怎么处理网络和缓存的?networkRequest什么时候为空?

class CacheStrategy internal constructor(
val networkRequest: Request?,
val cacheResponse: Response?
)

  fun compute(): CacheStrategy {
    val candidate = computeCandidate()
    return candidate
  }
  private fun computeCandidate(): CacheStrategy {
    //没有缓存情况下,返回空缓存
    if (cacheResponse == null) {
      return CacheStrategy(request, null)
    }
    //...
    //缓存控制不是 no-cache,且未过期
    if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
      val builder = cacheResponse.newBuilder()
      return CacheStrategy(null, builder.build())
    }
    return CacheStrategy(conditionalRequest, cacheResponse)
  }

在这个缓存策略生存的过程中,只有一种情况下会返回缓存,也就是缓存控制不是no-cache,并且缓存没过期情况下,就返回缓存,然后设置networkRequest为空。

四、ConnectInterceptor连接拦截器:打开与目标服务器的连接,然后进入下一个拦截器。该网络可能用于返回的响应,或者用于使用条件GET验证缓存的响应。

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

连接拦截器的代码是真的简单,其他不说,就调用了 initExchange方法,Exchange是交换的意思,其实就是用网络请求地址打开一个通道,使得前后端能通过这个通道进行数据传递。
initExchange()方法最终执行到:findConnection()方法(返回用于托管新流的连接。如果存在现有连接,它将优先使用,然后是池,最后建立一个新连接。)

private fun findConnection(): RealConnection {
  // 1、尝试复用当前连接
  val callConnection = call.connection 
  if (callConnection != null) {
      //检查这个连接是否可用和可复用
      if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
        toClose = call.releaseConnectionNoEvents()
      }
    return callConnection
  }

  //2、从连接池中获取可用连接
  if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
    val result = call.connection!!
    eventListener.connectionAcquired(call, result)
    return result
  }

  //3、从连接池中获取可用连接(通过一组路由routes)
  if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
      val result = call.connection!!
      return result
    }
  route = localRouteSelection.next()


  // 4、创建新连接
  val newConnection = RealConnection(connectionPool, route)
  newConnection.connect

  // 5、再获取一次连接,防止在新建连接过程中有其他竞争连接被创建了
  if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) { 
    return result
  }

  //6、还是要使用创建的新连接,放入连接池,并返回
  connectionPool.put(newConnection)
  return newConnection
}

实际上,连接拦截器是获取一个与服务端真实的连接。如果当前有可以复用的就用复用的,没有的话从连接池中取,连接池中也没有则创建一个新的连接,然后再放入连接池,再将这个连接返回。OkHttp中连接池最大可以容纳5个空闲连接,五分钟空闲时间后将回收,具体可与看一下ConnectionPool.kt类:

class ConnectionPool internal constructor(
  internal val delegate: RealConnectionPool
) {
  constructor(
    maxIdleConnections: Int,
    keepAliveDuration: Long,
    timeUnit: TimeUnit
  ) : this(RealConnectionPool(
      taskRunner = TaskRunner.INSTANCE,
      maxIdleConnections = maxIdleConnections,
      keepAliveDuration = keepAliveDuration,
      timeUnit = timeUnit
  ))
  //连接池最大支持5个空闲连接,最大空闲时间为5分钟
  constructor() : this(5, 5, TimeUnit.MINUTES)
  //省略...
}

这里就留下一点坑吧,要不然篇幅太长太长了,我也是读源码读到这里发现这个连接池的东西,有时间再详细看看这个连接池的具体设计。

五、CallServerInterceptor拦截器:这是链中的最后一个拦截器,它与服务器进行数据交换

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

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {

    //写header数据
    exchange.writeRequestHeaders(request)
    //写body数据
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
      requestBody.writeTo(bufferedRequestBody)
    } else {
      exchange.noRequestBody()
    }

    //结束请求
    if (requestBody == null || !requestBody.isDuplex()) {
      exchange.finishRequest()
    }

    //获取响应数据
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .build()

    var code = response.code
    response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    return response
  }
}

实际上,在连接拦截器中,已经拿到连接,打开了与服务器的通道,这里就剩下read和write操作了,最后返回response。
最后:
OkHttp使用建造者模式来创建client以及request,调用newCall方法后获取Call对象,实际上是RealCall对象。之后不管是同步还是异步都会先执行自定义拦截器,五个内置拦截器,其中还会有一个network拦截器,定义在倒数第二个,用来获取最后数据交互的真实数据及响应前的数据。拦截器采用 责任链模式 来一层层顺序调用,顺序返回结果。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容