OkHttp3源码分析[缓存策略]

2024-09-06 01:18

本文主要是介绍OkHttp3源码分析[缓存策略],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

OkHttp系列文章如下

  • OkHttp3源码分析[综述]
  • OkHttp3源码分析[复用连接池]
  • OkHttp3源码分析[缓存策略]
  • OkHttp3源码分析[DiskLruCache]
  • OkHttp3源码分析[任务队列]

本文专门分析OkHttp的缓存策略,应该是okhttp分析中最简单的一篇了


HTTP缓存基础知识

在分析源码之前,我们先回顾一下http的缓存Header的含义

1. Expires

表示到期时间,一般用在response报文中,当超过此事件后响应将被认为是无效的而需要网络连接,反之而是直接使用缓存

Expires: Thu, 12 Jan 2017 11:01:33 GMT
2. Cache-Control

相对值,单位是秒,指定某个文件被续多少秒的时间,从而避免额外的网络请求。比expired更好的选择,它不用要求服务器与客户端的时间同步,也不用服务器时刻同步修改配置Expired中的绝对时间,而且它的优先级比Expires更高。比如简书静态资源有如下的header,表示可以续31536000秒,也就是一年。

Cache-Control: max-age=31536000, public
3. 修订文件名(Reving Filenames)

如果我们通过设置header保证了客户端可以缓存的,而此时远程服务器更新了文件如何解决呢?我们这时可以通过修改url中的文件名版本后缀进行缓存,比如下文是又拍云的公共CDN就提供了多个版本的JQuery

upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js
4. 条件GET请求(Conditional GET Requests)与304

如缓存果过期或者强制放弃缓存,在此情况下,缓存策略全部交给服务器判断,客户端只用发送条件get请求即可,如果缓存是有效的,则返回304 Not Modifiled,否则直接返回body。

请求的方式有两种:

4.1. Last-Modified-Date:

客户端第一次网络请求时,服务器返回了

Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT

客户端再次请求时,通过发送

If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT

交给服务器进行判断,如果仍然可以缓存使用,服务器就返回304

4.2. ETag

ETag是对资源文件的一种摘要,客户端并不需要了解实现细节。当客户端第一请求时,服务器返回了

ETag: "5694c7ef-24dc"

客户端再次请求时,通过发送

If-None-Match:"5694c7ef-24dc"

交给服务器进行判断,如果仍然可以缓存使用,服务器就返回304

如果 ETag 和 Last-Modified 都有,则必须一次性都发给服务器,它们没有优先级之分,反正这里客户端没有任何判断的逻辑。

5. 其它标签
  • no-cache/no-store: 不使用缓存
  • only-if-cached: 只使用缓存
  • Date: The date and time that the message was sent
  • Age: The Age response-header field conveys the sender's estimate of the amount of time since the response (or its revalidation) was generated at the origin server. 说人话就是CDN反代服务器到原始服务器获取数据延时的缓存时间

"only-if-cached"标签非常具有诱导性,它只在请求中使用,表示无论是否有网完全只使用缓存(如果命中还好说,否则返回503错误/网络错误),这个标签比较危险。

全部的标签,可以到这里看

以上内容是作为一个服务器开发或者客户端的常识,下图是网上找的总结,注意图中的 ETag 和 Last-Modified 可能有优先级的歧义,你只需要记住它们是没有优先级的。


图源: 浏览器缓存机制 - 吴秦(Tyler)

2. 源码分析

OkHttp中使用了CacheStrategy实现了上文的流程图,它根据之前的缓存结果与当前将要发送Request的header进行策略分析,并得出是否进行请求的结论。

2.1. 总体请求流程分析

CacheStrategy类似一个mapping操作,将两个值输入,再将两个值输出

Inputrequest, cacheCandidate
CacheStrategy处理,判断Header信息
OutputnetworkRequest, cacheResponse

Request:
开发者手动编写并在Interceptor中递归加工而成的对象(如果读者需要调试分析的话,可以用logging-interceptor进行log操作),我们只需要知道了目前传入的Request中并没有任何关于缓存的Header

cacheCandidate:
也就是上次与服务器交互缓存的Response,可能为null。这里的缓存全部是基于文件系统的Map,key是请求中url的md5,value是在文件中查询到的缓存,页面置换基于LRU算法,我们现在只需要知道它是一个可以读取缓存Header的Response即可。

当被CacheStrategy加工输出后,输出networkRequestcacheResponse,根据是否为空执行不同的请求

networkRequestcacheResponseresult
nullnullonly-if-cached(表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误)
nullnon-null不进行网络请求,而且缓存可以使用,直接返回缓存,不用请求网络
non-nullnull需要进行网络请求,而且缓存不存在或者过期,直接访问网络
non-nullnon-nullHeader中含有ETag/Last-Modified标签,需要在条件请求下使用,还是需要访问网络

以上是对networkRequest/cacheResponse进行findusage查询获得出的结论

基本上与上文的图片完全一致,以上就是OkHttp的缓存策略

关于此部分的分析,读者可以在HttpEngine对象中通过对userResponse进行findUsage分析得出,源码都是一大堆的if判断

2.2. CacheStrategy的加工过程

CacheStrategy使用Factory模式进行构造,参数如下

InternalCache responseCache = Internal.instance.internalCache(client);
//cacheCandidate从disklurcache中获取
//request的url被md5序列化为key,进行缓存查询
Response cacheCandidate = responseCache != null ? responseCache.get(request) : null;
//请求与缓存
factory = new CacheStrategy.Factory(now, request, cacheCandidate);
cacheStrategy = factory.get();
//输出结果
networkRequest = cacheStrategy.networkRequest;
cacheResponse = cacheStrategy.cacheResponse;
//进行一大堆的if判断,内容同上表格
.....

可以看出Factory.get()是最关键的缓存策略的判断,我们点入get()方法,可以发现是对getCandidate()的一个封装,我们接着点开getCandidate(),全是if与数学计算,详细代码如下

private CacheStrategy getCandidate() {//如果缓存没有命中(即null),网络请求也不需要加缓存Header了if (cacheResponse == null) {//`没有缓存的网络请求,查上文的表可知是直接访问return new CacheStrategy(request, null);}// 如果缓存的TLS握手信息丢失,返回进行直接连接if (request.isHttps() && cacheResponse.handshake() == null) {//直接访问return new CacheStrategy(request, null);}//检测response的状态码,Expired时间,是否有no-cache标签if (!isCacheable(cacheResponse, request)) {//直接访问return new CacheStrategy(request, null);}CacheControl requestCaching = request.cacheControl();//如果请求报文使用了`no-cache`标签(这个只可能是开发者故意添加的)//或者有ETag/Since标签(也就是条件GET请求)if (requestCaching.noCache() || hasConditions(request)) {//直接连接,把缓存判断交给服务器return new CacheStrategy(request, null);}//根据RFC协议计算//计算当前age的时间戳//now - sent + age (s)long ageMillis = cacheResponseAge();//大部分情况服务器设置为max-agelong freshMillis = computeFreshnessLifetime();if (requestCaching.maxAgeSeconds() != -1) {//大部分情况下是取max-agefreshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));}long minFreshMillis = 0;if (requestCaching.minFreshSeconds() != -1) {//大部分情况下设置是0minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());}long maxStaleMillis = 0;//ParseHeader中的缓存控制信息CacheControl responseCaching = cacheResponse.cacheControl();if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {//设置最大过期时间,一般设置为0maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());}//缓存在过期时间内,可以使用//大部分情况下是进行如下判断//now - sent + age + 0 < max-age + 0if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {//返回上次的缓存Response.Builder builder = cacheResponse.newBuilder();return new CacheStrategy(null, builder.build());}//缓存失效, 如果有etag等信息//进行发送`conditional`请求,交给服务器处理Request.Builder conditionalRequestBuilder = request.newBuilder();if (etag != null) {conditionalRequestBuilder.header("If-None-Match", etag);} else if (lastModified != null) {conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);} else if (servedDate != null) {conditionalRequestBuilder.header("If-Modified-Since", servedDateString);}//下面请求实质还说网络请求Request conditionalRequest = conditionalRequestBuilder.build();return hasConditions(conditionalRequest) ? new CacheStrategy(conditionalRequest,cacheResponse) : new CacheStrategy(conditionalRequest, null);
}

太长不看的话,大多数常见的情况可以用这个估算

now - sent + age < max-age

这里有个技巧,对构造函数进行findUsage查询,就可以看出各个输出是否为空的结果,然后各个击破分析


new CacheStrategy()

3. 结论

通过上面的分析,我们可以发现,okhttp实现的缓存策略实质上就是大量的if判断集合,这些是根据RFC标准文档写死的,并没有相当难的技巧。

  1. Okhttp的缓存是自动完成的,完全由服务器Header决定的,自己没有必要进行控制。网上热传的文章在Interceptor中手工添加缓存代码控制,它固然有用,但是属于Hack式的利用,违反了RFC文档标准,不建议使用,OkHttp的官方缓存控制在注释中。如果读者的需求是对象持久化,建议用文件储存或者数据库即可(比如realm)。
  2. 服务器的配置非常重要,如果你需要减小请求次数,建议直接找对接人员对max-age等头文件进行优化;服务器的时钟需要严格NTP同步
  3. 充分利用Idea的findUsage的功能,源码的各个跳转条件可以很快分析完成
  4. 使用CMD + Y可以快速预览某个函数,类似于forcetouch功能


    Idea quick preview
  5. 使用CMD + 左键可以添加标签,方便跳转代码,如图


    Idea Favorite Bookmarks

最后,感谢大家的观看

这篇关于OkHttp3源码分析[缓存策略]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140584

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud