源码解析之OkHttp五大拦截器原理解析

简介

Okhttp是目前一个比较流行的高效网络请求框架,支持多种网络请求方式,功能强大。如下图所示:

使用步骤

  1. 创建okhttpClient实例对象
  1. 创建一个网络请求
  1. 发起网络请求

源码分析

设计模式

  1. 建造者模式

Request在创建网络请求体时,使用建造者设计模式封装多种属性,在不同的使用场景下通过Build一步一步构建出所需要的请求对象。

  1. 责任链模式

Okhttp支持五大拦截器使用,通过拦截器构建一条接收者对象链,其调用顺序通过责任链模式进行链式调用,每个接收者对象中都包含有另一个接收者的引用。如果当前接收者不处理网络请求,就会调用下一个接收者处理。

构建拦截链

  1. 策略模式

在拦截器CacheInterceptor中,响应数据的选择中使用了策略模式,选择缓存数据还是选择网络访问。CacheInterceptor根据一个缓存策略,来决定选择缓存数据,还是网络请求数据。

同步请求

1.发起同步网络请求。

scss 复制代码
 okHttpClient.newCall(request).execute()

2.将网络请求体封装成RealCall ,真正发起网络请求的是RealCall。

kotlin 复制代码
 override fun newCall(request: Request): Call =
     RealCall(this, request, forWebSocket = false)

3.调用RealCallexecute进行同步请求。

kotlin 复制代码
override fun execute(): Response {
    //超时取消
    timeout.enter()
    //日志监听
    callStart()
    try {
        //调用Dispatcher分发器进行同步请求
      client.dispatcher.executed(this)
        //执行拦截器返回请求结果
      return getResponseWithInterceptorChain()
    } finally {
        //完成请求将任务从队列中移除
      client.dispatcher.finished(this)
    }
  }

4.将请求任务放进同步请求队列runningSyncCalls。

kotlin 复制代码
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

5.执行RealInterceptorChain拦截器链进行网络请求。

kotlin 复制代码
internal fun getResponseWithInterceptorChain(): Response {
    // 创建拦截器集合
    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
    )
    try {
        //开始网络请求
      val response = chain.proceed(originalRequest)
      //返回请求结果
      return response
 
    }
  }

异步请求

1.发起异步请求。

kotlin 复制代码
 okHttpClient.newCall(request).enqueue(
             object :Callback{
            override fun onFailure(call: Call, e: IOException) {

            }

            override fun onResponse(call: Call, response: Response) {

            }

        })

2.调用分发器dispatcher.enqueue执行网络请求。

kotlin 复制代码
 override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    //真正执行网络请求    
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

3.将异步请求放入异步等待队列readyAsyncCalls

kotlin 复制代码
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()
  }

4.从readyAsyncCalls 异步等待队列中取出请求任务,放进线程池executorService执行。

kotlin 复制代码
 private fun promoteAndExecute(): Boolean {
      ...
    //定义可执行任务集合
    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
    //从异步准备队列中取出一个任务
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()
        //如果正在运行队列中的任务数超过最大64个,返回
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
         //异步任务对同一个host请求超过5个则返回
          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
  }

5.线程池的执行体调用getResponseWithInterceptorChain返回数据。

kotlin 复制代码
override fun run() {
      ...........
        try {
          //开始网络请求
          val response = getResponseWithInterceptorChain()
          //返回请求结果数据
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          
        } catch (t: Throwable) {
          //抛出异常立即取消操作
          cancel()
          ...........
        } finally {
          //不管请求是否成功,都要进行finish操作
          client.dispatcher.finished(this)
        }
      }
    }
  }

6.调用拦截器链RealInterceptorChain开始请求网络。

kotlin 复制代码
internal fun getResponseWithInterceptorChain(): Response {
    // 创建拦截器集合
    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
    )
    try {
        //开始网络请求
      val response = chain.proceed(originalRequest)
      //返回请求结果
      return response
 
    }
  }

拦截器详解

OKhttp不管是同步请求还是异步请求最终都是调用拦截器的getResponseWithInterceptorChain()完成真正的网络请求操作。

1.getResponseWithInterceptorChain()

kotlin 复制代码
internal fun getResponseWithInterceptorChain(): Response {
    // 创建拦截器集合
    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
    )
    try {
        //开始网络请求
      val response = chain.proceed(originalRequest)
      //返回请求结果
      return response
 
    }
  }

可以看出这个方法的核心就是拦截器集合interceptors, 首先将client.interceptors 全部加入其中 接着还创建并添加了BridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptor ,最后通过RealInterceptorChaindproceed (originalRequest ) 来执行整个interceptorchain ,那么整个网络请求的重点就是必须清楚拦截器是怎么完成网络请求的?我们接着往下看。

2.RealInterceptorChaind.proceed

kotlin 复制代码
verride fun proceed(request: Request): Response {
    //检查当前执行的下标是否小于拦截器集合长度
    check(index < interceptors.size)
    //计数
    calls++
    .............

    // 获取下一个拦截器,
    val next = copy(index = index + 1, request = request)
    //取出当前拦截器    
    val interceptor = interceptors[index]
     //返回下一个拦截器的执行结果
    @Suppress("USELESS_ELVIS")   
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
     //检查拦截器是否已经执行完了   
      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
  }

从这段代码实现可以看出,按照拦截器添加到 interceptors 集合的顺序,逐个往下调用拦截器的intercept()方法,所以在前面的拦截器会先被调用。

RetryAndFollowUpInterceptor失败重定向拦截器

kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    var followUpCount = 0 //统计重定向次数,不能大于20
    var priorResponse: Response? = null
    var newExchangeFinder = true
    //定义一个失败恢复集合    
    var recoveredFailures = listOf<IOException>()
     //进入死循环   
    while (true) {
      ....................
      try {
        //取消网络请求  
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }

        try {
           //调用下一个interceptor的来获得响应内容
          response = realChain.proceed(request)

        } catch (e: RouteException) {
            ......
            //抛出异常继续重试
            continue
        } catch (e: IOException) {
            ......
         //抛出异常继续重试
          continue
        }
        //如果请求任务存在,则创建response
        if (priorResponse != null) {
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
              .build()
        }
        //重新发起请求
        val followUp = followUpRequest(response, exchange)
        ......................
        //如果重试次数大于20次 则退出循环
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }
        request = followUp
        priorResponse = response
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

RetryAndFollowUpInterceptor这个拦截器 主要负责错误处理和重定向等问题,比如链接错误、IO异常

等。接下来就执行到BridgeInterceptor。

BridgeInterceptor桥接拦截器

kotlin 复制代码
 override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request() //用户请求
    val requestBuilder = userRequest.newBuilder() //构建请求
    //请求体
    val body = userRequest.body
    if (body != null) {
      val contentType = body.contentType()
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString())
      }
       //添加必要的请求头
      val contentLength = body.contentLength()
      if (contentLength != -1L) {
        requestBuilder.header("Content-Length", contentLength.toString())
        requestBuilder.removeHeader("Transfer-Encoding")
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked")
        requestBuilder.removeHeader("Content-Length")
      }
    }
   ................................
     //设置压缩编码类型
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.header("Accept-Encoding", "gzip")
    }
    //设置cookie
    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))
    }
    //设置代理
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", userAgent)
    }

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

    return responseBuilder.build()
  }

在这个BridgeInterceptor桥接拦截器中,okhttp为我们添加了必要请求头信息Content-Type、Content-Length、cookie、Connection、Host、Accept-Encoding,gzip处理等。负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应结果进行解压处理。

CacheInterceptor缓存拦截器

kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    //获取缓存    
    val cacheCandidate = cache?.get(chain.request())
     .............  
    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)
          .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)
          }
    }
    //缓存不为空,拿缓存
    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }

    var networkResponse: Response? = null
  
        //调用下一个拦截器执行
      networkResponse = chain.proceed(networkRequest)
   

    //有网络缓存直接拿网络缓存数据.
    if (cacheResponse != null) {
      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()
            .............

      } else {
        cacheResponse.body?.closeQuietly()
      }
    }

   ...................

    return response
  }

CacheInterceptor 拦截器主要工作是读取缓存和更新缓存,如果有缓存并且缓存可用,那就使用缓存,否则进行调用下一个拦截器ConnectionInterceptor 进行网络请求,并将响应内容缓存。

ConnectionInterceptor 连接拦截器

kotlin 复制代码
 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)
  }

ConnectionInterceptor 拦截器主要是打开一个到目标服务器的 connection 并调用下一个拦截器 CallServerInterceptor ,并向服务器发起真正的网络请求。

CallServerInterceptor

kotlin 复制代码
override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()
     ...................
    var responseBuilder: Response.Builder? = null
      //判断请求方法get还是post,且请求体不能为空  
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
        //校验工作
        ......................
    //构建真正的网络请求
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    var code = response.code
       //相应码处理 
    if (code == 100) {
      responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
      if (invokeStartEvent) {
        exchange.responseHeadersStart()
      }
      //读取相应  
      response = responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      code = response.code
    }
   ............
    //请求头校验
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)) {
      exchange.noNewExchangesOnConnection()
    }
    ...........
    return response
  }

CallServerInterceptor 服务器请求拦截器才是真正的进行网络数据请求。整个调用流程最终都是将请求任务交给CallServerInterceptor 处理,并返回服务器返回结果。

从整个请求流程来看,Okhttp的拦截器才是整个网络框架的核心。整个Okhttp请求流程如下图所示:

总结

感谢您的阅读,创造不易,如果您觉得本篇文章对您有帮助,请点击关注小编,您的支持就是小编创作的最大动力

相关推荐
雨白4 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹5 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空7 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭7 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日8 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安8 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑8 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟13 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡14 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0014 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体