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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM