OkHttp网络请求流程小结

OkHttp网络请求流程小结

前言

本文基于OkHttp 4.7.2,下面的源码都是kotlin,我看了看OkHttp的github,发现最新4.8.0版本kotlin和java五五开,所以还不会kotlin的Android程序猿还不赶快学起来~

简单使用

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
      .url(url)
      .build();
Response response = client.newCall(request).enqueue(
      object : Callback {
          override fun onFailure(call: Call, e: IOException) {
              ...
          }

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

关键就是这一行:

client.newCall(request).enqueue()

其实就是两个方法:

OkHttpClient的newCall() 和 RealCall的execute()/enqueue()方法(execute()/enqueue()分别为同步和异步请求)

源码分析

以下源码主要分析异步请求,因为它的步骤比同步请求多了创建并加入线程池这一步,但两者之后的步骤都是一样的,所以不再分析同步请求的源码。

创建Call对象并加入线程池处理

OkHttpClient.newCall()

可以看到,newCall方法直接创建了一个RealCall对象

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

RealCall.enqueue()

override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
        check(!executed) { "Already Executed" }	//如果executed为true则抛出异常
        executed = true
    }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}

在创建OkHttpClient对象时,会通过它的内部类Builder来创建,其中创建了Dispatcher对象。因此这里可以通过client.dispatcher直接获取它持有的dispatcher对象。

Dispatcher任务调度类

Dispatcher用于控制并发请求,下面是其中一些主要的属性(省略了set和get方法)

ArrayDeque即双端队列,它可以从两端对元素进行增删,其中存储元素的数据结构为数组。

var maxRequests = 64			//最大并发请求数

var maxRequestsPerHost = 5		//每个主机的最大请求数

val executorService: ExecutorService	//消费者线程池

private val readyAsyncCalls = ArrayDeque<AsyncCall>()	//将要运行的异步请求队列

private val runningAsyncCalls = ArrayDeque<AsyncCall>()	//正在运行的异步请求队列

private val runningSyncCalls = ArrayDeque<RealCall>()	//正在运行的同步请求队列

构造方法

class Dispatcher constructor()		//默认构造,会在网络请求前创建默认线程池

constructor(executorService: ExecutorService) : this() {	//有参构造方法需要传入一个线程池对象
    this.executorServiceOrNull = executorService
}

RealCall.enqueue(responseCallback: Callback)

又调用回dispatcher的enqueue方法

override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
        check(!executed) { "Already Executed" }
        executed = true
    }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}

Dispatcher.enqueue(call: AsyncCall)

AsyncCall是RealCall的内部类,它是一个Runnable对象

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)	//直接将任务加入待运行异步任务队列末尾

    //下面是为了共享同一主机正在运行的调用的AtomicInteger
    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()	//这个方法会将待运行队列中符合要求的任务跃迁至正在运行队列,并将它们加入线程池运行
}

Dispatcher.promoteAndExecute()

这个函数将“readyAsyncCalls队列中符合要求的任务取出并加入到runningAsyncCalls队列”这个操作称为“跃迁”(promote)

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

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
        val i = readyAsyncCalls.iterator()
        //while循环遍历待执行异步队列
        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)		//将跃迁的任务加入executableCalls列表
            runningAsyncCalls.add(asyncCall)	//将跃迁的任务加入runningAsyncCalls队列
        }
        isRunning = runningCallsCount() > 0
    }

    //遍历上面跃迁的所有任务,并加入线程池执行
    for (i in 0 until executableCalls.size) {
        val asyncCall = executableCalls[i]
        asyncCall.executeOn(executorService)
    }

    return isRunning
}

AsyncCall.executeOn(executorService: ExecutorService)

fun executeOn(executorService: ExecutorService) {
  client.dispatcher.assertThreadDoesntHoldLock()

  var success = false
  try {
    executorService.execute(this)	//在线程池中执行AsyncCall的run方法,这个线程池类似于CachedExecutorService,只有“无限”多个非核心线程
    success = true
  } catch (e: RejectedExecutionException) {
    ...
  } finally {
    if (!success) {
      client.dispatcher.finished(this) //最终总会调用Dispatcher的finish方法
    }
  }
}

dispatcher.finished(this)

internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
}

private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
        //将执行完的任务移除runningAsyncCalls队列
        if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
        idleCallback = this.idleCallback
    }

    val isRunning = promoteAndExecute()		//执行完继续调用promoteAndExecute方法取出并执行后面的待执行请求

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

AsyncCall.run()

上面AsyncCall的executeOn方法中将AsyncCall对象加入到线程池并执行了下面的run方法:

override fun run() {
    threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
            val response = getResponseWithInterceptorChain()	//请求网络,获取数据
            signalledCallback = true
            responseCallback.onResponse(this@RealCall, response)	//回调接口的onResponse方法,并将response作为参数传入
        } catch (e: IOException) {
            ...
        } catch (t: Throwable) {
            ...
        } finally {
            client.dispatcher.finished(this)
        }
    }
}

通过拦截器发送请求

RealCall.getResponseWithInterceptorChain()

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)

    //创建RealInterceptorChain拦截器链,其中封装了一些请求的基本信息
    val chain = RealInterceptorChain(
        call = this,
        interceptors = 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)	//调用proceed方法获取数据
        if (isCanceled()) {
            response.closeQuietly()
            throw IOException("Canceled")
        }
        return response
    } catch (e: IOException) {
        calledNoMoreExchanges = true
        throw noMoreExchanges(e) as Throwable
    } finally {
        if (!calledNoMoreExchanges) {
            noMoreExchanges(null)
        }
    }
}

RealInterceptorChain.proceed(request: Request)

override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++
    
	...

    //实例化拦截器链中下一个拦截器,其实就是又创建了一个RealInterceptorChain对象,但更改了两个参数
    val next = copy(index = index + 1, request = request)
    //获取列表中当前的拦截器
    val interceptor = interceptors[index]	//interceptors就是上面getResponseWithInterceptorChain函数中创建RealInterceptorChain时传入的列表

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")	//看这里,当前拦截器的intercept方法中传入的是下一个拦截器next
	...

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
}

上面 val response = interceptor.intercept(next) 这行代码intercept方法中又会调用传入的下一个拦截器next,这就实现了一个调用链:

在这里插入图片描述

它们各有各的作用,职责分明。它们的作用分别如下:

  1. RetryAndFollowUpInterceptor:取消、失败重试、重定向
  2. BridgeInterceptor:把用户请求转换为 HTTP 请求;把 HTTP 响应转换为用户友好的响应
  3. CacheInterceptor:读写缓存、根据策略决定是否使用
  4. ConnectInterceptor:实现和服务器建立连接
  5. CallServerInterceptor:实现读写数据

最终的网络请求是在CallServerInterceptor的intercept方法中的,其中通过Exchange完成网络请求,它的实现类之一是Http1ExchangeCodec,请求和获取数据是通过I/O流向Socket对象写入或读取实现的。

缓存策略

HTTP的缓存

HTTP协议的缓存

OkHttp的缓存策略

上面请求流程中,中间会经过CacheInterceptor进行拦截。

OkHttp的缓存策略可以从它的CacheInterceptor的intercept方法看出:

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    val cacheCandidate = cache?.get(chain.request())	//获取待使用缓存,是一个DiskLruCache缓存,

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

    //不进行网络请求,并且没有缓存或缓存已过期,则返回504 Unsatisfiable Request错误
    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
    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()
        }
    }

    //有缓存,并且响应码为304: Not Modified,则将缓存和请求来的响应进行合并
    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()

            networkResponse.body!!.close()

            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            cache!!.trackConditionalCacheHit()
            cache.update(cacheResponse, response)
            return response.also {
                listener.cacheHit(call, it)
            }
        } else {
            cacheResponse.body?.closeQuietly()
        }
    }

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

    //缓存策略允许缓存并且响应体不为空,就将请求来的数据存入缓存
    if (cache != null) {
        if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            val cacheRequest = cache.put(response)
            return cacheWritingResponse(cacheRequest, response).also {
                if (cacheResponse != null) {
                    // This will log a conditional cache miss only.
                    listener.cacheMiss(call)
                }
            }
        }

        if (HttpMethod.invalidatesCache(networkRequest.method)) {
            try {
                cache.remove(networkRequest)
            } catch (_: IOException) {
                // The cache cannot be written.
            }
        }
    }

    return response
}

CacheInterceptor执行流程小结

  1. 如果网络不可用并且无可用的有效缓存,则返回504错误
  2. 如果禁止了网络请求,则直接使用缓存
  3. 如果没有缓存且网络请求可用,则进行网络请求
  4. 如果此时有缓存,并且网络请求返回的响应码为 304: Not Modified,说明缓存还是有效的,则合并网络响应和缓存结果,并更新缓存
  5. 如果没有缓存,则将请求回来的结果写入新的缓存中;

CacheStrategy.Factory(…).compute()

我们从 cache 拿到缓存响应后,还需要做这几件事:

  1. 看当前调用方允不允许使用缓存(判断 request.cacheControl() 的值)
  2. 允许使用缓存的话,验证这个缓存还有没有效

这个compute方法就是来完成这些事情的。
获取到缓存策略后,在CacheInterceptor的intercept方法中就会根据networkRequest和CacheResponse的情况来做出不同的处理。

/**
	上面CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute() 通过这个compute方法来获取缓存策略
*/
fun compute(): CacheStrategy {
    val candidate = computeCandidate()

    // We're forbidden from using the network and the cache is insufficient.
    //禁止使用网络并且缓存无效
    if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
        return CacheStrategy(null, null)
    }

    return candidate
}


/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
    //没有缓存的响应
    if (cacheResponse == null) {
        return CacheStrategy(request, null)
    }

    // Drop the cached response if it's missing a required handshake.
    //HTTPS请求没有进行握手
    if (request.isHttps && cacheResponse.handshake == null) {
        return CacheStrategy(request, null)
    }

    // If this response shouldn't have been stored, it should never be used as a response source.
    // This check should be redundant as long as the persistence store is well-behaved and the
    // rules are constant.
    if (!isCacheable(cacheResponse, request)) {
        return CacheStrategy(request, null)
    }

    //请求头Cache-Control为no-cache或者请求头包含If-Modified-Since或者If-None-Match,则本地缓存过期,需要服务器验证本地缓存是不是还能继续使
    val requestCaching = request.cacheControl
    if (requestCaching.noCache || hasConditions(request)) {
        return CacheStrategy(request, null)
    }

    val responseCaching = cacheResponse.cacheControl

    val ageMillis = cacheResponseAge()
    var freshMillis = computeFreshnessLifetime()

    if (requestCaching.maxAgeSeconds != -1) {
        freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
    }

    var minFreshMillis: Long = 0
    if (requestCaching.minFreshSeconds != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
    }

    var maxStaleMillis: Long = 0
    if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
    }
	//缓存过期了,但仍然可用,给相应头中添加了Warning,使用缓存
    if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        val builder = cacheResponse.newBuilder()
        if (ageMillis + minFreshMillis >= freshMillis) {
            builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
        }
        val oneDayMillis = 24 * 60 * 60 * 1000L
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
            builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
        }
        return CacheStrategy(null, builder.build())
    }

    // Find a condition to add to the request. If the condition is satisfied, the response body
    // will not be transmitted.
    val conditionName: String
    val conditionValue: String?
    
    //缓存已经过期,满足相应的条件则给请求头添加相应的参数,来验证资源是否改变
    when {
        etag != null -> {
            conditionName = "If-None-Match"
            conditionValue = etag
        }

        lastModified != null -> {
            conditionName = "If-Modified-Since"
            conditionValue = lastModifiedString
        }

        servedDate != null -> {
            conditionName = "If-Modified-Since"
            conditionValue = servedDateString
        }

        else -> return CacheStrategy(request, null) // No condition! Make a regular request.
    }

    val conditionalRequestHeaders = request.headers.newBuilder()
    conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)

    val conditionalRequest = request.newBuilder()
    .headers(conditionalRequestHeaders.build())
    .build()
    return CacheStrategy(conditionalRequest, cacheResponse)
}

失败重连

OkHttp的失败重连是通过拦截器链中的RetryAndFollowUpInterceptor来实现的,它的intercept方法如下:

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 newExchangeFinder = true
    var recoveredFailures = listOf<IOException>()
    while (true) {
        call.enterNetworkInterceptorExchange(request, newExchangeFinder)

        var response: Response
        var closeActiveExchange = true
        try {
            if (call.isCanceled()) {
                throw IOException("Canceled")
            }

            try {
                response = realChain.proceed(request)	//调用下一个拦截器
                newExchangeFinder = true
            } catch (e: RouteException) {
                if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {	//调用了recover方法
                    throw e.firstConnectException.withSuppressed(recoveredFailures)
                } else {
                    recoveredFailures += e.firstConnectException
                }
                newExchangeFinder = false
                continue	//重新进行请求
            } catch (e: IOException) {
                if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {	//调用了recover方法
                    throw e.withSuppressed(recoveredFailures)
                } else {
                    recoveredFailures += e
                }
                newExchangeFinder = false
                continue	//重新进行请求
            }
			...
        } finally {
            call.exitNetworkInterceptorExchange(closeActiveExchange)
        }
    }
}

可以看到,在请求出现异常时会调用recover方法尝试重连:

private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
): Boolean {
    //app层面禁止重连
    if (!client.retryOnConnectionFailure) return false

    //不能再发送请求体了
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    //致命异常
    if (!isRecoverable(e, requestSendStarted)) return false

    //没有更多尝试的途径了
    if (!call.retryAfterFailure()) return false

    // For failure recovery, use the same route selector with a new connection.
    //对于失败重连,使用相同的route selector和一个新的连接
    return true
}

也就是说,只要不满足上面四个if语句的条件,就会返回true,并在intercept方法中调用下一个拦截器重新请求。

请求流程图

img

参考资料

《Android进阶之光》

OkHttp 源码解析(Kotlin版)

揭秘网络框架第三篇: OkHttp 核心机制深入学习(彻底理解五个拦截器)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值