OkHttp浅析

引言

说到Android开发中用到的网络库,OkHttp可谓是不可不提的一个,它是由Square 公司为 JVM、Android 和 GraalVM 精心设计的 HTTP 客户端,Google官方也在源码当中的 HttpURLConnection 的底层实现改成 了OkHttp

尽管我们在进行网络请求时通常情况是使用的对于它二次封装版本Retrofit,但归根结底依靠的还是OkHttp,包括像其他一些库在关于网络请求方面(例如Glide)都对它做了支持,因而了解它是对于我们来说也是至关重要的


OkHttp使用起来方便,优点也很多:

  • HTTP/2支持,允许对同一个主机的所有请求共享一个套接字
  • 使用连接池降低请求延迟 (HTTP/2不可用情况下)
  • 支持gzip压缩,以便通过减少网络数据的大小来提高网络效率
  • 自动重试重定向,在请求失败时自动重试请求,从而提高请求可靠性
  • 自动处理请求结果的缓存,避免网络重复请求,以便最大化网络效

概述

本文基于OkHttp 4.12.0网络请求的执行流程进行分析,OkHttp主要分为四个角色,分别是OkHttpClient、RequestCallResponse

角色

含义

OkHttpClient

网络请求客户端

Request

网络请求对象

Call

一次具体的网络请求发起者

Response

网络请求返回的数据结果

使用流程可简单分为以下三步:

  1. 通过建造者模式来分别创建出OkHttpClientRequest对象
  2. 调用OkHttpClientnewCall方法构建出Call对象
  3. 使用Call对象的同步请求方法execute或者是异步请求方法enqueue发起请求获取到Response
val client = OkHttpClient.Builder().build()
val request = Request.Builder().url("https://wanandroid.com").build()

val call =client.newCall(request)

val response =call.execute()

从上面可以看到,OkHttp的使用还是比较简单的,本文重点分析请求流程,在分析前这里先介绍另外一个角色就是调度器Dispatcher

HttpClient的内部持有一个Dispatcher对象,它主要是用来对异步请求的策略管理,默认设置最多存在的请求数量为64,同一主机最大请求数为5,所以一旦不满足其中一个,那么该请求就不会立马得到处理。Dispatcher内部有三个ArrayDeque(用数组实现的双端队列)分别是:

  • 正在执行的同步请求队列 runningSyncCalls
  • 准备执行的异步请求队列 readyAsyncCalls
  • 正在执行的异步请求队列 runningAsyncCalls

同步请求execute

通过如下代码发起同步请求

client.newCall(request).execute()

接下来看这个execute方法的具体实现

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

    timeout.enter()
    // 这里主要是个监听器,表示开始进行网络请求了
    callStart()
    try {
        //将自身加入runningSyncCalls中
        client.dispatcher.executed(this)
        // 获得相应结果
        return getResponseWithInterceptorChain()
    } finally {
        client.dispatcher.finished(this)
    }
}

直接看try语句块的代码,一共就两行:

第一句的调用在dispatcher的实现中是往runningSyncCalls加入元素(也就是RealCall

第二句调用getResponseWithInterceptorChain返回 Resopnse

同步请求只用了两代码行就完成了,是不是很简单呢?重点是getResponseWithInterceptorChain,这个内部具体实现先不管,先来分析异步请求

异步请求enqueue

使用enqueue方法发起异步请求,传入callback接收请求结果

client.newCall(request).enqueue(CallBack)

enqueue实现中会将我们在外部传入的Callback包装成一个AsyncCall对象,作为参数执行Dispatcherenqueue方法。AsyncCall是我们通过OkHttpClient.newCall构建出的RealCall对象的内部类,所以也就天然有RealCall对象包括其内部字段的直接引用,它还实现了Runnable接口,便于后续被放入线程池中执行。

来到Dispatcher的相关方法

internal fun enqueue(call: AsyncCall) {  
  synchronized(this) {  
    readyAsyncCalls.add(call)   
    //查找当前是否有指向同一 Host 的异步请求
    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 
      //检查当前call的主机的请求是否超过最大限制
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue 
  
      i.remove()  
      //标记当前call主机数+1
      asyncCall.callsPerHost.incrementAndGet()  
      //加入到可请求队列中
      executableCalls.add(asyncCall)  
      runningAsyncCalls.add(asyncCall)  
    }  
    isRunning = runningCallsCount() > 0  
  }  
  
  if (executorService.isShutdown) {
      for (i in 0 until executableCalls.size) {  
        val asyncCall = executableCalls[i]  
        asyncCall.callsPerHost.decrementAndGet()  
  
        synchronized(this) {  
          runningAsyncCalls.remove(asyncCall)  
        }  
  
        asyncCall.failRejected()  
      }  
      idleCallback?.run()  
  } else {  
	//将所有符合条件的可执行请求加入线程池中等待执行
    for (i in 0 until executableCalls.size) {  
      val asyncCall = executableCalls[i]  
      asyncCall.executeOn(executorService)  
    }  
  }  
  
  return isRunning  
}

Dispatcher中首先将它加入readyAsyncCalls双端队列中。然后就是策略过滤出该队列中全部的AsyncCall,放入线程池去执行,即触发AsyncCall的 run() 方法。

override fun run() {  
  threadName("OkHttp ${redactedUrl()}") {  
    var signalledCallback = false  
    timeout.enter()  
    try {  
      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 {  
      client.dispatcher.finished(this)  
    }  
  }  
}

可以看到,在AsyncCallrun方法中,也是通过getResponseWithInterceptorChain方法来获取Response,最后通过之前包装的Callback将执行结果返回到外部去。

获取请求结果

不管是同步还是异步请求最后获取请求结果都是通过走getResponseWithInterceptorChain这个方法,而它顾名思义就是以拦截器链来获取请求结果的。它的这个设计思想就有点类似于我们Andrid当中的触摸事件TouchEvent的传递,又或者是说是设计模式当中的责任制模式。


具体实现如下:

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

总的来说主要的步骤是将OkHtttpClient中定义好的Interceptor放入空集合中,包含我们用户自定义的普通拦截器和网络拦截器,然后再加入内置的几个已实现好的Interceptor构建出一个完整的拦截器集合,然后让拦截器链(Interceptor.Chain)去处理它

RealInterceptorChain是拦截器链的具体实现,在构建出完整的拦截器集合后紧接着就是创建RealInterceptorChain对象,最后就是调用它的proceed方法得到Response

注意自定义拦截器和OkHttp的内置拦截器在整个拦截器链中的位置

OkHttp五大拦截器

RetryAndFollowUpInterceptor

重试重定向拦截器用于请求失败的重试工作以及重定向的后续请求工作,同时还会对连接做一些初始化工作。

@Throws(IOException::class)  
override fun intercept(chain: Interceptor.Chain): Response {  
  val realChain = chain as RealInterceptorChain  
  var request = chain.request  
  val call = realChain.call  
  var followUpCount = 0  
  var priorResponse: Response? = null  
  var newRoutePlanner = true  
  var recoveredFailures = listOf<IOException>()  
  while (true) {  
    call.enterNetworkInterceptorExchange(request, newRoutePlanner, chain)  
  
    var response: Response  
    var closeActiveExchange = true  
    try {  
      if (call.isCanceled()) {  
        throw IOException("Canceled")  
      }  
  
      try {  
      //1.获取响应
        response = realChain.proceed(request)  
        newRoutePlanner = true  
      } catch (e: IOException) {    
      // 尝试检查重试
        if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {  
          throw e.withSuppressed(recoveredFailures)  
        } else {  
          recoveredFailures += e  
        }  
        newRoutePlanner = false  
        continue      }  
 
      response = response.newBuilder()  
        .request(request)  
        .priorResponse(priorResponse?.stripBody())  
        .build()  
  
      val exchange = call.interceptorScopedExchange  
      //跟踪重定向。构建后续的Request  
      val followUp = followUpRequest(response, exchange)  
  
      if (followUp == null) {  
        if (exchange != null && exchange.isDuplex) {  
          call.timeoutEarlyExit()  
        }  
        closeActiveExchange = false  
        return response  
      }  

	  //如果请求时一次性的则放弃重试
      val followUpBody = followUp.body  
      if (followUpBody != null && followUpBody.isOneShot()) {  
        closeActiveExchange = false  
        return response  
      }  
  
      response.body.closeQuietly()  
  
      if (++followUpCount > MAX_FOLLOW_UPS) {  
        throw ProtocolException("Too many follow-up requests: $followUpCount")  
      }  
  
      request = followUp  
      priorResponse = response  
    } finally {  
      call.exitNetworkInterceptorExchange(closeActiveExchange)  
    }  
  }  
}

请求失败自动重试并且根据需要进行重定向。这里面有个死循环一直在执行请求,通过返回Resopnse或者抛出异常的形式结束循环。重试就是请求失败捕获到IO异常时,在这个请求连接还未关闭时(IO异常不属于ConnectionShutdownException时),通过recover方法去判断是否能够进行重试。重定向就是通过followUpRequest尝试构建重定向请求来重新请求,如果这个重定向请求方法返回的Requestnull或者RequesBody是一次性的话(isOneShot)就直接返回这次的Response。否则就是重定向次数加一,进行重定向请求,重定向有个最大次数20次,超过的话就直接丢出异常(ProtocolException

BridgeInterceptor

桥接拦截器,主要用于补全请求头+gzip解析。这块的介绍就不贴代码了,这里简要地说下逻辑:在请求之前,它向我们的请求头中添加必要的参数,在拿到请求的响应后,根据响应头中的参数去判断是否需要 gzip 解析,如果需要则通过 GzipSource 去解析。

CacheInterceptor

用于网络缓存,我们可以通过OkHttpClient.cache()方法来配置缓存。在讲它之前先来了解下HTTP的缓存规则,可以按照其行为将其分为两大类:

  • 缓存:浏强览器并不会将请求发送给服务器。强缓存是利用 http 的返回头中的 Expires 或者 Cache-Control 两个字段来控制的,用来表示资源的缓存时间
  • 协商缓存:浏览器会将请求发送至服务器。服务器根据 http 头信息中的 Last-Modify/If-Modify-SinceEtag/If-None-Match 来判断是否命中协商缓存。如果命中,则 http 返回码为 304 ,客户端从本地缓存中加载资源

接着来看看 CacheInterceptor 拦截器做了哪些工作吧

override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    //本地的缓存
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()

    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    //需要发起的请求
    val networkRequest = strategy.networkRequest
    //本地的缓存
    val cacheResponse = strategy.cacheResponse

    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE

    if (cacheCandidate != null && cacheResponse == null) {
        // The cache candidate wasn't applicable. Close it.
        cacheCandidate.body.closeQuietly()
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    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)")
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build().also {
                listener.satisfactionFailure(call, it)
            }
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
        return cacheResponse!!.newBuilder()
            .cacheResponse(cacheResponse.stripBody())
            .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 {
        networkResponse = chain.proceed(networkRequest)
    } finally {
        // If we're crashing on I/O or otherwise, don't leak the cache body.
        if (networkResponse == null && cacheCandidate != null) {
            cacheCandidate.body.closeQuietly()
        }
    }

    //协商缓存
    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(cacheResponse.stripBody())
                .networkResponse(networkResponse.stripBody())
                .build()

            networkResponse.body.close()

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

    val response = networkResponse!!.newBuilder()
        .cacheResponse(cacheResponse?.stripBody())
        .networkResponse(networkResponse.stripBody())
        .build()

    if (cache != null) {
        if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        
            val cacheRequest = cache.put(response)
            return cacheWritingResponse(cacheRequest, response).also {
                if (cacheResponse != null) {
                    listener.cacheMiss(call)
                }
            }
        }

        if (HttpMethod.invalidatesCache(networkRequest.method)) {
            try {
                cache.remove(networkRequest)
            } catch (_: IOException) {
            }
        }
    }

    return response
}

缓存拦截器整的工作流程总的来说就是:首先根据本次请求的Request先从磁盘获取缓存,然后根据缓存策略重新计算出需要通过网络发起的请求(networkRequest)和可用的缓存(cacheResponse)。后面的话就是通过这两个来判断处理了,假如二者都为null,那么就直接构建出一个504错误码失败的Resopnse返回;如果networkRequest为null,cacheResponse不为null,那么就使用缓存返回;如果前面两步都没走到的话,那么就会拿networkRequest开始网络请求,服务器返回请求后,如果刚开始通过缓存策略计算出的cacheResponse不为null,而且服务器返回的状态码是304说明资源没有变换,那就还是使用缓存返回。不是的话就使用本次的响应,然后更新磁盘cache

ConnectInterceptor

连接拦截器,获取连接 Exchange,传递到下一给拦截器处理

object ConnectInterceptor : Interceptor {  
  @Throws(IOException::class)  
  override fun intercept(chain: Interceptor.Chain): Response {  
    val realChain = chain as RealInterceptorChain  
    //获取连接 Exchange:数据交换(封装了连接)  
    val exchange = realChain.call.initExchange(realChain)  
    //根据Exchange对象复制创建一个新的链
    val connectedChain = realChain.copy(exchange = exchange)  
    return connectedChain.proceed(realChain.request)  
  }  
}

连接拦截器的代码很简单,主要逻辑在initExchange方法中

internal fun initExchange(chain: RealInterceptorChain): Exchange {  
  synchronized(this) {  
    check(expectMoreExchanges) { "released" }  
    check(!responseBodyOpen)  
    check(!requestBodyOpen)  
  }  

  //这里的exchangeFinder的注入是在重试拦截器中完成的
  val exchangeFinder = this.exchangeFinder!!  
  //查找连接Realconnection 
  val connection = exchangeFinder.find()  
  //返回ExchangeCodec
  val codec = connection.newCodec(client, chain)  
  // Exchange:数据交换器 包含了exchangecodec与Realconnection
  val result = Exchange(this, eventListener, exchangeFinder, codec)  
  this.interceptorScopedExchange = result  
  this.exchange = result  
  synchronized(this) {  
    this.requestBodyOpen = true  
    this.responseBodyOpen = true  
  }  
  
  if (canceled) throw IOException("Canceled")  
  return result  
}

稍微介绍下上面的ExchangeExchangeCode
Exchange

传递单个 HTTP 请求和响应对,在 ExchangeCode 的基础上担负了一些管理及事件分发的作用

ExchangeCode

负责对 request 编码及解码 Response ,即写入请求及读取响应,我们的请求及响应数据都是通过它来读写

CallServerInterceptor

最后一个拦截器,与服务器进行通信。面对比较大的请求体时,会先去询问服务器是否接收此请求体,如果服务器接收并返回响应码200,则继续发送请求体,否则直接返回。

结语

通过本文的分析可以看出,整个的网络请求实现就在这五个内置的拦截器中,它们的协作就好比是生产流水线那样,将网络请求经过自己的工序处理后交给下一环节,较为不同的是,生产出的产品Response又会由里向外地传递。

对于ConnectInterceptorCallServerInterceptor这两者是实际完成网络请求的地方,涉及到一些底层的东西就简单概过了

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值