OKHTTP缓存机制--转发

1,114 阅读8分钟

这几天在使用OKHttp框架做WebView数据缓存,找到一篇好文,分享一波。

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

一、概念

<h5>①服务器端缓存</h5>

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

<h5>②客户端缓存</h5>

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

二、适用场景

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

三、浅谈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

<h5>1.配置okhttp中的Cache文件目录 </h5>

OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
File cacheFile = new File(content.getExternalCacheDir(),"tangnuer");
Cache cache = new Cache(cacheFile,1024*1024*50);

<h5>2.配置okhttp中的Cache</h5>
分为两种:

服务器支持缓存

如果服务器支持缓存,请求返回的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);
 }


作者:小杜先生
链接:https://www.jianshu.com/p/2821000526df
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。