不是吧!做了 3 年 Android 还没看过 OkHttp 源码?好吧,kotlin 开源
Dispatcher.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()//得到下一个快递
//很重要的判断,如果 64 个快递车都出发了,也就没快递车了,那么将无法请求
if (runningAsyncCalls.size >= this.maxRequests) break
//发往北京的 5 辆快递车也都走了,那么继续下一个快递
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
//上面都通过了,说明有车,并且发往北京的快递车也还有,那么就离开打包车
i.remove()
//把去往北京的快递车数量+1
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
}
上面的代码很长,但其实只做了这么三件事
先判断还有没有快递车了,没有车的话什么快递都寄不出去了,跳出循环
在判断同一目的地的车辆还有没有了,目的地如果是北京,那就是判断去北京的车还有没有了,没有的话就看下一个快递。如果还有车的话,就把它装进即将出发的车里,然后计数+1,表示又占用了一辆去北京的车
再次循环,将所有要寄出去的快递寄出去
最终啊快递出发还是回到了 AsyncCall 的 executeOn 方法,我们先看看 AsyncCall 对象是什么
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
}
其它的我们都不需要关注,我们只需要关注两点,第一它接受了回调,也就是你的电话号。第二,它继承了 Runnable,这是什么?这不就是线程么,创建 Thread,实现 Runnable 接口。没错就是这样喵
上面说到,最终发送是调用了asyncCall.executeOn(executorService)
方法,那我们具体看一下这个方法
asyncCall.executeOn
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
//暂时定义发送没成功
var success = false
try {
//使用线程池来执行自身
executorService.execute(this)
//发送成功了
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
//失败回调
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
//结束
client.dispatcher.finished(this)
}
}
}
别看代码很长,其实就是做了一件事,这个 AsyncCall 把自己加入到了线程池,然后由线程池来开启线程执行自己的 run 方法。
小伙伴会问了,什么是线程池?
线程池可有讲究了,咱们本篇研究的是 OkHttp,这里就不讨论线程池了。以后的文章中会专门研究线程的种种,包括线程池。这里咱们只研究一点,就是这是一个什么样的线程池
还记得刚才 Dispatcher 类中定义了单例并且线程安全的线程池么
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
//定义了一个缓存线程池
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
这里就是定义了一个缓存的线程池,什么是缓存线程池?就是具有缓存功能的,如果一个线程工作完了并且 60s 之内又有请求过来,就复用刚才那个线程,这提高了性能。
然后交由线程池之后,线程池会开启线程执行 asyncCall 的 run 方法
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) {
//如果寄快递过程没出错,但是 try/catch 还是报异常了只能说 onResponse 中出错了,这就是用户的锅
if (signalledCallback) {
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
//失败回调
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()//取消请求
//okhttp 自己的锅,发送过程中出现了错误
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
//失败回调
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
//结束
client.dispatcher.finished(this)
}
}
}
}
走到这一步不容易,核心流程算是走完了。最终是通过 getResponseWithInterceptorChain 方法发出去快递并且得到换回来的货。
看懵了?不要紧,我们来梳理一下流程
乌拉!我要放大招了!
流程如下:
进入快递点,对应创建 okhttpClient 对象
填写快递单,对应创建 Request 对象
将快递单交给快递小哥,对应创建 RealCall 对象并将 Request 对象交给它
让小哥寄快递并告诉自己的手机号,对应调用 Call 的 enqueue 方法,并传递回调
小哥把快递交给了调度员,并且把电话号告诉了开车的老司机,然后调度员把快递放到了打包车中,对应 dispatcher 的 enqueue 方法和
调度员询问还有没有车了,以及有没有北京的车了两个判断。对应 dispatcher 的 promoteAndExecute 方法
答案是有车的话,就把老司机叫过来,给他分配一辆车,让他开车去把快递送出去。对应于线程池分配一个线程去执行 AsyncCall 的 run 方法
老司机开车一路上会遇到安检,收费站等等地方,对应于拦截器链。
老司机不管是送到了以后拿到了要换的货还是没送到,老司机都会回来,并且照着快递小哥给的电话给你打电话告诉你结果。对应于回调方法的触发
这边是主线流程,是不是相当的简单呢?和我们寄快递没有一点点差别,所以说啊,代码来源于生活,又抽象与生活。
相信有小伙伴肯定不满足于如此简单的主线流程,那么我们就进入 OkHttp 的支线剧情,看看拦截器链具体做了什么
前方高能!!
前方高能!!
前方高能!!
如果你只是想大概关注一下,可以直接拉到后面看结论,前面将要关门放狗,啊不,放源码了!
拦截器嘛,顾名思义就是拦截你的。
比如安检就是拦截你的,老司机开车进出北京市,那肯定要过安检的,毕竟安全为重。上路以后还得过收费站,中间在经过休息区等等。所以拦截器链的作用就是对请求做各种各样的操作的。
拦截器链采用的是责任链模式。看到了吗,一个设计优秀的框架必然会使用很多的经典的设计模式来保证代码的健壮性。所以我们也应该学习这种设计模式,不过今天就不学了,可以关注我,随后我会发布设计模式专栏。
在上面的代码介绍中,我们最终是走到了 getResponseWithInterceptorChain 方法,通过这个方法得到了 reponse 响应,也就是我要换的货。
我们就从这个方法开始看 ,究竟什么是责任链,什么是拦截器
@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
)
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)
}
}
}
如果自己不定义拦截器的话,OkHttp 默认的是五个拦截器。
可能有的小伙伴不太明白责任链到底是怎么运作的
很简单,当调用了 chain.proceed 方法以后即进入下一个拦截器的方法,下一个拦截器依然调用 chain.proceed 方法,重复不断的进入下一个拦截器。这就是请求层层被拦截,响应传回来的时候会被最后一个拦截器拦截,然后在层层的传回来。
如图所示
这里暂不讨论用户自定义的拦截器,我们看下默认的五个拦截器分别是做什么的
这是五个拦截器的作用。我们一个个来看
RetryAndFollowUpInterceptor
总的来说 RetryAndFollowUpInterceptor 就是负责重试和请求重定向的一个拦截器,它还额外做了一个工作就是创建了一个 ExchangeFinder 对象,这个对象就是用来管理连接池为后来的连接做准备的。
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
companion object {
//最大重试次数或者重定向次数
private const val MAX_FOLLOW_UPS = 20
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
...
while (true) {
//创建了 ExchangeFinder 对象,这个对象用于管理连接池以及
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
...
try {
//让下一个拦截器来处理
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
...
} catch (e: IOException) {
...
}
...
//后面的拦截器执行完了,拿到 Response,解析看下是否需要重试或重定向,需要则返回新的 Request
val followUp = followUpRequest(response, exchange)
//新的 Request 为空,直接返回 response
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
//如果 RequestBody 有值且只许被调用一次,直接返回 response
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,继续循环
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
}
BridgeInterceptor
桥接,负责把应用请求转换成网络请求,把网络响应转换成应用响应,就是添加各种响应头信息的。负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。 在 Request 阶段配置用户信息,并添加一些请求头。在 Response 阶段,进行 gzip 解压。
有以下几点需要注意:
开发者没有添加 Accept-Encoding 时,自动添加 Accept-Encoding: gzip
自动添加 Accept-Encoding,会对 request,response 进行自动解压
手动添加 Accept-Encoding,不负责解压缩
自动解压时移除 Content-Length,所以上层 Java 代码想要 contentLength 时为-1
自动解压时移除 Content-Encoding
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
...
val body = userRequest.body
if (body != null) {
//添加各种请求头信息
requestBuilder.header("Content-Type", contentType.toString())
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.header("Host", userRequest.url.toHostHeader())
requestBuilder.header("Connection", "Keep-Alive")
requestBuilder.header("Accept-Encoding", "gzip")
requestBuilder.header("Cookie", cookieHeader(cookies))
//让下一个拦截器来做操作
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
//这里有个坑:如果你在请求的时候主动添加了"Accept-Encoding: gzip" ,transparentGzip=false,那你就要自己解压
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
CacheInterceptor
我们知道为了节省流量和提高响应速度,Okhttp 是有自己的一套缓存机制的,CacheInterceptor 就是用来负责读取缓存以及更新缓存的。它内部的实现是使用的 OKIO 是进行读取和写入的。
@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()
//根据策略,不使用网络,又没有缓存的直接报错,并返回错误码 504。
if (networkRequest == null && cacheResponse == null) {
return ...
}
// 根据策略,不使用网络,有缓存的直接返回。
if (networkRequest == null) {
return ...
}
var networkResponse: Response? = null
try {
//让下一个拦截器进行工作
networkResponse = chain.proceed(networkRequest)
} finally {
...
}
// 接收到网络结果,如果响应 code 式 304,则使用缓存,返回缓存结果
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
...
return response.also {listener.cacheHit(call, it)}
} else {
cacheResponse.body?.closeQuietly()
}
}
//7. 读取网络结果。
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
//8. 对数据进行缓存。
if (cache != null) {
//省略判断,这里使用了 OKio 来进行缓存的写入
return cacheWritingResponse(cacheRequest, response)
}
//返回响应结果
return response
}
整个方法的流程如下所示:
读取候选缓存,
创建缓存策略,强制缓存、对比缓存等
根据策略,不使用网络,又没有缓存的直接报错,并返回错误码 504。
根据策略,不使用网络,有缓存的直接返回。
前面两个都没有返回,继续执行下一个 Interceptor,即 ConnectInterceptor。
接收到网络结果,如果响应 code 式 304,则使用缓存,返回缓存结果。
读取网络结果。
对数据进行缓存。
返回网络读取的结果。
关于缓存方面,一会放在后面专门讲缓存
ConnectInterceptor
顾名思义,这个拦截器就是用来建立连接的。
还记得在之前的 RetryAndFollowUpInterceptor 中定义的 ExchangeFinder 对象吗,它里面包含了一个连接池,用于在连接池中取得连接对象。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
//开始连接工作
val exchange = realChain.call.initExchange(chain)
//将 exchange 对象传递到下一个拦截器中
val connectedChain = realChain.copy(exchange = exchange)
//进行下一个拦截器的工作
return connectedChain.proceed(realChain.request)
}
}
这个拦截器里面的代码很少,但是工作作了不少。它真正的建立了连接。并且将 exchange 对象传递给下一个拦截器,这个对象的作用是什么呢?
exchange 对象主要就是用来发送 HTTP 请求和接受响应的。
不妨我们看看连接是如何建立的?如果你并不想关注连接是如何建立的,可以跳过这一部分,直接看结果
RealCall.initExchange
internal fun initExchange(chain: RealInterceptorChain): Exchange {
...
val exchangeFinder = this.exchangeFinder!! //在 RetryAndFollowUpInterceptor 定义的对象,里面有个连接池
val codec = exchangeFinder.find(client, chain)//查找连接,返回的是 ExchangeCodec 对象
val result = Exchange(this, eventListener, exchangeFinder, codec)//创建 Exchange 用来发送和接受请求
this.interceptorScopedExchange = result
this.exchange = result
...
return result
}
ExchangeFinder.find
fun find(client: OkHttpClient,chain: RealIntercep
torChain): ExchangeCodec {
try {
//查找可用的连接
val resultConnection = findHealthyConnection(
...
)
//创建新的 ExchangeCodec 对象
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
...
}
}
ExchangeFinder.findHealthyConnection
@Throws(IOException::class)
private fun findHealthyConnection(connectTimeout: Int,readTimeout: Int,
writeTimeout: Int,pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean):RealConnection {
while (true) {
//查找连接
val candidate = findConnection(
...
)
//确认连接是正常的
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
//如果不正常则从连接池取出
candidate.noNewExchanges()
...
}
}
ExchangeFinder.findConnection
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
if (call.isCanceled()) throw IOException("Canceled")
// 尝试重用连接
val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
if (callConnection != null) {
...
}
// 尝试从池中获取连接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
...
}
...
// 创建一个新的连接
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
//连接服务器
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
...
synchronized(newConnection) {
//添加到连接池中
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
return newConnection
}
工作很简单,就是先尝试重用连接,如果没法重用就从连接池中取一个连接,如果没取到就创建新的连接。
可以看到,链接对象最终是 RealConnection。,并且调用了 RealConnection.connect 方法来进行连接。并且经过调用,最后到了 RealConnection.connectSocket 方法。
RealConnection.connectSocket
@Throws(IOException::class)
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
...
//创建 Socket 对象
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
this.rawSocket = rawSocket
try {
//真正进行 Socket 连接
Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
} catch (e: ConnectException) {
throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
initCause(e)
}
}
try {
//okio 中的接口,用来输入,类似于 InputStream
source = rawSocket.source().buffer()
//okio 中的接口 ,用来输出,类似于 OutputStream
sink = rawSocket.sink().buffer()
} catch (npe: NullPointerException) {
if (npe.message == NPE_THROW_WITH_NULL) {
throw IOException(npe)
}
}
}
这个方法的内容也很简单,我做了部分删减。主要就是创建了 Socket 对象,并且使用 Socket 对象建立了连接,然后使用 OKio 中的接口获得输入/输出流。
ConnectInterceptor 小结
ConnectInterceptor 是一个特别重要的拦截器,在这个拦截器中真正的建立了连接,并且获得了输入输出流,为将来的输入输出进行了准备。
总的来说就是做了这么几个工作:
首先查找是否有可用的连接,没有的话就尝试是否有能重用的连接,没有的话就去连接池中找,连接池中也没有就创建新的连接对象 RealConnection
然后调用连接对象 RealConnection 的 connect 方法,最终创建了 Socket 对象用于真正的连接,然后使用了 OkIO 的的输入输出流,为输入输出做准备
最终返回 Exchange 对象,它是负责发送请求和接受响应的,而真正具体干活的是 ExchangeCodec 对象。
将 Exchange 对象放到拦截链中,让下一个拦截器进行真正的请求和响应
CallServerInterceptor
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
//写入请求头,如果是 GET 那么请求已经结束
exchange.writeRequestHeaders(request)
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
//如果熟悉 HTTP 的小伙伴应该知道 POST 请求会发送两个包,先发送请求头,获得相应为 100 后再发送请求体。
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
}
//写入请求体
requestBody.writeTo(bufferedRequestBody)
//读取响应头
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
response = if (forWebSocket && code == 101) {
...
} else {
//读取响应体
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
...
return response
}
}
为了便于观看,我删除了一些判断逻辑,但是不影响整体流程
流程大致为
先写入请求头,如果是 GET 请求的话就已经请求完毕,POST 请求的话是先发送请求头再发送请求体,会发送两个 TCP 包
然后读取响应头,接着判断过后,读取响应体。
最终将响应的结果返回,这个结果会层层的向上传递,经过上面所有的拦截器。
最终走到了我们自定义的回调处。
拦截器采用了责任链模式,层层向下请求,请求后的结果层层向上传递
首先经过了 RetryAndFollowUpInterceptor 拦截器,这个拦截器负责重试和重定向,最大重试次数为 20 次。并且在这个对象中创建了 ExchangeFinder 对象,用于管理连接池等,为随后的链接做准备
经过 BridgeInterceptor 拦截器,这个拦截器主要就是帮我们添加一些请求头的和压缩/解压缩的。在这个拦截器中表明了,如果用户自定义了 gzip 请求头,需要自行解压缩,OkHttp 则不再负责解压缩
CacheInterceptor 是负责缓存的,并且内部使用的是 OKio 进行的缓存,缓存策略下面会有讲。
ConnectInterceptor 拦截器是负责建立连接的,最终是通过 RealConnection 对象建立的 Socket 连接,并且获得了输入输出流为下一步读写做准备。RealConnection 对象的获取是优先复用的,没有复用的就从连接池里取,连接池也没的话在创建新的,并加入连接池
CallServerInterceptor 拦截器就是最终的拦截器了,它将负责数据真正的读取和写入。
首先学习一下 HTTP 缓存相关的理论知识,是实现 Okhttp 机制的基础。
HTTP 的缓存机制也是依赖于请求和响应 header 里的参数类实现的。缓存分为两种,一种是强制缓存,一种是对比缓存
强制缓存:
客户端先看有没有缓存,有缓存直接拿缓存,如果没缓存的话就请求服务器然后将结果缓存,以备下次请求。
强制缓存使用的的两个标识:
Expires:Expires 的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。到期时间是服务端生成的,客户端和服务端的时间可能有误差。
Cache-Control:Expires 有个时间校验的问题,所有 HTTP1.1 采用 Cache-Control 替代 Expires。
Cache-Control 的取值有以下几种:
评论