快速实现OkHttp请求缓存

网络请求是每一个Android应用都需要的功能,通过网络获取内容既缓慢,成本又高;大的响应需要在客户端和服务器之间进行多次往返通信,这拖延了客户端可以使用和处理内容的时间,同时也增加了访问者的数据成本。因此,缓存和重用以前获取的资源的能力成为优化性能很关键的一个方面。

Cache-Control

Android应用需要提供额外的配置标志,以确保启用了 HTTP 缓存,并根据用途设置了合理的缓存大小,同时,确保缓存持久化。Cache-Control指定了请求和响应遵循的缓存机制。好的缓存机制可以减少对网络带宽的占用,可以提高访问速度,提高用户的体验,还可以减轻服务器的负担。

快速实现OkHttp请求缓存_第1张图片

服务器在返回响应时,还会发出一组 HTTP 头,用来描述内容类型、长度、缓存指令、验证令牌等。例如,在上图的交互中,服务器返回了一个 1024 字节的响应,指导客户端缓存响应长达 120 秒,允许读取过期时间小于86400值的缓存对象。

  • public ---- 数据内容皆被储存起来,就连有密码保护的网页也储存,安全性很低
  • private ---- 数据内容只能被储存到私有的cache,仅对某个用户有效,不能共享
  • no-cache ---- 可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端
  • no-store ---- 请求和响应都禁止被缓存
  • max-age: ----- 本响应包含的对象的过期时间
  • max-stale ---- 允许读取过期时间必须小于max-stale 值的缓存对象。
  • no-transform ---- 告知代理,不要更改媒体类型,比如jpg,被你改成png.

针对上面关于Cache-Control的配置说明,以下是几种常见的情况:

  • 在某些情况下,如用户单击“刷新”按钮,就可能有必要跳过缓存,并直接从服务器获取数据。要强制刷新,添加无缓存指令:”Cache-Control”: “no-cache”。
  • 如果缓存只是用来和服务器做验证,可是设置更有效的”Cache-Control”:”max-age=0”。
  • 有时你会想显示可以立即显示的资源。这是可以使用的,这样你的应用程序可以在等待最新的数据下载的时候显示一些东西, 重定向request到本地缓存资源,添加”Cache-Control”:”only-if-cached”。
  • 有时候过期的response比没有response更好,设置最长过期时间来允许过期的response响应: int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale

使用ETag验证缓存的响应

服务器通过ETag HTTP头传递验证令牌

通过验证令牌可以进行高效的资源更新检查:如果资源未更改,则不会传输任何数据

让我们假设在首次获取资源 120 秒之后,客户端又对该资源发起了新请求。首先,客户端会检查本地缓存并找到之前的响应,不幸的是,这个响应现在已经’过期’,无法再使用。此时,客户端也可以直接发出新请求,获取新的完整响应,但是这样做效率较低,因为如果资源未被更改过,我们就没有理由再去下载与缓存中已有的完全相同的字节。

这就是 ETag 头中指定的验证令牌所要解决的问题:服务器会生成并返回一个随机令牌,通常是文件内容的哈希值或者某个其他指纹码。客户端不必了解指纹码是如何生成的,只需要在下一个请求中将其发送给服务器:如果指纹码仍然一致,说明资源未被修改,我们就可以跳过下载。

OkHttp的缓存策略

  1. 首先需要配置OkHttp的Cache

    public static Cache getCache(){
     File httpCacheDirectory = new File(YourenApplication.getInstance().getCacheDir(),       "responses");
     return new Cache(httpCacheDirectory, 10 * 1024 * 1024);         // 缓存空间10M
    }
    
    OkHttpClient.Builder().cache(getCache())
    
  2. 如果服务端有做缓存处理的话,我们只需要在Request的Header中添加Cache-Control即可,Retrofit添加Header很方便,只需要在接口定义处添加@Headers注解即可。

    @Headers({
         "Cache-Control: public, max-age=3600"
      })
    @GET("/open/coins")
    Observable>> getCoinList(@Query("type") String       type, @Query("page") int page, @Query("page_size") int pageSize);
    

    但是如果服务端没有做缓存处理的话,返回的响应头中Cache-Control是no-cache,这时还是无法做缓存。那么如果还想要实现缓存,可以通过拦截器修改响应头中Cache-Control。

    public class HttpCacheInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            if (NetworkUtil.isAvailable(mContext)) {
                int maxAge = 0;                     // 有网络时 设置缓存超时时间0个小时
                response.newBuilder()
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .removeHeader("Pragma")     // 清除头信息
                        .build();
            } else {
                int maxStale = 60 * 60 * 24;        // 无网络时,设置超时为1天
                response.newBuilder()
                 .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .removeHeader("Pragma")
                    .build();
            }
            return response;
        }
    }
    

    上面的代码展示了完整的拦截返回值,并添加缓存参数,可以做到在有网络的情况下不使用缓存,无网络情况下只使用缓存,缓存有效期为24小时。

  3. 在OkHttpClient创建处添加该拦截器

    OkHttpClient.Builder()
        .addInterceptor(new HttpCacheInterceptor())     // 设置缓存参数
        .cache(getCache())
        .connectTimeout(10, TimeUnit.SECONDS)
        .build();
    

结语

OkHttp自身实现了Http1.1的缓存机制,浏览器的缓存也是相同的原理,不过其实现的方式和复杂度跟客户端有很多不同,使用OkHttp自身的缓存策略可以很方便快捷地实现请求的缓存功能。

你可能感兴趣的:(快速实现OkHttp请求缓存)