天天看点

Volley缓存-原理介绍

本文将从以下方面介绍Volley的缓存机制

  • 使用缓带来哪些优势
  • Volley的缓存原理
  • Volley对服务端header解析
  • Volley缓存过期判断

使用缓带来哪些优势:

  • 速度:已缓存的资源加载的更快;
  • 减少服务器负载:不用每次都和服务器进行交互;
  • 较少网络流量:对于已缓存且在有效期内的数据将不会再次向服务器请求,如果已过期,只需要判断返回的数据是否发生变化,没有变化返回304,告诉客户端可以使用以前的缓存数据,否则返回新的数据;
  • 离线浏览:用户可以在离线时使用;

Volley缓存原理

Volley的缓存原理和浏览器的缓存原理基本一致,基于HTTP协议,通过HTTP协议Header中的Cache-Control(或Expires)和Last-Modified(或Etag)等字段来控制缓存,再使用DiskBasedCache类将数据存储在本地;,下面对Header里面的一些字段进行说明, 服务端:Cache-Control、Expires、max-age、Last-Modified、ETag、stale-while-revalidate、must-revalidate、proxy-revalidate、no-cache、no-store;客户端:If-Modified-Since、If-None-Match;

服务端:

  • Cache-Control:控制是否进行缓存,Http1.1后才出现,可添加的配置信息如下:
    • no-cache或no-store,示列:Cache-Control:no-store或Cache-Control:no-cache表示不缓存数据
    • max-age:缓存有效期(单位秒);示列Cache-Control:max-age=60表示基于当前时间60秒后缓存数据过期
    • stale-while-revalidate:异步缓存机制(单位秒),当请求一个即将过期的文件时,会将本地的过期数据返回给用户,然后在异步请求数据,与must-revalidate或proxy-revalidate配套使用;示列:Cache-Control:stale-while-revalidate=1
    • must-revalidate或proxy-revalidate:表示是否启用stale-while-revalidate字段,有了该字段后stale-while-revalidate才会生效;示列:Cache-Control:stale-while-revalidate=1;must-revalidate
  • Expires:表示在该指定日期前缓存数据有效;示列:Expires:1451273426630
  • Last-Modified:最后一次修改时间,精确到秒;可表示某个文件或者某个请求返回数据的最后修改时间,与If-Modified-Since配套使用,服务端通过取If-Modified-Since值与需要返回的Last-Modified值进行对比判断是否返回304;示列:Last-Modified:1451273426630
  • ETag:表示某个请求或者某个文件的标记,如某个文件或者某个请求返回数据的md5值、hashcode值等,与If-None-Match配套使用,服务端通过取If-None-Match值与需要返回的ETag值进行对比判断是否返回304;示列:ETag:ee4f36eca94844eaebfcc2633f1a8de8

    客户端:

  • If-Modified-Since:最后一次修改时间;值取服务端Last-Modified值,在客户端header中设置该字段,服务端取值后与需要返回的Last-Modified值进行对比判断是否返回304
  • If-None-Match:缓存标识:值取服务端ETag值,在客户端的header中设置该字段,服务端取值后与需要返回的ETag值进行对比判断是否返回304

    Volley在BasicNetwork的addCacheHeaders方法中对If-Modified-Since和If-None-Match进行设置,代码如下:

private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }

        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }

        if (entry.lastModified > ) {
            Date refTime = new Date(entry.lastModified);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }
           

Volley对服务端header解析

Volly中对服务返回header信息的解析在HttpHeaderParser的parseCacheHeaders方法中,下面展现部分代码:

.........省略部分代码............
        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (int i = ; i < tokens.length; i++) {
                String token = tokens[i].trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring());
                    } catch (Exception e) {
                    }
                } else if (token.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring());
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }

        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            lastModified = parseDateAsEpoch(headerValue);
        }

        serverEtag = headers.get("ETag");

        // Cache-Control takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        if (hasCacheControl) {
            softExpire = now + maxAge * ;
            finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * ;
        } else if (serverDate >  && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }
.........省略部分代码............
           

从以上代码中可以看出Cache-Control的优先级要高于 Expires

Volley缓存过期判断

Volley的缓存逻辑处理在CacheDispatcher类的run方法中;

  1. 通过Cache.Entry entry = mCache.get(request.getCacheKey());取出缓存信息,如果不存在缓存信息,则将请求放入mNetworkQueue
  2. 通过 Cache.Entry 的isExpired()方法判断缓存是否过期;isExpired()方法代码如下:
/** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }
           

这里的ttl时候为header解析时的finalExpire时间,如果过期,则将请求放入mNetworkQueue;finalExpire取值如下:

// Cache-Control takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        if (hasCacheControl) {
            softExpire = now + maxAge * ;
            finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * ;
        } else if (serverDate >  && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }
           

可以看出finalExpire>=softExpire ,与stale-while-revalidate和must-revalidate或proxy-revalidate的设置有关。

3.通过Cache.Entry的refreshNeeded()方法判断是否需要刷新

/** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
           

这里的sotfTtl时间为softExpire,该方法的用途如下:

if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
                } else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
           

这段代码表示如果不需要刷新,这将数据直接返回缓存数据,否则,返回缓存数据的同时异步请求新的数据。

继续阅读