前言
在上一篇文章中,我们梳理了一遍Okhttp的源码,初步了解了基本执行流程,是一篇总览全局的文章。抛砖引玉,通过上一篇文章我们搞明白了Okhttp的基本工作原理,也知道Okhttp网络请求的真正执行是通过拦截器链关联的给个拦截器进行处理的,每个拦截器负责不同的功能,下面我们就来一一分析每个拦截器。
拦截器责任链
OKHttp最核心的工作是在getResponseWithInterceptorChain()
中进行的,在分析之前,我们先来了解什么事责任链模式。
责任链模式
责任链顾名思义就是由一系列的负责者构成的一个链条。
为请求创建了一个接受者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会吧相同的请求传给下一个接收者,以此类推。
拦截器流程
先看下RealCall
的getResponseWithInterceptorChain()
,拦截器的添加和连接器链的执行都在此方法中。
@Throws(IOException::class)
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
)
...
//拦截器链的执行
val response = chain.proceed(originalRequest)
...
return response
...
}
getResponseWithInterceptorChain()
中拦截器链流程:
回顾下上一篇介绍的Okhttp五大默认拦截器:
-
RetryAndFollowUpInterceptor
:第一个接触到请求,最后接触到响应(U型调用)的默认拦截器;负责处理错误重试和重定向 。 -
BridgeInterceptor
:补全请求,并对响应进行额外处理。 -
CacheInterceptor
:请求前查询缓存,获得响应并判断是否需要缓存。 -
ConnectInterceptor
:与服务器完成TCP连接。 -
CallServerInterceptor
: 与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)。
RetryAndFollowUpInterceptor(重试、重定向)
如果没有添加应用拦截器,那么第一个拦截器就是RetryAndFollowUpInterceptor
,主要作用就是:连接失败后进行重试、对请求结果跟进后进行重定向。接着看下它的Intercept
方法:
@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 {
//继续执行下一个Interceptor
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
}
// 清除下游拦截器的其他请求标头、cookie 等
response = response.newBuilder()
.request(request)
.priorResponse(priorResponse?.stripBody())
.build()
val exchange = call.interceptorScopedExchange
//跟进结果,主要作用是根据响应码处理请求,返回Request不为空时则进行重定向处理-拿到重定向的request
val followUp = followUpRequest(response, exchange)
//followUp为空,不需要重试,直接返回
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()
//最多重试20次
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
//赋值,以便再次请求
request = followUp
priorResponse = response
} finally {
// 请求没成功,释放资源。
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
重试
可以看到请求阶段发生了IOException(老版本还会捕获RouteException),会通过recover
方法判断是否能够进行重试,若返回true
,则表示允许重试。
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// 应用层禁止重试,就不重试
if (!client.retryOnConnectionFailure) return false
// 不能再次发送请求,就不重试(requestSendStarted只在http2的io异常中为true,先不管http2)
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// 发生的异常是致命的,就不重试
if (!isRecoverable(e, requestSendStarted)) return false
// 没有路由可以尝试,就不重试
if (!call.retryAfterFailure()) return false
return true
}
接下来看一下isRecoverable
中判断了哪些异常。
private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
// 出现协议异常,不能重试
if (e is ProtocolException) {
return false
}
// 如果发生中断( requestSendStarted认为它一直为false(不管http2)),不要恢复;但如果连接到路由器超时(socket超时),判定可以重试
if (e is InterruptedIOException) {
return e is SocketTimeoutException && !requestSendStarted
}
//SSL握手异常中,证书出现问题,不能重试
if (e is SSLHandshakeException) {
if (e.cause is CertificateException) {
return false
}
}
//SSL握手未授权异常 不能重试
if (e is SSLPeerUnverifiedException) {
return false
}
return true
}
- 协议异常:例如你的请求或者服务器的响应本身就存在问题,没有按照http协议来定义数据,直接无法重试。
- 超时异常:可能由于网络波动造成了Socket管道的超时,如果有下一个路由的话,尝试下一个路由。
- SSL证书异常/SSL验证失败异常:证书验证失败或者缺少证书,无法重试。
简单总结一下,首先在用户未禁止重试的前提下,如果出现了某些异常,则会在isRecoverable
中进行判断,进过上面一系列判断后,如果允许进行重试,就会再检查当前是否有可用路由进行连接重试。比如DNS对域名解析后可能会返回多个IP,在一个IP失败后,尝试另一个IP进行重试。
重定向
如果realChain.proceed没有发生异常,返回了结果response,就会使用followUpRequest方法跟进结果并构建重定向request。 如果不用跟进处理(例如响应码是200),则返回null。看下followUpRequest方法:
@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
// 407 客户端使用了HTTP代理服务器,在请求头中添加 “Proxy-Authorization”,让代理服务器授权
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
// 401 需要身份验证 有些服务器接口需要验证使用者身份 在请求头中添加 “Authorization”
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
//308 永久重定向 ,307 临时重定向,300,301,302,303
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// 408 客户端请求超时
HTTP_CLIENT_TIMEOUT -> {
// 408 算是连接失败了,所以判断用户是不是允许重试
if (!client.retryOnConnectionFailure) {
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
// 如果是本身这次的响应就是重新请求的产物同时上一次之所以重请求还是因为408,那我们这次不再重请求了
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
return null
}
// 如果服务器告诉我们了 Retry-After 多久后重试,那框架不管了。
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
// 503 服务不可用 和408差不多,但是只在服务器告诉你 Retry-After:0(意思就是立即重试) 才重请求
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request
}
return null
}
HTTP_MISDIRECTED_REQUEST -> {
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
主要就是根据响应码判断如果需要重定向,如果此方法返回空,那就表示不需要再重定向了,直接返回响应;但是如果返回非空,那就要重新请求返回的Request
,但是需要注意的是,followup`在拦截器中定义的最大次数为20次。
总结
- 如果没有添加应用拦截器,那么
RetryAndFollowUpInterceptor
就是第一个拦截器,主要功能是判断是否需要重试和重定向。 - 如果请求阶段发生了IOException,就会通过
recover
方法判断是进行连接重试。 - 重定向发生在重试的判定之后,如果不满足重试的条件,还需要进一步调用
followUpRequest
根据Response
的响应码(当然,如果直接请求失败,Response
都不存在就会抛出异常)判断是否重定向;followup
最大发生次数20次。
BridgeInterceptor(桥接拦截器)
BridgeInterceptor
,连接应用程序和服务器的桥梁,我们发出的请求会经过它的处理才能发给服务器,比如设置请求内容的长度,编码,gzip压缩,cookie等;获取响应后保存cookie、解压等操作。相对比较简单。
首先,chain.proceed()执行前,对请求头进行补全,补全请求头如下:
请求头 | 说明 |
---|---|
Content-Type |
请求体类型,如:application/x-www-form-urlencoded
|
Content-Length /Transfer-Encoding
|
请求体解析方式 |
Host |
请求的主机站点 |
Connection: Keep-Alive |
保持长连接 |
Accept-Encoding: gzip |
接受响应支持gzip压缩 |
Cookie |
cookie身份辨别 |
User-Agent |
请求的用户信息,如:操作系统、浏览器等 |
在补全了请求头后交给下一个拦截器处理,得到响应后,主要干两件事情:
- 先把响应header中的cookie存入cookieJar(如果有),在下次请求则会读取对应的数据设置进入请求头,默认的
CookieJar
不提供实现,需要我们在初始化OkhttpClient时配置我们自己的cookieJar。 - 如果使用gzip返回的数据,则使用
GzipSource
包装便于解析。
总结
- 对用户构建的
Request
进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的Request
。 - 将符合网络请求规范的Request交给下一个拦截器处理,并获取
Response
。 - 如果响应体经过了gzip压缩,那就需要解压,再构建成用户可用的
Response
并返回。
CacheInterceptor(缓存拦截器)
CacheInterceptor
,在发出请求前,先判断是否命中缓存,如果命中则可以不请求,直接使用缓存的响应(默认只会对Get请求进行缓存);如果未命中则进行网络请求,并将结果缓存,等待下次请求被命中。
先来看下CacheInterceptor
的intercept
方法代码:
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 用request的url 从缓存中获取Response作为候选(CacheStrategy决定是否使用)
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// 根据request、候选Response 获取缓存策略。
// 缓存策略 将决定是否使用缓存:strategy.networkRequest为null,不适用网络;
// strategy.cacheResponse为null,不使用缓存
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
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)
}
}
// 如果不用网络,cacheResponse肯定不为空了,那么就使用缓存了,结束了
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)
}
//到这里,networkRequest != null (cacheResponse可能为null,也可能!null)
//networkRequest != null,就要进行网络请求了, 所以拦截器链就继续往下处理了
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,表示服务端资源没有修改,那么就结合网络响应和缓存响应,然后更新缓存->返回->结束
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))//结合header
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)//请求事件
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)//接受事件
.cacheResponse(cacheResponse.stripBody())
.networkResponse(networkResponse.stripBody())
.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 {
//如果是非304,说明服务端资源有更新,就关闭缓存body
cacheResponse.body.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(cacheResponse?.stripBody())
.networkResponse(networkResponse.stripBody())
.build()
if (cache != null) {
//网络响应可缓存(请求和响应的头Cache-Control都不是'no-store')
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// 写入缓存
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
//Okhttp默认只会对get请求进行缓存,因为get请求的数据一般是比较持久的,而post一般是交互操作,没太大意义进行缓存
//不是get请求就移除缓存
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
大概梳理一下步骤:
从缓存中获取对应请求的响应缓存
-
创建
CacheStrategy
,创建时会判断是否能够使用缓存,在CacheStrategy
中有两个成员:networkRequest
和cacheResponse
。他们有如下组合进行判断:networkRequest cacheResponse 说明 Null Null 网络请求、缓存 都不能用,okhttp直接返回504 Null Not Null 直接使用缓存 Not Null Null 向服务器发起请求 Not Null Not Null 向服务器发起请求,若得到响应码为304(无修改),则更新缓存响应并返回 交给下一个责任链继续处理
后续工作,返回304则用缓存的响应;否则使用网络响应并缓存本次响应(只缓存Get请求的响应)
缓存拦截器中判断使用缓存或者请求服务器都是通过CacheStrategy
判断。分析CacheStrategy
前我们先来了解下Http的缓存机制,以便更好的理解:
首次请求:
第二次请求:
上面两张图是对http缓存机制的一个总结:根据 缓存是否过期、过期后是否有修改 来决定 请求是否使用缓存。详细内容可看这篇文章: 彻底弄懂HTTP缓存机制及原理。
回过头我们再来看CacheStrategy
,它的生成代码看起来很简单,只有一行代码:
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
把请求request
、获选缓存cacheCandidate
传入工厂类Factory
,然后调用compute()方法。
class Factory(
private val nowMillis: Long,
internal val request: Request,
private val cacheResponse: Response?
) {
init {
if (cacheResponse != null) {
/*获取候选缓存的请求时间、响应时间,从header中获取 过期时间、修改时间、资源标记等(如果有)*/
this.sentRequestMillis = cacheResponse.sentRequestAtMillis
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
val headers = cacheResponse.headers
for (i in 0 until headers.size) {
val fieldName = headers.name(i)
val value = headers.value(i)
when {
fieldName.equals("Date", ignoreCase = true) -> {
servedDate = value.toHttpDateOrNull()
servedDateString = value
}
fieldName.equals("Expires", ignoreCase = true) -> {
expires = value.toHttpDateOrNull()
}
fieldName.equals("Last-Modified", ignoreCase = true) -> {
lastModified = value.toHttpDateOrNull()
lastModifiedString = value
}
fieldName.equals("ETag", ignoreCase = true) -> {
etag = value
}
fieldName.equals("Age", ignoreCase = true) -> {
ageSeconds = value.toNonNegativeInt(-1)
}
}
}
}
}
/** Returns a strategy to satisfy [request] using [cacheResponse]. */
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
}
}
Factory
构造方法内,获取了候选响应的请求时间、响应时间、过期时长、修改时间、资源标记等。
compute
方法内部先调用了computeCandidate()
获取到缓存策略实例,先跟进到computeCandidate
方法中:
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
// 没有缓存——进行网络请求
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
//https请求,但没有握手——进行网络请求
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// 网络响应不可缓存(请求或响应头Cache-Control是'no-store')——进行网络请求
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
//请求头Cache-Control是no-cache或者请求头有"If-Modified-Since"或"If-None-Match"——进行网络请求
//意思是不使用缓存或者请求手动添加了头部"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()))
}
//可接受缓存的最小剩余时间(min-fresh表示客户端不愿意接收剩余有效期<=min-fresh的缓存)
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
//可接受缓存的最大过期时间(max-stale指令表示了客户端愿意接收一个过期了的缓存,例如:过期了1小时还可以用)
var maxStaleMillis: Long = 0
// 第一个判断:是否要求必须去服务器验证资源状态
// 第二个判断:获取max-stale值,如果不等于-1,说明缓存过期后还能使用指定的时长
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
//如果响应头没有要求忽略本地缓存并且整合后的缓存年龄小于整合后的过期时间,那么缓存就可以用
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
//没有满足“可接受的最小剩余有效时间”,加个110警告
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
//isFreshnessLifetimeHeuristic表示没有过期时间且缓存的年龄大于一天,就加个113警告
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
return CacheStrategy(null, builder.build())
}
//到这里,说明缓存是过期的
//然后找缓存里的Etag、lastModified、servedDate
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()
//conditionalRequest(条件网络请求):有缓存但过期了,去请求网络询问服务器是否能用,能用侧返回304,不能则正常执行网络请求
return CacheStrategy(conditionalRequest, cacheResponse)
}
同样经过一系列判断:
- 没有缓存、https请求但没有握手、手动设置不缓存、忽略缓存或者手动配置缓存过期,都是直接进行网络请求。
- 1中都不满足时,如果缓存没有过期,则使用缓存(可能会添加警告)。
- 1中都不满足时,如果缓存过期了,但响应头有Etag,Last-Modified,Date,就添加这些header进行条件网络请求。
- 1中都不满足时,如果缓存过期了,且响应头没有设置Etag,Last-Modified,Date,就进行常规网络请求。
回头接着再看compute()
方法:
/** Returns a strategy to satisfy [request] using [cacheResponse]. */
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
}
}
computeCandidate()
获取到缓存策略候选后,又进行了一个判断:使用网络请求 但是 原请求配置了只能使用缓存,按照上面的分析,此时即使有缓存,也是过期的缓存,所以又new了实例,两个值都为null。
到这里okhttp的缓存机制源码就看完了。okhttp的缓存机制是符合开头http的缓存机制那两张图的,只是增加很多细节判断。
另外注意,缓存的读写是通过 InternalCache
完成的。InternalCache
是在创建CacheInterceptor
实例时 用client.internalCache()
作为参数传入。而InternalCache
是okhttp内部使用,类似一个代理,InternalCache
的实例是 类的属性。Cache
是我们初始化 OkHttpClient
时传入的。所以如果没有传入Cache
实例是没有缓存功能的。
val client = OkHttpClient.Builder()
.cache(Cache(getExternalCacheDir(),500 * 1024 * 1024))
.build()
缓存的增删改查,Cache
是通过okhttp内部的DiskLruCache
实现的,原理和jakewharton的DiskLruCache
是一致的,这里就不再叙述。
结语
本篇文章讲解了三个拦截器工作原理,分别是:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor。由于篇幅原因,剩下两个拦截器:ConnectInterceptor、CallServerInterceptor,放在下一篇进行讲解。