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 生成的请求执行对象,执行时 执行 只有这样才会执行实际的网络请求
  • 响应:通过网络发出请求后,服务器返回的信息就在其中。包含返回的状态码,以及响应消息的正文 响应体
  1. 拦截器用户定义的拦截器在重试拦截器之前执行
  2. retryAndFollowUpInterceptor 重试拦截器
  3. BridgeInterceptor 为网桥建立拦截器,主要是为网络请求添加各种必要的参数。如 Cookie、Content-type
  4. CacheInterceptor 缓存拦截器,主要是在网络请求时根据返回码处理缓存。
  5. ConnectInterceptor 连接拦截器,主要是从连接池中寻找可重用的socket连接。
  6. networkInterceptors 用户定义的网络拦截器,在 CallServerInterceptor(执行网络请求拦截器)之前运行。
  7. CallServerInterceptor 实际执行的是网络请求的逻辑。

实施过程

image.png

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  
 }  
 返回空  
 }  
  
 复制代码
  1. 首先将 AsyncCall 添加到 readyAsyncCalls 队列中。

  2. 然后使用findExistingCallWithHost查找runningAsyncCalls和readyAsyncCalls中是否有同一主机的AsyncCall,如果有,调用call.reuseCallsPerHostFrom()进行复用

  3. 最后调用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)结束。

拦截器

让我们仔细看看拦截器

image.png

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()
    }
    复制代码

  1. 设置标头的 Content-Type。解释内容类型是什么
  2. 如果contentLength大于等于0,设置header的Content-Length(表示内容大小是多少);否则,设置header的Transfer-Encoding为chunked(表示传输编码为chunked传输)
  3. 如果Host不存在,则在header中设置Host(出现在Http 1.1之后,可以通过同一个URL访问不同的主机,从而实现服务器虚拟服务器的负载均衡。如果1.1之后不设置,则会返回 404)。
  4. 如果Connection不存在,将header中的Connection设置为Keep-Alive(代表连接状态需要保持活跃)
  5. 如果 Accept-Encoding 和 Range 为空,则必须设置 Accept-Encoding 为 gzip(表示请求会被 gzip 压缩)
  6. 从 CookieJar 缓存中获取设置到标头的 cookie
  7. 如果 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 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/12254/58090411

posted @   哈哈哈来了啊啊啊  阅读(213)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示