Volley Cache机制分析

1.http缓存机制

要弄明白volley缓存机制,那么肯定是和浏览器的缓存机制有关了,简单来说volley整套框架要做的事都是模拟浏览器来进行一次次的http交互

1.1.概述

http缓存的是指当Web请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是从原始服务器中提取这个文档。

1.2.与缓存有关的头信息

1.2.1.request:

  • Cache-Control: max-age=0 以秒为单位
  • If-Modified-Since: Mon, 19 Nov 2012 08:38:01 GMT 缓存文件的最后修改时间。
  • If-None-Match: "0693f67a67cc1:0" 缓存文件的Etag值
  • Cache-Control: no-cache 不使用缓存
  • Pragma: no-cache 不使用缓存

1.2.2.response:

  • Cache-Control: public 响应被缓存,并且在多用户间共享
  • Cache-Control: private 响应只能作为私有缓存,不能在用户之间共享
  • Cache-Control:no-cache 提醒浏览器要从服务器提取文档进行验证
  • Cache-Control:no-store 绝对禁止缓存(用于机密,敏感文件)
  • Cache-Control: max-age=60 60秒之后缓存过期(相对时间)
  • Date: Mon, 19 Nov 2012 08:39:00 GMT 当前response发送的时间
  • Expires: Mon, 19 Nov 2012 08:40:01 GMT 缓存过期的时间(绝对时间)
  • Last-Modified: Mon, 19 Nov 2012 08:38:01 GMT 服务器端文件的最后修改时间
  • ETag: "20b1add7ec1cd1:0" 服务器端文件的Etag值

如果同时存在cache-control和Expires怎么办呢?

优先使用cache-control,如果没有cache-control才考虑Expires

2.Volley缓存机制

1.当你add进来一个request的时候,其会根据request.shouldCache(默认为true)进行分发决定是交给NetworkDispatcher还是CacheDispatcher处理

2.NetworkDispatcher通过Network请求数据, 如果有缓存的头信息,会一起发送给服务器

    // Perform the network request.
 NetworkResponse networkResponse = mNetwork.performRequest(request);

public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
                .....
                .....
                .....
                addCacheHeaders(headers, request.getCacheEntry());
                .....
                .....
                .....
        }
    }

3.解析NetworkResponse

 Response<?> response = request.parseNetworkResponse(networkResponse);

在这,得在自定义的request中调用很重要的一个静态方法

HttpHeaderParser.parseCacheHeaders(response)

这个方法主要是将NetworkResponse进行封装成一个Cache.Entry对象

 public static Cache.Entry  parseCacheHeaders(NetworkResponse response) {
        //当前系统时间
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long maxAge = 0;
        boolean hasCacheControl = false;
        String serverEtag = null;
        String headerValue;
        headerValue = headers.get("Date");
        if (headerValue != null) {
            //服务器时间
            serverDate = parseDateAsEpoch(headerValue);
        }
        //获取Cache-Control信息
        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (int i = 0; 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(8));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    maxAge = 0;
                }
            }
        }
        headerValue = headers.get("Expires");
        if (headerValue != null) {
            //过期时间(绝对时间)
            serverExpires = parseDateAsEpoch(headerValue);
        }
        //ETag
        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 * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
    // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
        }
        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = entry.softTtl;
        entry.serverDate = serverDate;
        entry.responseHeaders = headers;
        return entry;
    }

4.在NetworkDispatcher中,再根据其shouldCache和是否有缓存实体来判断是否要进行缓存操作

    if (request.shouldCache() && response.cacheEntry != null) {
       mCache.put(request.getCacheKey(), response.cacheEntry);
   request.addMarker("network-cache-written"); }

5.CacheDispatcher的处理

当收到一个request之后,会先到Cache里面去取,看下是否有缓存,
当出现没有缓存 和 缓存过期的情况就直接丢给NetworkDispatcher来处理;
如果缓存没过期,直接拿到缓存实体丢给request的parseNetworkResponse方法这里调用就和NetworkDispatcher里面处理差不多了;

 @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

// Make a blocking call to initialize the cache.
        mCache.initialize();
        while (true) {
            try {
    // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
     // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
    // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");
                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.
                            }
                        }
                    });
                }
            } catch (InterruptedException e) {
         // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
原文地址:https://www.cnblogs.com/adison/p/4039312.html