OKHTTP缓存机制的学习

这几天在封装OKHttp框架,之前项目没用到数据缓存,所以了解一下,现在分享一波,肯定有不正确的地方,希望大家可以纠正。

首先,一般有两种缓存:服务器端缓存、客户端缓存

一、概念

①服务器端缓存

服务端缓存又分为代理服务器缓存和反向代理服务器缓存。常见的CDN就是服务器缓存。当浏览器重复访问一张图片地址时,CDN会判断这个请求有没有缓存,如果有的话就直接返回这个缓存的请求回复,而不再需要让请求到达真正的服务地址,这么做的目的是减轻服务端的运算压力。通俗的讲,一般大点的项目,都会有多个缓存服务器,但是只有一个源服务器,缓存服务器是根据模块划分的,客户端请求接口是请求的各个缓存服务器,缓存服务器再去源服务器请求数据。

②客户端缓存

当客户端首次请求服务器接口时,如果服务器返回的数据正常,那么客户端将数据返回到本地,当客户端再次访问同一个地址时,客户端会检测本地有没有缓存,如果有缓存的话,查看数据是否过期,如果没有过期则直接用本地缓存数据。

二、适用场景

数据更新不频繁的查询操作,客户端缓存可以减少访问服务器次数,减少服务器压力,并且无网络状态也可以显示历史数据;服务器端缓存,响应快,方便管理。

三、浅谈HTTP知识

一般的响应头:

HTTP/1.1 200 OK
Server: openresty
Date: Mon, 24 Oct 2016 09:00:34 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
X-Powered-By: PHP 5.4.28
Content-Encoding: gzip

咱们只关心倒数第三条:Cache-Control: private

Cache-control是由服务器返回的Response中添加的头信息,它的目的是告诉客户端是要从本地读取缓存还是直接从服务器摘取消息。它有不同的值,每一个值有不同的作用。

max-age:这个参数告诉浏览器将页面缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新。对于静态的页面,比如图片、CSS、Javascript,一般都不大变更,因此通常我们将存储这些内容的时间设置为较长的时间,这样浏览器会不会向浏览器反复发起请求,也不会去检查是否更新了。
s-maxage:这个参数告诉缓存服务器(proxy,如Squid)的缓存页面的时间。如果不单独指定,缓存服务器将使用max-age。对于动态内容(比如文档的查看页面),我们可告诉浏览器很快就过时了(max-age=0),并告诉缓存服务器(Squid)保留内容一段时间(比如,s-maxage=7200)。一旦我们更新文档,我们将告诉Squid清除老的缓存版本。
must-revalidate:这告诉浏览器,一旦缓存的内容过期,一定要向服务器询问是否有新版本。
proxy-revalidate:proxy上的缓存一旦过期,一定要向服务器询问是否有新版本。
no-cache:不做缓存。
no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。
public:告诉缓存服务器, 即便是对于不该缓存的内容也缓存起来,比如当用户已经认证的时候。所有的静态内容(图片、Javascript、CSS等)应该是public的。
private:告诉proxy不要缓存,但是浏览器可使用private cache进行缓存。一般登录后的个性化页面是private的。
no-transform: 告诉proxy不进行转换,比如告诉手机浏览器不要下载某些图片。
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

四、和客户端有关的缓存设置(本文的核心)

在OKHttp开发中我们常见到的有下面几个:

  • max-age

  • no-cache

  • max-stale

1.配置okhttp中的Cache文件目录
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
File cacheFile = new File(content.getExternalCacheDir(),"tangnuer");
Cache cache = new Cache(cacheFile,1024*1024*50);
2.配置okhttp中的Cache

分为两种:

服务器支持缓存

如果服务器支持缓存,请求返回的Response会带有这样的Header:Cache-Control, max-age=xxx,这种情况下我们只需要手动给okhttp设置缓存就可以让okhttp自动帮你缓存了。这里的max-age的值代表了缓存在你本地存放的时间,可以根据实际需要来设置其大小。

使用缓存可以让我们的app不用长时间地显示令人厌烦的加载圈,提高了用户体验,而且还节省了流量,在数据更新不是很频繁的地方使用缓存就非常有必要了。想要加入缓存不需要我们自己来实现,Okhttp已经内置了缓存,默认是不使用的,如果想使用缓存我们需要手动设置。

httpClientBuilder
    .cache(cache)
    .connectTimeout(20, TimeUnit.SECONDS)
    .readTimeout(20, TimeUnit.SECONDS)

服务器不支持缓存

如果服务器不支持缓存就可能没有指定这个头部,或者指定的值是如no-store等,但是我们还想在本地使用缓存的话要怎么办呢?这种情况下我们就需要使用Interceptor来重写Respose的头部信息,从而让okhttp支持缓存。

public class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        Response response1 = response.newBuilder()
            .removeHeader("Pragma")
            .removeHeader("Cache-Control")
            //cache for 30 days
            .header("Cache-Control", "max-age=" + 3600 * 24 * 30)
            .build();
        return response1;
    }
}

然后将该Intercepter作为一个NetworkInterceptor加入到okhttpClient中:

httpClientBuilder
    .addNetworkInterceptor(new CacheInterceptor())
    .cache(cache)
    .connectTimeout(20, TimeUnit.SECONDS)
    .readTimeout(20, TimeUnit.SECONDS)

设置好Cache我们就可以正常访问了。我们可以通过获取到的Response对象拿到它正常的消息和缓存的消息。
这样我们就可以在服务器不支持缓存的情况下使用缓存了。

这里我需要解释几个概念

Response的消息有两种类型,CacheResponse和NetworkResponse。CacheResponse代表从缓存取到的消息,NetworkResponse代表直接从服务端返回的消息。

第一次访问的时候,Response的消息是NetworkResponse消息,此时CacheResponse的值为Null.而第二次访问的时候Response是CahceResponse,而此时NetworkResponse为空。

所以咱们的思路是:通过拿到NetworkResponse网络数据,做缓存,刚才这个方法其实原理就是:定义一个拦截器,人为地添加Response中的消息头,然后再传递给用户,这样用户拿到的Response就有了我们理想当中的消息头Headers,从而达到控制缓存的意图,正所谓移花接木。

缺点:

  1. 网络访问请求的资源是文本信息,如新闻列表,这类信息经常变动,一天更新好几次,它们用的缓存时间应该就很短。
  2. 网络访问请求的资源是图片或者视频,它们变动很少,或者是长期不变动,那么它们用的缓存时间就应该很长。

okhttp官方文档缓存方法

/**强制使用网络请求*/
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

/**强制性使用本地缓存,如果本地缓存不满足条件,则会返回code为504*/
public static final CacheControl FORCE_CACHE = new Builder()
  .onlyIfCached()
  .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
  .build();

FORCE_NETWORK常量用来强制使用网络请求。FORCE_CACHE只取本地的缓存。它们本身都是CacheControl对象,由内部的Buidler对象构造。

CacheControl.Builder

- noCache();//不使用缓存,用网络请求
- noStore();//不使用缓存,也不存储缓存
- onlyIfCached();//只使用缓存
- noTransform();//禁止转码
- maxAge(10, TimeUnit.MILLISECONDS);//设置超时时间为10ms。
- maxStale(10, TimeUnit.SECONDS);//超时之外的超时时间为10s
- minFresh(10, TimeUnit.SECONDS);//超时时间为当前时间加上10秒钟。

根据这些方法做一个动态的缓存机制。
我这边的封装是这样的:

OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
if(isCache) {
   File cacheFile = new File(BaseApplication.getAppContext().getExternalCacheDir(),"ZhiBookCache");
   Cache cache = new Cache(cacheFile,1024*1024*50);
  Interceptor interceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
         Request request = chain.request();
         if (!SystemState.isNetConnected()) {
             request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build();
         }
         Response response = chain.proceed(request);
         if (SystemState.isNetConnected()) {
             int maxAge = 0 * 60;
             // 有网络时 设置缓存超时时间0个小时
             response.newBuilder()
               .header("Cache-Control", "public, max-age=" + maxAge)
               .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
               .build();
       } else {
            // 无网络时,设置超时为4周
            //int maxStale = 60 * 60 * 24 * 28;
             int maxStale = 0;
            response.newBuilder()
            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
            .removeHeader("Pragma")
            .build();
     }
      return response;
 }
 };
  httpClientBuilder.cache(cache)
 .addInterceptor(interceptor)
  .addNetworkInterceptor(interceptor);
 }

谢谢阅读,我这里也是学习的态度在这里分享,有什么问题希望大家能提出来能提出来,随时可以和我交流探讨:QQ:707086125 微信:loveme_dp

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-01 17:13
想去,但是听说加班强度实在难崩,所以拒绝了,现在有点心梗对面hr感觉也是实习生,打电话的时候怪紧张的,但是感觉人很好嘞
水中水之下水道的鼠鼠:哥们这不先去体验一下,不行再跑呗,大不了混个实习经历(有更好的转正offer就当我没说)
点赞 评论 收藏
分享
06-27 12:54
已编辑
门头沟学院 Java
累了,讲讲我的大学经历吧,目前在家待业。我是一个二本院校软件工程专业。最开始选专业是觉得计算机感兴趣,所以选择了他。本人学习计算机是从大二暑假结束开始的,也就是大三开始。当时每天学习,我个人认为Java以及是我生活的一部分了,就这样持续学习了一年半,来到了大四上学期末,大概是在12月中旬,我终于找的到了一家上海中厂的实习,但我发现实习生的工作很枯燥,公司分配的活也不多,大多时间也是自己在自学。就这样我秋招末才找到实习。时间来到了3月中旬,公司说我可以转正,但是转正工资只有7000,不过很稳定,不加班,双休,因为要回学校参加答辩了,同时当时也是心高气傲,认为可以找到更好的,所以放弃了转正机会,回学校准备论文。准备论文期间就也没有投递简历。然后时间来到了5月中旬,这时春招基本也结束了,然后我开始投递简历,期间只是约到了几家下场面试。工资也只有6-7k,到现在我不知道该怎么办了。已经没有当初学习的心劲了,好累呀,但是又不知道该干什么去。在家就是打游戏,boss简历投一投。每天日重一次。26秋招都说是针对26届的人,25怎么办。我好绝望。要不要参加考公、考研、央国企这些的。有没有大佬可以帮帮我。为什么感觉别人找工作都是顺其自然的事情,我感觉自己每一步都在艰难追赶。八股文背了又忘背了又忘,我每次都花很长时间去理解他,可是现在感觉八股、项目都忘完了。真的已经没有力气再去学习了。图片是我的简历,有没有大哥可以指正一下,或者说我应该走哪条路,有点不想在找工作了。
码客明:太累了就休息一下兄弟,人生不会完蛋的
如果实习可以转正,你会不...
点赞 评论 收藏
分享
05-19 19:57
蚌埠学院 Python
2237:Gpa70不算高,建议只写排名,个人技能不在多而在精,缩到8条以内。项目留一个含金量高的,减少间距弄到一页,硕士简历也就一页,本科不要写很多
实习,投递多份简历没人回...
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
06-29 17:30
找实习找着找着就要进入7月了,马上秋招也要开始了,找实习还有意义吗?
绝迹的星:有面就面, 没面上就当日薪4位数大佬免费培训, 面上了再考虑要不要实习
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务