Retrofit 实现离线缓存

Retrofit 2.0开始,底层的网络连接全都依赖于OkHttp,故要设置缓存,必须从OkHttp下手。

特别要说明的是,retrofit 2.0已经默认支持缓存,OkHttp版本不同,配置方法也不同。

OkHttp3.0版本

接口方法加上@UseCache,即可实现Cache.
Why?Retrofit 2.0,为Observable提供了Cache支持.

cache的一些表现:
Cache获取与网络获取是异步进行的.
若网络获取比Cache获取速度快,Cache不会发射出去.
网络获取完毕后,异步进行保存Cache操作,不阻塞结果的发射.

优秀Demo:https://github.com/cpoopc/RetrofitRxCache

OkHttp2.6版本

开启OkHttp缓存

File httpCacheDirectory = new File(UIUtils.getContext().getExternalCacheDir(), "responses");
client.setCache(new Cache(httpCacheDirectory,10 * 1024 * 1024));

我们可以看到 先获取系统外部存储的缓存路径,命名为response,此文件夹可以在android/data/<包名>/cache/resposes看到里面的内容,具体OkHttp是如何做到离线缓存的呢?

我们进入Cache类,有重大发现,首先是它的注释,极其详细。

Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and bandwidth.
Cache Optimization
To measure cache effectiveness, this class tracks three statistics:
Request Count: the number of HTTP requests issued since this cache was created.
Network Count: the number of those requests that required network use.
Hit Count: the number of those requests whose responses were served by the cache.
Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of the response, the client will issue a conditional GET. The server will then send either the updated response if it has changed, or a short 'not modified' response if the client's copy is still valid. Such responses increment both the network count and hit count.
The best way to improve the cache hit rate is by configuring the web server to return cacheable responses. Although this client honors all HTTP/1.1 (RFC 7234) cache headers, it doesn't cache partial responses.
Force a Network Response
In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:

Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder().noCache().build())
    .url("http://publicobject.com/helloworld.txt")
    .build();

If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 directive instead:

    Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
            .maxAge(0, TimeUnit.SECONDS)
            .build())
        .url("http://publicobject.com/helloworld.txt")
        .build();

Force a Cache Response
Sometimes you'll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:

      Request request = new Request.Builder()
          .cacheControl(new CacheControl.Builder()
              .onlyIfCached()
              .build())
          .url("http://publicobject.com/helloworld.txt")
          .build();
      Response forceCacheResponse = client.newCall(request).execute();
      if (forceCacheResponse.code() != 504) {
        // The resource was cached! Show it.
      } else {
        // The resource was not cached.
      }

This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:

    Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder()
            .maxStale(365, TimeUnit.DAYS)
            .build())
        .url("http://publicobject.com/helloworld.txt")
        .build();

The CacheControl class can configure request caching directives and parse response caching directives. It even offers convenient constants CacheControl.FORCE_NETWORK and CacheControl.FORCE_CACHE that address the use cases above.

文档详细说明了此类的作用,支持Http缓存使用,然后具体的用法,可惜的是我们这里使用的是Retrofit,无法直接用OkHttp,如果直接用OkHttp的童鞋们,可以根据上面的提示,完成具体的缓存操作。

通过阅读文档,我们知道还有一个类,CacheControl类,只要负责缓存策略的管理,其中,支持一下策略

  1. noCache 不使用缓存,全部走网络
  2. noStore 不使用缓存,也不存储缓存
  3. onlyIfCached 只使用缓存
  4. maxAge 设置最大失效时间,失效则不使用 需要服务器配合
  5. maxStale 设置最大失效时间,失效则不使用 需要服务器配合 感觉这两个类似 还没怎么弄清楚,清楚的同学欢迎留言
  6. minFresh 设置有效时间,依旧如上
  7. FORCE_NETWORK 只走网络
  8. FORCE_CACHE 只走缓存

通过上面的CacheControl类,我们很快就能指定详细的策略

首先,判断网络,有网络,则从网络获取,并保存到缓存中,无网络,则从缓存中获取

所以,最终的代码如下

-首先,给OkHttp设置拦截器

client.interceptors().add(interceptor);

-然后,在拦截器内做Request拦截操作

Request request = chain.request();
                if (!AppUtil.isNetworkReachable(UIUtils.getContext())) {
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .url(path).build();
                    UIUtils.showToastSafe("暂无网络");
                }

其中,AppUtil.isNetworkReachable(UIUtils.getContext())是判断网络是否连接的方法,具体逻辑如下

/** * 判断网络是否可用 * * @param context Context对象 */
public static Boolean isNetworkReachable(Context context) {
    ConnectivityManager cm = (ConnectivityManager) context
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo current = cm.getActiveNetworkInfo();
    if (current == null) {
        return false;
    }
    return (current.isAvailable());
}

在每个请求发出前,判断一下网络状况,如果没问题继续访问,如果有问题,则设置从本地缓存中读取

-接下来是设置Response

 Response response = chain.proceed(request);
                if (AppUtil.isNetworkReachable(UIUtils.getContext())) {
                    int maxAge = 60*60; // read from cache for 60 minute
                    response.newBuilder()
                            .removeHeader("Pragma")
                            .header("Cache-Control", "public, max-age=" + maxAge)
                            .build();
                } else {
                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                    response.newBuilder()
                            .removeHeader("Pragma")
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                            .build();
                }

先判断网络,网络好的时候,移除header后添加haunch失效时间为1小时,网络未连接的情况下设置缓存时间为4周

-最后,拦截器全部代码

Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (!AppUtil.isNetworkReachable(UIUtils.getContext())) {
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .url(path).build();
                    UIUtils.showToastSafe("暂无网络");
                }

                Response response = chain.proceed(request);
                if (AppUtil.isNetworkReachable(UIUtils.getContext())) {
                    int maxAge = 60 * 60; // read from cache for 1 minute
                    response.newBuilder()
                            .removeHeader("Pragma")
                            .header("Cache-Control", "public, max-age=" + maxAge)
                            .build();
                } else {
                    int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                    response.newBuilder()
                            .removeHeader("Pragma")
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                            .build();
                }
                return response;
            }
        };

参考:http://www.cnblogs.com/Android-MR-wang/p/5133925.html

你可能感兴趣的:(离线缓存,retrofit)