从通讯流程聊OkHttp拦截器

点击上方蓝字关注我,知识会给你力量java

前言

以前咱们结合设计模式简单说了下OkHttp的大致流程,今天就继续说说它的核心部分——拦截器web

由于拦截器组成的链实际上是完成了网络通讯的整个流程,因此咱们今天就从这个角度说说各拦截器的功能。算法

首先,作一下简单回顾,从getResponseWithInterceptorChain方法开始。segmentfault

简单回顾(getResponseWithInterceptorChain)

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(
        interceptors = interceptors
        //...
    )

    val response = chain.proceed(originalRequest)
  }

这些拦截器会造成一条链,组织了请求接口的全部工做。设计模式

以上为上节内容,不了解的朋友能够返回上一篇文章看看。promise

假如我来设计拦截器

先抛开拦截器的这些概念不谈,咱们回顾下网络通讯过程,看看实现一个网络框架至少要有哪些功能。浏览器

  • 请求过程:封装请求报文、创建TCP链接、向链接中发送数据
  • 响应过程:从链接中读取数据、处理解析响应报文

而以前说过拦截器的基本代码格式是这样:缓存

  override fun intercept(chain: Interceptor.Chain): Response {
    //作事情A

    response = realChain.proceed(request)

    //作事情B
  }

也就是分为 请求前工做,请求传递,获取响应后工做 三部分。服务器

那咱们试试能不能把上面的功能分一分,设计出几个拦截器?微信

  • 拦截器1: 处理请求前的 请求报文封装,处理响应后的 响应报文分析

诶,不错吧,拦截器1就用来处理 请求报文和响应报文的一些封装和解析工做。就叫它封装拦截器吧。

  • 拦截器2: 处理请求前的 创建TCP链接

确定须要一个拦截器用来创建TCP链接,可是响应后好像没什么须要作链接方面的工做了?那就先这样,叫它链接拦截器吧。

  • 拦截器3:处理请求前的 数据请求(写到数据流中) 处理响应后的 数据获取(从数据流拿数据)

这个拦截器就负责TCP链接后的 I/O操做,也就是从流中读取和获取数据。就叫它 数据IO拦截器 吧。

好了,三个拦截器好像足够了,我得意满满的偷看了一眼okhttp拦截器代码,7个???我去。。

那再思考思考🤔...,还有什么状况没考虑到呢?好比失败重试?返回301重定向?缓存的使用?用户本身对请求的统一处理?因此又能够模拟出几个新的拦截器:

  • 拦截器4:处理响应后的 失败重试和重定向功能

没错,刚才只考虑到请求成功,请求失败了要不要重试呢?响应码为30一、302时候的重定向处理?这都属于要从新请求的部分,确定不能丢给用户,须要网络框架本身给处理好。就叫它 重试和重定向拦截器吧。

  • 拦截器5:处理响应前的 缓存复用 ,处理响应后的 缓存响应数据

还有一个网络请求有可能的需求就是关于缓存,这个缓存的概念可能有些朋友了解的很少,其实它多用于浏览器中。

浏览器缓存通常分为两部分:强制缓存和协商缓存

强制缓存就是服务器会告诉客户端该怎么缓存,例如 cache-Control 字段,随便举几个例子:

  • private:全部内容只有客户端能够缓存,Cache-Control的默认取值
  • max-age=xxx:表示缓存内容将在xxx秒后失效
  • no-cache:客户端缓存内容,可是是否使用缓存则须要通过协商缓存来验证决定
  • no-store:全部内容都不会被缓存,即不使用强制缓存,也不使用协商缓存

协商缓存就是须要客户端和服务器进行协商后再决定是否使用缓存,好比强制缓存过时失效了,就要再次请求服务器,并带上缓存标志,例如Etag。客户端再次进行请求的时候,请求头带上If-None-Match,也就是以前服务器返回的Etag值。

Etag值就是文件的惟一标示,服务器经过某个算法对资源进行计算,取得一串值(相似于文件的md5值),以后将该值经过etag返回给客户端

而后服务器就会将Etag值和服务器自己文件的Etag值进行比较,若是同样则数据没改变,就返回304,表明你要请求的数据没改变,你直接用就行啦。若是不一致,就返回新的数据,这时候的响应码就是正常的200

这个拦截器就是用于处理这些状况,咱们就叫它 缓存拦截器 吧。

  • 拦截器6: 自定义拦截器

最后就是自定义的拦截器了,要给开发者一个能够自定义的拦截器,用于统一处理请求或响应数据。

这下好像齐了,至于以前说的7个拦截器还有1个,留个悬念最后再说。

最后再给他们排个序吧:

  • 一、自定义拦截器的公共参数处理。
  • 二、封装拦截器封装请求报文
  • 三、缓存拦截器的缓存复用。
  • 四、链接拦截器创建TCP链接。
  • 五、IO拦截器的数据写入。
  • 六、IO拦截器的数据读取。
  • 七、缓存拦截器保存响应数据缓存。
  • 八、封装拦截器分析响应报文
  • 九、重试和重定向拦截器处理重试和重定向状况。
  • 十、自定义拦截器统一处理响应数据。

有点绕,来张图瞧一瞧:

因此,拦截器的顺序也基本固定了:

  • 一、自定义拦截器
  • 二、重试和重定向拦截器
  • 三、封装拦截器
  • 四、缓存拦截器
  • 五、链接拦截器
  • 六、IO拦截器

下面具体看看吧。

自定义拦截器

在请求以前,咱们通常建立本身的自定义拦截器,用于添加一些接口公共参数,好比把token加到Header中。

class MyInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()

        request = request.newBuilder()
                    .addHeader("token""token")
                    .url(url)
                    .build()

        return chain.proceed(request)
    }

要注意的是,别忘了调用chain.proceed,不然这条链就没法继续下去了。

在获取响应以后,咱们通常用拦截器进行结果打印,好比经常使用的HttpLoggingInterceptor

addInterceptor(
    HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }
)

重试和重定向拦截器(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
        }
        //赋予重定向请求,再次进入下一次循环
        request = followUp
      } 
    }
  }
}

这样代码就很清晰了,重试和重定向的处理都是须要从新请求,因此这里用到了while循环。

  • 当发生请求过程当中错误的时候,就须要重试,也就是经过continue进入下一次循环,从新走到 realChain.proceed方法进行网络请求。
  • 当请求结果须要 重定向的时候,就赋予新的请求,并进入下一次循环,从新请求网络。
  • 当请求结果没有重定向,那么就直接返回 response响应结果。

封装拦截器(BridgeInterceptor)

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

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //添加头部信息
    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)

    val networkResponse = chain.proceed(requestBuilder.build())

    //解压
    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()))
      }
    }

    return responseBuilder.build()
  }

请求前的代码很简单,就是添加了一些必要的头部信息,包括Content-Type、Host、Cookie等等,封装成一个完整的请求报文,而后交给下一个拦截器。

而获取响应后的代码就有点不是很明白了,gzip是啥?GzipSource又是什么类?

gzip压缩是基于deflate中的算法进行压缩的,gzip会产生本身的数据格式,gzip压缩对于所须要压缩的文件,首先使用LZ77算法进行压缩,再对获得的结果进行huffman编码,根据实际状况判断是要用动态huffman编码仍是静态huffman编码,最后生成相应的gz压缩文件。

简单的说,gzip就是一种压缩方式,能够将数据进行压缩,在添加头部信息的时候就添加了这样一个头部:

requestBuilder.header("Accept-Encoding""gzip")

这一句其实就是在告诉服务器,客户端所能接受的文件的压缩格式,这里设置了gzip以后,服务器看到了就能把响应报文数据进行gzip压缩再传输,提升传输效率,节省流量。

因此请求以后的这段关于gzip的处理其实就是客户端对压缩数据进行解压缩,而GzipSource是okio库里面一个进行解压缩读取数据的类。

缓存拦截器(CacheInterceptor)

继续看缓存拦截器—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

    // 若是不容许使用网络,而且缓存数据为空
    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
      } 
    }

    //若是开发者设置了缓存,则将响应数据缓存
    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        //缓存header
        val cacheRequest = cache.put(response)
        //缓存body
        return cacheWritingResponse(cacheRequest, response)
      }
    }

    return response
  }
}

仍是分两部分看:

  • 请求以前,经过request获取了缓存,而后判断缓存为空,就直接返回code为504的结果。若是有缓存而且缓存可用,则直接返回缓存。
  • 请求以后,若是返回 304表明服务器数据没修改,则直接返回缓存。若是 cache不为空,那么就把 response缓存下来。

这样看是否是和上面咱们说过的缓存机制对应上了?请求以前就是处理强制缓存的状况,请求以后就会处理协商缓存的状况。

可是仍是有几个问题须要弄懂:

一、缓存是怎么存储和获取的?二、每次请求都会去存储和获取缓存吗?三、缓存策略(CacheStrategy)究竟是怎么处理网络和缓存的?networkRequest何时为空?

首先,看看缓存哪里取的:

val cacheCandidate = cache?.get(chain.request())

internal fun get(request: Request): Response? {
    val key = key(request.url)
    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
  }

经过cache.get方法获取了response缓存,get方法中主要是用到了请求Request的url来做为获取缓存的标志。因此咱们能够推断,缓存的获取是经过请求的url做为key来获取的。

那么cache又是哪里来的呢?

val cache: Cache? = builder.cache

interceptors += CacheInterceptor(client.cache)

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

没错,就是实例化CacheInterceptor的时候传进去的,因此这个cache是须要咱们建立OkHttpClient的时候设置的,好比这样:

  val okHttpClient =
      OkHttpClient().newBuilder()
          .cache(Cache(cacheDir, 10 * 1024 * 1024))
          .build()

这样设置以后,okhttp就知道cache存在哪里,大小为多少,而后就能够进行服务器响应的缓存处理了。

因此第二个问题也解决了,并非每次请求都会去处理缓存,而是开发者须要去设置缓存的存储目录和大小,才会针对缓存进行这一系列的处理操做。

最后再看看缓存策略方法 CacheStrategy.Factory().compute()

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为空。因此也就对应上一开始缓存拦截器中的获取缓存后的判断:

    // 若是不容许使用网络,可是有缓存,则直接返回缓存
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

链接拦截器(ConnectInterceptor)

继续,链接拦截器,以前说了是关于TCP链接的。

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方法:

internal fun initExchange(chain: RealInterceptorChain): Exchange {
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    return result
  }

  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  )
: ExchangeCodec {
    try {
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      return resultConnection.newCodec(client, chain)
    } 
  }

好像有一点眉目了,找到一个ExchangeCodec类,并封装成一个Exchange类。

  • ExchangeCodec:是一个链接所用的编码解码器,用于编码HTTP请求和解码HTTP响应。
  • Exchange:封装这个编码解码器的一个工具类,用于管理ExchangeCodec,处理实际的 I/O。

明白了,这个链接拦截器(ConnectInterceptor)就是找到一个可用链接呗,也就是TCP链接,这个链接就是用于HTTP请求和响应的。你能够把它能够理解为一个管道,有了这个管道,才能把数据丢进去,也才能够从管道里面取数据。

而这个ExchangeCodec,编码解码器就是用来读取和输送到这个管道的一个工具,至关于把你的数据封装成这个链接(管道)须要的格式。我咋知道的?我贴一段ExchangeCodec代码你就明白了:

//Http1ExchangeCodec.java
  fun writeRequest(headers: Headers, requestLine: String) {
    check(state == STATE_IDLE) { "state: $state" }
    sink.writeUtf8(requestLine).writeUtf8("\r\n")
    for (i in 0 until headers.size) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n")
    }
    sink.writeUtf8("\r\n")
    state = STATE_OPEN_REQUEST_BODY
  }

这里贴的是Http1ExchangeCodec的write代码,也就是Http1的编码解码器。

很明显,就是将Header信息一行一行写到sink中,而后再由sink交给输出流,具体就不分析了。只要知道这个编码解码器就是用来处理链接中进行输送的数据便可。

而后就是这个拦截器的关键了,链接究竟是怎么获取的呢?继续看看:

  private fun findConnection(): RealConnection {

    // 一、复用当前链接
    val callConnection = call.connection 
    if (callConnection != null) {
        //检查这个链接是否可用和可复用
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          toClose = call.releaseConnectionNoEvents()
        }
      return callConnection
    }

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

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


    // 四、建立新链接
    val newConnection = RealConnection(connectionPool, route)
    newConnection.connect

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

    //六、仍是要使用建立的新链接,放入链接池,并返回
    connectionPool.put(newConnection)
    return newConnection
  }

获取链接的过程很复杂,为了方便看懂,我简化了代码,分红了6步。

  • 一、检查当前链接是否可用。

怎么判断可用的?主要作了两个判断 1)判断是否再也不接受新的链接 2)判断和当前请求有相同的主机名和端口号。

这却是很好理解,要这个链接是链接的同一个地方才能复用是吧,同一个地方怎么判断?就是判断主机名和端口号

还有个问题就是为何有当前链接??明明还没开始链接也没有获取链接啊,怎么链接就被赋值了?

还记得重试和重定向拦截器吗?对了,就是当请求失败须要重试的时候或者重定向的时候,这时候链接还在呢,是能够直接进行复用的。

  • 2和三、从链接池中获取可用链接

第2步和第3步都是从链接池获取链接,有什么不同吗?

connectionPool.callAcquirePooledConnection(address, call, nullfalse)
connectionPool.callAcquirePooledConnection(address, call, routes, false)

好像多了一个routes字段?

这里涉及到HTTP/2的一个技术,叫作 HTTP/2 CONNECTION COALESCING(链接合并),什么意思呢?

假设有两个域名,能够解析为相同的IP地址,而且是能够用相同的TLS证书(好比通配符证书),那么客户端能够重用相同的TCP链接从这两个域名中获取资源。

再看回咱们的链接池,这个routes就是当前域名(主机名)能够被解析的ip地址集合,这两个方法的区别也就是一个传了路由地址,一个没有传。

继续看callAcquirePooledConnection代码:

  internal fun isEligible(address: Address, routes: List<Route>?)Boolean {

    if (address.url.host == this.route().address.url.host) {
      return true 
    }

    //HTTP/2 CONNECTION COALESCING
    if (http2Connection == nullreturn false
    if (routes == null || !routeMatchesAny(routes)) return false
    if (address.hostnameVerifier !== OkHostnameVerifier) return false
    return true 
  }

1)判断主机名、端口号等,若是请求彻底相同就直接返回这个链接。 

2)若是主机名不一样,还能够判断是否是HTTP/2请求,若是是就继续判断路由地址,证书,若是都能匹配上,那么这个链接也是可用的。

  • 四、建立新链接

若是没有从链接池中获取到新链接,那么就建立一个新链接,这里就很少说了,其实就是调用到socket.connect进行TCP链接。

  • 五、再从链接池获取一次链接,防止在新建链接过程当中有其余竞争链接被建立了

建立了新链接,为何还要去链接池获取一次链接呢?由于在这个过程当中,有可能有其余的请求和你一块儿建立了新链接,因此咱们须要再去取一次链接,若是有能够用的,就直接用它,防止资源浪费。

其实这里又涉及到HTTP2的一个知识点:多路复用

简单的说,就是不须要当前链接的上一个请求结束以后再去进行下一次请求,只要有链接就能够直接用。

HTTP/2引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样在收到数据以后,就能够按照序列对数据进行合并,而不会出现合并后数据错乱的状况。一样是由于有了序列,服务器就能够并行的传输数据,这就是流所作的事情。

因此在HTTP/2中能够保证在同一个域名只创建一路链接,而且能够并发进行请求。

  • 六、新链接放入链接池,并返回

最后一步好理解吧,走到这里说明就要用这个新链接了,那么就把它存到链接池,返回这个链接。

这个拦截器确实麻烦,你们好好梳理下吧,我也再来个图:

IO拦截器(CallServerInterceptor)

链接拿到了,编码解码器有了,剩下的就是发数据,读数据了,也就是跟I/O相关的工做。

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
  }
}

这个拦截器 却是没干什么活,以前的拦截器兄弟们都把准备工做干完了,它就调用下exchange类的各类方法,写入header,body,拿到code,response

这活可干的真轻松啊。

被遗漏的自定义拦截器(networkInterceptors)

好了,最后补上这个拦截器networkInterceptors,它也是一个自定义拦截器,位于CallServerInterceptor以前,属于倒数第二个拦截器。

那为何OkHttp在有了一个自定义拦截器的前提下又提供了一个拦截器呢?

能够发现,这个拦截器的位置是比较深的位置,处在发送数据的前一刻,以及收到数据的第一刻。这么敏感的位置,决定了经过这个拦截器能够看到更多的信息,好比:

  • 请求以前,OkHttp处理以后的请求报文数据,好比增长了各类header以后的数据。
  • 请求以后,OkHttp处理以前的响应报文数据,好比解压缩以前的数据。

因此,这个拦截器就是用来网络调试的,调试比较底层、更全面的数据。

总结

最后再回顾下每一个拦截器的做用:

  • addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在全部的拦截器处理以前进行最先的拦截处理,好比一些公共参数,Header均可以在这里添加。
  • RetryAndFollowUpInterceptor,这里会对链接作一些初始化工做,以及请求失败的重试工做,重定向的后续请求工做。
  • BridgeInterceptor,这里会为用户构建一个可以进行网络访问的请求,同时后续工做将网络请求回来的响应Response转化为用户可用的Response,好比添加文件类型,content-length计算添加,gzip解包。
  • CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,并且若是本地有了可⽤的Cache,就能够在没有网络交互的状况下就返回缓存结果。
  • ConnectInterceptor,这里主要就是负责创建链接了,会创建TCP链接或者TLS链接,以及负责编码解码的HttpCodec。
  • networkInterceptors,这里也是开发者本身设置的,因此本质上和第一个拦截器差很少,可是因为位置不一样,用处也不一样。这个位置添加的拦截器能够看到请求和响应的数据了,因此能够作一些网络调试。
  • CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操做,经过socket读写数据。

参考

https://www.jianshu.com/p/bfb13eb3a425 https://segmentfault.com/a/1190000020386580 

https://www.jianshu.com/p/02db8b55aae9 

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc

感谢你们的阅读,有一块儿学习的小伙伴能够关注下公众号—码上积木❤️

每日一个知识点,创建完总体系架构。



在看你最好看


本文分享自微信公众号 - 码上积木(Lzjimu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索