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

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

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

原文链接:http://www.jianshu.com/p/3a8d910cce38

时间: 2024-10-02 05:37:19

【转】Retrofit 源码解读之离线缓存策略的实现的相关文章

Retrofit2 源码解读

开源库地址:https://github.com/square/retrofit 解读版本:2.1.0 基本概念 Retrofit 是一个针对Java/Android类型安全的Http请求客户端. 基本使用如下: 首先定义一个接口,抽象方法的返回值必须为Call<XX>. public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Pa

AFNetworking 3.0 源码解读 总结

终于写完了 AFNetworking 的源码解读.这一过程耗时数天.当我回过头又重头到尾的读了一篇,又有所收获.不禁让我想起了当初上学时的种种情景.我们应该对知识进行反复的记忆和理解.下边是我总结的 AFNetworking 中能够学到的知识点. 1.枚举(enum) 使用原则:当满足一个有限的并具有统一主题的集合的时候,我们就考虑使用枚举.这在很多框架中都验证了这个原则.最重要的是能够增加程序的可读性. 示例代码: /** * 网络类型 (需要封装为一个自己的枚举) */ typedef NS

Retrofit源码设计模式解析(下)

本文将接着<Retrofit源码设计模式解析(上)>,继续分享以下设计模式在Retrofit中的应用: 适配器模式 策略模式 观察者模式 单例模式 原型模式 享元模式 一.适配器模式 在上篇说明CallAdapter.Factory使用工厂模式时,提到CallAdapter本身采用了适配器模式.适配器模式将一个接口转换成客户端希望的另一个接口,使接口本不兼容的类可以一起工作. Call接口是Retrofit内置的发送请求给服务器并且返回响应体的调用接口,包括同步.异步请求,查询.取消.复制等功

structs2源码解读(6)之解析package标签

structs2源码解读之解析package标签 上面讨论过,在创建Dispacher对象时,调用dispacher.init()方法完成初始化,在这个方法中先创建各种配置文件的解析器(ConfigurationProvider),然后循环遍历这些解析器的register()方法解析各个配置文件.  for (final ContainerProvider containerProvider : providers)         {             containerProvider

Android Retrofit源码分析(一边用一边侃)

这几天空余时间总是想着写点什么,所以紧跟着之前android盒子模型FlexBoxLayout之后有写下了这篇Retrofit源码分析使用篇. 前言: Retrofit由于简单与出色的性能,使其安卓上最流行的HTTP Client库之一. Android Studio开发相关配置如下: compile "com.squareup.retrofit2:retrofit:2.0.2" compile "com.squareup.retrofit2:converter-gson:2

SpringMVC源码解读 - HandlerMapping

SpringMVC在请求到handler处理器的分发这步是通过HandlerMapping模块解决的.handlerMapping 还处理拦截器. 先看看HandlerMapping的继承树吧 可以大致这样做个分类: 1. 一个接口HandlerMapping,定义一个api: HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 2. 一个基础抽象类:主要是准备上下文环境,提供getHand

struct2源码解读(8)之container原理

struct2源码解读之container原理 container翻译成中文的意思是容器,通俗地来说,就是struct2的运行环境.这个所谓的运行环境,有点类似于一个容器,里面装着各种对象,当struct2处理aciton请求的,就会容器中取相应的对象.下面探讨下container的实现原理.container是一个接口,主要有两个方法,一个是inject() 一个是getInstance():getInstance()是从容器取出对象,inject()是依赖注入.struts在启动的时候,就把

精通Spark:Spark内核剖析、源码解读、性能优化和商业案例实战

这是世界上第一个Spark内核高端课程: 1, 该课程在对Spark的13个不同版本源码彻底研究基础之上提炼而成: 2, 课程涵盖Spark所有内核精髓的剖析: 3, 课程中有大量的核心源码解读: 4, 全景展示Spark商业案例下规划.部署.开发.管理技术: 5, 涵盖Spark核心优化技巧 该课程是Spark的高端课程,其前置课程是“18小时内掌握Spark:把云计算大数据速度提高100倍以上!”. 培训对象 1,  系统架构师.系统分析师.高级程序员.资深开发人员: 2, 牵涉到大数据处理

第15课:Spark Streaming源码解读之No Receivers彻底思考

本期内容: Direct Access Kafka 前面有几期我们讲了带Receiver的Spark Streaming 应用的相关源码解读.但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 1. 更强的控制自由度 2. 语义一致性 其实No Receivers的方式更符合我们读取数据,操作数据的思路的.因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receive