Retrofit 源码解读之离线缓存策略的实现

Retrofit 源码解读之离线缓存策略的实现

相关代码已上传至 GitHub,已开源库,请移步:
| 优雅的给 Retrofit 加上缓存 RetrofitCache

Retrofit 是square公司开发的一款网络框架,也是至今Android网络请求中最火的一个,配合OkHttp+RxJava+Retrofit三剑客更是如鱼得水,公司项目重构时,我也在第一时间使用了RxJava+Retrofit,使用过程中遇到的一些问题,也会在后续的博客中,一点点分享出来,供大家参考!

在项目的过程中,项目需求需要在离线的情况下能够继续浏览app内容,第一时间想到缓存,于是经过各种google搜索,得出以下结论(使用Retrofit 2.0)

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

-具体的使用过程为:1.先开启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.

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

回到Retrofit,通过阅读上面的文档,我们知道还有一个类,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();//拦截reqeust
                if (!AppUtil.isNetworkReachable(UIUtils.getContext())) {//判断网络连接状况
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)//无网络时只从缓存中读取
                            .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; // 有网络时 设置缓存超时时间1个小时
                    response.newBuilder()
                            .removeHeader("Pragma")
                            //清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                            .header("Cache-Control", "public, max-age=" + maxAge)//设置缓存超时时间
                            .build();
                } else {
                    int maxStale = 60 * 60 * 24 * 28; // 无网络时,设置超时为4周
                    response.newBuilder()
                            .removeHeader("Pragma")
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                            //设置缓存策略,及超时策略
                            .build();
                }

先判断网络,网络好的时候,移除header后添加cache失效时间为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("暂无网络");//子线程安全显示Toast
                }

                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;
            }
        };

快过年了,祝所有的童鞋们,身体健康,事事如意!!,咳咳,还有最重要的,程序无Bug!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容