OkHttp-源码解析(一)
前言
OkHttp
是一组处理 HTTP
网络请求的依赖库,由 正方形
该公司设计和开发并开源,目前可在 爪哇
和 科特林
用于。用于 安卓应用
说, OkHttp
现在它几乎占据了所有的网络请求操作,我们通过了解它的内部实现原理,可以更好地扩展、封装和优化它的功能。
本文基于 OkHttp 4.11.0
分析
OkHttp
具体使用可以参考 官方网站 ,这里不做具体解释,本文主要来自 OkHttp
从使用 , 开始详细分析 OkHttp
实现原理。
介绍
OkHttp通过socket和okio交换数据
val 客户端 = OkHttpClient()
val 请求 = Request.Builder()。 get().url("http://xxx").build()
client.newCall(request).enqueue(object : Callback {
覆盖 fun onFailure (调用: Call, e: IOException) {
}
覆盖有趣的onResponse(调用:调用,响应:响应){
}
})
复制代码
从上面我们可以看到几个 OkHttp
重要部分
- OkHttp客户端:
好的http
请求的执行客户端 - Request:通过 Builder 设计模式构造的请求对象
- 通话:已通过
client.newCall
生成的请求执行对象,执行时执行
只有这样才会执行实际的网络请求 - 响应:通过网络发出请求后,服务器返回的信息就在其中。包含返回的状态码,以及响应消息的正文
响应体
- 拦截器用户定义的拦截器在重试拦截器之前执行
- retryAndFollowUpInterceptor 重试拦截器
- BridgeInterceptor 为网桥建立拦截器,主要是为网络请求添加各种必要的参数。如 Cookie、Content-type
- CacheInterceptor 缓存拦截器,主要是在网络请求时根据返回码处理缓存。
- ConnectInterceptor 连接拦截器,主要是从连接池中寻找可重用的socket连接。
- networkInterceptors 用户定义的网络拦截器,在 CallServerInterceptor(执行网络请求拦截器)之前运行。
- CallServerInterceptor 实际执行的是网络请求的逻辑。
实施过程
OkHttpClient
类生成器构造函数(){
//Okhttp请求调度器是整个OkhttpClient的执行核心
内部 var 调度程序:Dispatcher = Dispatcher()
//Okhttp连接池,但会将任务委托给RealConnectionPool处理
内部变量连接池:连接池 = 连接池()
// 用户定义的拦截器,在重试拦截器之前执行
内部 val 拦截器:MutableList<Interceptor> = 可变列表()
//用户自定义的网络拦截器,在CallServerInterceptor(执行网络请求拦截器)之前运行。
内部验证网络拦截器:可变列表<Interceptor>= 可变列表()
// 进程监听
内部 var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
//连接失败时是否重新连接
内部 var retryOnConnectionFailure = true
//服务器认证设置
内部 var 验证器:Authenticator = Authenticator.NONE
//是否重定向
内部变量 followRedirects = true
//是否重定向到https
内部变量 followSslRedirects = true
//cookie持久化设置
内部变量 cookieJar:CookieJar = CookieJar.NO_COOKIES
//缓存设置
内部变量缓存:缓存? =空
//DNS设置
内部变量 dns:Dns = Dns.SYSTEM
//代理设置
内部 var 代理:代理? =空
内部 var proxySelector: ProxySelector? =空
内部 var proxyAuthenticator: Authenticator = Authenticator.NONE
//默认socket连接池
内部 var socketFactory: SocketFactory = SocketFactory.getDefault()
//https的socket连接池
内部变量 sslSocketFactoryOrNull:SSLSocketFactory? =空
//用来信任Https证书的对象
内部变量 x509TrustManagerOrNull:X509TrustManager? =空
内部 var connectionSpecs: 列表<ConnectionSpec>= DEFAULT_CONNECTION_SPECS
//http协议集合
内部 var 协议:列表<Protocol>= DEFAULT_PROTOCOLS
//https 检查主机
内部 var hostnameVerifier : HostnameVerifier = OkHostnameVerifier
内部 var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
内部 var certificateChainCleaner:CertificateChainCleaner? =空
//请求超时
内部 var callTimeout = 0
//连接超时
内部变量 connectTimeout = 10_000
//读取超时
内部变量 readTimeout = 10_000
//写超时
内部变量 writeTimeout = 10_000
内部变量 pingInterval = 0
内部变量 minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
内部 var routeDatabase:RouteDatabase? =空
}
复制代码
client.newCall(请求):
覆盖 fun newCall (request: Request): Call = RealCall(this, request, forWebSocket = false)
复制代码
这里生成一个RealCall对象,第三个参数是否为websocket,默认为false。
拿到RealCall对象后,有两种方式发送网络请求:
- execute() :这种方法很少使用
- enqueue() :该方法将每个请求放入队列中,并按顺序一一消费。
RealCall.enqueue()
覆盖有趣的入队 (responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "已经执行" }
调用开始()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
私人乐趣 callStart () {
this.callStackTrace = 平台。 get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(这个)
}
复制代码
以下是主要步骤
- 首先回调eventListener的callStart()方法,
- 然后创建 AsyncCall 对象并将 responseCallback 传入。
- 最后是 Dispatcher 的 enqueue() 方法。
Dispatcher.enqueue()
类调度程序构造函数(){
……
// 按运行顺序为异步调用准备队列
私有 val readyAsyncCalls = ArrayDeque<AsyncCall> ()
//正在运行的异步请求队列,包括已取消但尚未完成的AsyncCall
私有 val runningAsyncCalls = ArrayDeque<AsyncCall> ()
//正在运行的同步请求队列,包括已取消但尚未完成的 RealCall
私人 val runningSyncCalls = ArrayDeque<RealCall> ()
……
内部有趣的入队(调用:AsyncCall){
同步(这个){
readyAsyncCalls.add(调用)
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
促进和执行()
}
私人乐趣 findExistingCallWithHost (host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) 返回existingCall
}
for (readyAsyncCalls 中的existingCall) {
if (existingCall.host == host) 返回existingCall
}
返回空
}
复制代码
-
首先将 AsyncCall 添加到 readyAsyncCalls 队列中。
-
然后使用findExistingCallWithHost查找runningAsyncCalls和readyAsyncCalls中是否有同一主机的AsyncCall,如果有,调用call.reuseCallsPerHostFrom()进行复用
-
最后调用promoteAndExecute()通过线程池执行队列中的AsyncCall对象
私人乐趣promoteAndExecute():布尔{
this.assertThreadDoesntHoldLock()val executableCalls = mutableListOf
()
//判断请求是否正在执行
val isRunning:布尔值
// 锁确保线程安全
同步(这个){
// 遍历 readyAsyncCalls 队列
val i = readyAsyncCalls.iterator()
而(i.hasNext()){
val asyncCall = i.next()
//runningAsyncCalls的数量不能大于最大并发请求数64
if (runningAsyncCalls.size >= this.maxRequests) break // 最大容量。
//同一Host的最大数量为5
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 主机最大容量。
//从 readyAsyncCalls 队列中移除并添加到 executableCalls 和 runningAsyncCalls
i.remove()
asyncCall.callsPerHost.incrementAndGet()
可执行调用.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
// 遍历 executableCalls 执行 asyncCall
for (i in 0 until executableCalls.size) {
val asyncCall = 可执行调用[i]
asyncCall.executeOn(executorService)
}返回是运行
}
复制代码
这里遍历readyAsyncCalls队列,判断runningAsyncCalls数是否大于最大并发请求数64,
判断同一个Host的请求是否大于5,然后从readyAsyncCalls队列中取出AsyncCall,加入executableCalls和runningAsyncCalls,遍历executableCalls执行asyncCall。
内部内部类 AsyncCall(
private val responseCallback: 回调
) : 可运行 {
……
有趣的 executeOn (executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var 成功 = 假
尝试 {
//执行AsyncCall的run方法
executorService.execute(这个)
成功=真
} 捕捉(e:RejectedExecutionException){
val ioException = InterruptedIOException("执行者被拒绝")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(这个@RealCall,ioException)
} 最后 {
如果(!成功){
client.dispatcher.finished(this) // 此调用不再运行!
}
}
}
覆盖有趣的运行(){
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
尝试 {
//执行OkHttp的拦截器获取响应对象
val 响应 = getResponseWithInterceptorChain()
signalledCallback = 真
// 通过该方法回调响应对象
responseCallback.onResponse(这个@RealCall,响应)
} 捕捉(e:IOException){
如果(信号回调){
平台。 get().log("${toLoggableString()} 回调失败", Platform.INFO, e)
} 别的 {
//遇到IO异常回调失败方法
responseCallback.onFailure(这个@RealCall, e)
}
} 捕捉(t:可投掷){
//遇到其他异常时的失败方法
取消()
如果(!signalledCallback){
val cancelledException = IOException("由于 $t 而取消")
cancelledException.addSuppressed(t)
responseCallback.onFailure(这个@RealCall, cancelledException)
}
扔
} 最后 {
client.dispatcher.finished(这个)
}
}
}
}
复制代码
这里可以看到AsyncCall是一个Runable对象,线程执行会调用该对象的run方法,而executeOn方法就是执行runable对象。
在run方法中,主要执行以下步骤:
-
调用 getResponseWithInterceptorChain() 执行 OkHttp 拦截器并获取响应对象
-
调用responseCallback的onResponse方法回调Response对象
-
如果遇到IOException,调用responseCallback的onFailure方法回调异常
-
如果遇到其他异常,调用cancel()方法取消请求,调用responseCallback的onFailure方法回调异常
-
调用Dispatcher的finished方法结束执行
@Throws(IOException::class)
内部乐趣 getResponseWithInterceptorChain(): Response {
// 拦截器集合
val 拦截器 = mutableListOf()
//添加用户自定义集合
拦截器 += client.interceptors
拦截器 += RetryAndFollowUpInterceptor(客户端)
拦截器 += BridgeInterceptor(client.cookieJar)
拦截器 += CacheInterceptor(client.cache)
拦截器 += 连接拦截器
//如果不是sockect,添加newtwork拦截器
如果(!forWebSocket){
拦截器 += client.networkInterceptors
}
拦截器 += CallServerInterceptor(forWebSocket)
//构建拦截器责任链
val 链 = RealInterceptorChain(
打电话=这个,
拦截器=拦截器,
索引 = 0,
交换=空,
请求=原始请求,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)var 称为NoMoreExchanges = false
尝试 {
//执行拦截器责任链获取Response
val response = chain.proceed(originalRequest)
// 如果取消则抛出异常
if (isCanceled()) {
response.closeQuietly()
抛出 IOException(“取消”)
}
返回响应
} 捕捉(e:IOException){
称为NoMoreExchanges = true
将 noMoreExchanges(e) 抛出为 Throwable
} 最后 {
如果(!称为NoMoreExchanges){
noMoreExchanges(空)
}
}
}
复制代码
这里主要执行以下步骤
- 首先构建一个可变拦截器集并添加所有拦截器。如果是websocket,则不会添加networkInterceptor拦截器。添加这个拦截器集的顺序也是OkHttp拦截器的执行顺序。
- 构建一个 RealInterceptorChain 对象并包装所有拦截器
- 调用RealInterceptorChain的proceed方法获取Response对象
简单总结:这里使用责任链设计模式,构造RealInterceptorChain对象,然后执行proceed方法获取响应对象
有趣的界面拦截器{
//拦截方法
@Throws(IOException::class)
有趣的拦截(链:链):响应
伴随对象{
内联运算符 fun 调用(crossinline 块:(链:链)-> 响应):拦截器 =
拦截器{块(它)}
}
接口链{
//获取请求对象
有趣的请求():请求
//处理请求得到Response
@Throws(IOException::class)
有趣的进行(请求:请求):响应
……
}
}
复制代码 类 RealInterceptorChain(
内部验证调用:RealCall,
私有 val 拦截器:列表<Interceptor>,
私有 val 索引:Int,
内部 val 交换:交换?,
内部验证请求:请求,
内部 val connectTimeoutMillis: Int,
内部 val readTimeoutMillis: Int,
内部 val writeTimeoutMillis: Int
) : Interceptor.Chain {
内部有趣的副本(索引:Int = this.index,exchange:Exchange ? = this.exchange,request:Request = this.request,connectTimeoutMillis:Int = this.connectTimeoutMillis,readTimeoutMillis:Int = this.readTimeoutMillis,writeTimeoutMillis:Int = this .writeTimeoutMillis ) = RealInterceptorChain(调用,拦截器,索引,交换,请求,connectTimeoutMillis,
readTimeoutMillis, writeTimeoutMillis)
……
覆盖有趣的调用():调用=调用
覆盖有趣的请求():请求=请求
@Throws(IOException::class)
覆盖有趣的继续(请求:请求):响应{
检查(索引<拦截器.大小)
……
val next = 复制(索引 = 索引 + 1,请求 = 请求)
val拦截器=拦截器[索引]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
“拦截器$拦截器返回null”)
……
返回响应
}
}
复制代码
看这里看到copy()方法创建了一个RealInterceptorChain()对象,但是需要注意的是创建对象的时候索引是index = index + 1,这样索引index对应的拦截器就会是执行,后面会不断调用。一个拦截器,直到返回响应对象,即chain.proceed(originalRequest)结束。
拦截器
让我们仔细看看拦截器
RetryAndFollowUp拦截器
主要处理以下问题:
-
1.异常,或协议重试(408客户端超时,权限问题,503服务暂时未处理,retry-after为0)
-
2.重定向
-
3、重试次数不能超过20次。
@Throws(IOException::class)
覆盖有趣的拦截(链:拦截器。链):响应{
val realChain = 链作为 RealInterceptorChain
var 请求 = 链请求
val call = realChain.call
变量后续计数 = 0
是先前响应:响应? = 零
var newExchangeFinder = true
var 恢复失败 = listOf()
而(真){
//这里会新建一个ExchangeFinder,ConnectInterceptor会用到
call.enterNetworkInterceptorExchange(request, newExchangeFinder)var响应:响应
var closeActiveExchange = true
尝试 {
if (call.isCanceled()) {
抛出 IOException(“取消”)
}尝试 {
response = realChain.proceed(request)
newExchangeFinder = true
} 捕捉(e:RouteException){
// 尝试通过路由连接失败。不会发送请求。
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
抛出 e.firstConnectException.withSuppressed(recoveredFailures)
} 别的 {
恢复失败 += e.firstConnectException
}
newExchangeFinder = false
继续
} 捕捉(e:IOException){
// 尝试与服务器通信失败。请求可能已经发送。
if (!recover(e, call, request, requestSendStarted = e ! is ConnectionShutdownException)) {
抛出 e.withSuppressed(recoveredFailures)
} 别的 {
恢复失败 += e
}
newExchangeFinder = false
继续
}//尝试关联之前的响应,注意:body为null
if (priorResponse != null) {
响应 = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(空)
。建造())
。建造()
}val exchange = call.interceptorScopedExchange
//会根据responseCode判断,构建新请求返回重试或重定向
val followUp = followUpRequest(响应,交换)如果(跟进==空){
if (exchange != null && exchange.isDuplex) {
调用.timeoutEarlyExit()
}
closeActiveExchange = 假
返回响应
}
//如果请求体是一次性的,不需要重试
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = 假
返回响应
}response.body?.closeQuietly()
//最大重试次数,不同浏览器不同,例如:Chrome是21,Safari是16
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("太多后续请求:$followUpCount")
}请求 = 跟进
先验响应 = 响应
} 最后 {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
复制代码 -
1.调用RealCall的enterNetworkInterceptorExchange方法实例化一个
ExchangeFinder
在 RealCall 对象中。 -
2、执行RealCall的proceed方法,进入下一个拦截器,进行下一个请求处理。
-
3、如果出现路由异常,使用recover方法检查当前连接是否可以重试。如果无法重试,则会抛出异常并离开当前循环。
私人乐趣恢复(e:IOException,调用:RealCall,userRequest:Request,requestSendStarted:Boolean):Boolean {
// 禁用重新连接
如果 (!client.retryOnConnectionFailure) 返回 false// 请求体不能再次发送
if (requestSendStarted && requestIsOneShot(e, userRequest)) 返回 false// 致命异常
if (!isRecoverable(e, requestSendStarted)) 返回 false// 不再需要重新连接的线路
如果 (!call.retryAfterFailure()) 返回 false// 对于故障回复,使用与新连接相同的路由器
返回真
}
复制代码
桥接器
主要处理以下问题:
-
主要是在header中加入一些Content-Type、Content-Length、Host等数据。
-
拿到数据后,对数据进行处理,判断是否为gzip,解压数据。
@Throws(IOException::class)
覆盖有趣的拦截(链:拦截器。链):响应{
//获取原始请求数据
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
//重建请求,添加一些必要的请求头信息
val body = userRequest.body
如果(正文!= null){
val contentType = body.contentType()
如果(内容类型!= null){
requestBuilder.header("内容类型", contentType.toString())
}val contentLength = body.contentLength()
如果(内容长度!= - 1L){
requestBuilder.header("内容长度", contentLength.toString())
requestBuilder.removeHeader("传输编码")
} 别的 {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("内容长度")
}
}if (userRequest.header("Host") == null) {
requestBuilder.header("主机", userRequest.url.toHostHeader())
}if (userRequest.header("Connection") == null) {
requestBuilder.header(“连接”,“保持活动”)
}var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
透明Gzip = true
requestBuilder.header("接受编码", "gzip")
}val cookies = cookieJar.loadForRequest(userRequest.url)
如果(cookies.isNotEmpty()){
requestBuilder.header("Cookies", cookieHeader(cookies))
}if (userRequest.header("User-Agent") == null) {
requestBuilder.header("用户代理", userAgent)
}
//执行下一个拦截器
val networkResponse = chain.proceed(requestBuilder.build())cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
//新建一个responseBuilder,目的是将原来的请求数据构建到response中
val responseBuilder = networkResponse.newBuilder()
.request(用户请求)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("内容编码")
.removeAll("内容长度")
。建造()//修改响应头信息,去掉Content-Encoding、Content-Length信息
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("内容类型"
//修改响应体信息
responseBuilder.body(RealResponseBody(contentType, - 1L, gzipSource.buffer()))
}
}返回 responseBuilder.build()
}
复制代码
- 设置标头的 Content-Type。解释内容类型是什么
- 如果contentLength大于等于0,设置header的Content-Length(表示内容大小是多少);否则,设置header的Transfer-Encoding为chunked(表示传输编码为chunked传输)
- 如果Host不存在,则在header中设置Host(出现在Http 1.1之后,可以通过同一个URL访问不同的主机,从而实现服务器虚拟服务器的负载均衡。如果1.1之后不设置,则会返回 404)。
- 如果Connection不存在,将header中的Connection设置为Keep-Alive(代表连接状态需要保持活跃)
- 如果 Accept-Encoding 和 Range 为空,则必须设置 Accept-Encoding 为 gzip(表示请求会被 gzip 压缩)
- 从 CookieJar 缓存中获取设置到标头的 cookie
- 如果 User-Agent 为空,则将 User-Agent 设置为 header
缓存拦截器
用户通过 OkHttpClient.cache
配置缓存,缓存拦截器通过 缓存策略
判断是使用网络还是缓存构建 回复
.
@Throws(IOException::class)
覆盖有趣的拦截(链:Interceptor.Chain):响应{
val 调用 = 链 .call()
//通过request从OkHttpClient.cache中获取缓存
val cacheCandidate = 缓存? .get(chain.request())
val 现在 = 系统 .currentTimeMillis()
//创建缓存策略
val strategy = CacheStrategy .Factory(现在,chain.request(),cacheCandidate) .compute()
//为空表示不使用网络,否则表示使用网络
val networkRequest = 策略 .networkRequest
//如果为空,表示不使用缓存,否则表示使用缓存
val cacheResponse = 策略 .cacheResponse
// 跟踪网络和缓存使用情况
缓存? .trackResponse(策略)
val listener =(调用为?RealCall)? .eventListener ?: EventListener.NONE
//有缓存但不适用,关闭它
if (cacheCandidate != null && cacheResponse == null) {
缓存候选.body? .closeQuietly()
}
//如果网络被禁用,但是缓存是空的,构造一个代码为504的响应并返回
if (networkRequest == null && cacheResponse == null) {
返回响应 .Builder()
.request(链.request())
.protocol(协议.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("无法满足的请求(仅在缓存时)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(- 1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build() .also {
监听器 .satisfactionFailure(call, it)
}
}
//如果我们禁用网络不使用网络,并且有缓存,直接根据缓存内容构建并返回响应
if (networkRequest == null) {
返回缓存响应!! .newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build() .also {
监听器 .cacheHit(调用,它)
}
}
//添加监听到缓存
如果(缓存响应!= null){
监听器 .cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
监听器 .cacheMiss(调用)
}
是网络响应:响应? = 零
尝试 {
//执行下一个拦截器
networkResponse = 链 .proceed(networkRequest)
} 最后 {
//捕获I/O或其他异常,请求失败,networkResponse为空,有缓存时不暴露缓存内容
if (networkResponse == null && cacheCandidate != null) {
//否则关闭缓存响应体
缓存候选.body? .closeQuietly()
}
}
//如果有缓存
如果(缓存响应!= null){
//并且当网络返回的响应码为304时,使用缓存的内容构造一个新的Response返回。
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse .newBuilder()
.headers(组合(cacheResponse.headers,networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
。建造()
网络响应.body!! 。关()
隐!! .trackConditionalCacheHit()
缓存 .update(缓存响应,响应)
返回响应 .also {
监听器 .cacheHit(调用,它)
}
} 别的 {
//否则关闭缓存响应体
缓存响应.body? .closeQuietly()
}
}
//构造网络请求的响应
val 响应 = 网络响应!! .newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
。建造()
//如果缓存不为空,即用户已经在OkHttpClient中配置了缓存,则上一步新建的网络请求响应会保存在缓存中
如果(缓存!= null){
//根据响应的代码、header和CacheControl.noStore判断是否可以缓存
if (response.promisesBody() && CacheStrategy .isCacheable(response, networkRequest)) {
// 将响应存储在缓存中
val cacheRequest = cache .put(响应)
返回 cacheWritingResponse(cacheRequest, response) .also {
如果(缓存响应!= null){
监听器 .cacheMiss(调用)
}
}
}
//根据请求方法判断缓存是否有效,只缓存Get请求,移除其他方法的请求
if (HttpMethod.invalidatesCache(networkRequest.method)) {
尝试 {
//缓存无效,从客户端缓存配置中移除请求缓存
缓存 .remove(networkRequest)
} 捕捉(_:IOException){
}
}
}
返回响应
}
复制代码
网络请求前:
- 首先根据请求从OkHttpClient.cache中获取缓存,通过
缓存策略
获取本次请求的请求体和缓存的响应体。 - 如果请求正文
网络请求
和响应体缓存响应
如果两者都为空,则返回错误码为 504 - 如果请求正文
网络请求
空响应体缓存响应
如果不为空,则返回响应正文 - 如果请求正文
网络请求
如果不为空,则进入下一个拦截器。
网络请求后:
- 如果当前
缓存响应
不为空,并且网络响应
如果状态码为 304,则表示数据没有变化,则为缓存响应
建立一个新的回复
,根据当前时间更新缓存,返回上一个拦截器 - 如果
网络响应
如果状态码不是304,则判断是否缓存,最后返回上一个拦截器
从 LruCache 获取缓存
val cacheCandidate = cache?.get(chain.request())
复制代码 内部乐趣获取(请求:请求):响应? {
val key = key(request.url)
val 快照:DiskLruCache.Snapshot = 尝试 {
缓存[键]?:返回空
} 捕捉(_:IOException){
return null // 因为无法读取缓存而放弃。
}
val条目:条目=尝试{
条目(快照.getSource(ENTRY_METADATA))
} 捕捉(_:IOException){
快照.closeQuietly()
返回空
}
val response = entry.response(snapshot)
如果(!entry.matches(请求,响应)){
response.body?.closeQuietly()
返回空
}
返回响应
}
复制代码 @JvmStatic
有趣的键(url:HttpUrl): String = url.toString().encodeUtf8().md5().hex()
复制代码
- 先把url转换成urf-8,通过md5得到summary,然后调用hex得到16进制字符串,就是LruCache的key;
- 密钥获得
DiskLruCache.Snapshot
对象(这里DiskLruCache
get 方法在 ) 中被覆盖,根据DiskLruCache.Snapshot
对象获取 okio 的来源。
DiskLruCache:
@Synchronized @Throws(IOException::class)
操作员乐趣获取(键:字符串):快照? {
初始化()
checkNotClosed()
验证键(键)
val entry = lruEntries[key] ?: 返回 null
val 快照 = entry.snapshot() ?: 返回 null
冗余操作数++
journalWriter !!.writeUtf8(READ)
.writeByte(''.toInt())
.writeUtf8(键)
.writeByte('\n'.toInt())
if (journalRebuildRequired()) {
cleanupQueue.schedule(cleanupTask)
}
返回快照
}
复制代码
- 最后将数据转化为响应体
我们来看看那些需要缓存的响应体:
这里是网络请求的返回判断是否需要缓存的处理
如果(缓存!= null){
if (response.promisesBody() && CacheStrategy .isCacheable(response, networkRequest)) {
val cacheRequest = cache .put(响应)
返回 cacheWritingResponse(cacheRequest, response) .also {
如果(缓存响应!= null){
监听器 .cacheMiss(调用)
}
}
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
尝试 {
缓存 .remove(networkRequest)
} 捕捉(_:IOException){
}
}
}
复制代码
-
一、根据缓存对象是否为空,决定是否进入缓存判断
-
response.promisesBody()
判断响应体是否有body,CacheStrategy.isCacheable(response, networkRequest)
这里是确定需要缓存哪些状态码 -
这里
HttpMethod.invalidatesCache(networkRequest.method)
确定哪些请求方法是邮政
,修补
,放
,删除
,移动
,如果为真,则删除缓存。fun isCacheable(响应:响应,请求:请求):布尔{
当(response.code){
HTTP_OK,
HTTP_NOT_AUTHORITATIVE,
HTTP_NO_CONTENT,
HTTP_MULT_CHOICE,
HTTP_MOVED_PERM,
HTTP_NOT_FOUND,
HTTP_BAD_METHOD,
HTTP_GONE,
HTTP_REQ_TOO_LONG,
HTTP_NOT_IMPLEMENTED,
StatusLine.HTTP_PERM_REDIRECT -> {}
HTTP_MOVED_TEMP,
StatusLine.HTTP_TEMP_REDIRECT -> {
if (response.header("Expires") == null &&
response.cacheControl.maxAgeSeconds == - 1 &&
!response.cacheControl.isPublic &&
!response.cacheControl.isPrivate) {
返回假
}
}否则-> {
返回假
}
}
返回 !response.cacheControl.noStore && !request.cacheControl.noStore
}
复制代码 -
如果状态码是
200
,203
,204
,301
,404
,405
,410
,414
,501
,308
全部可以缓存,其他返回错误的
没有缓存
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。
这篇文章的链接: https://homecpp.art/5804/6421/0928
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通