Volley源碼解析-一次完整的StringRequest請求(二)

本文是承接Volley源碼解析之---一次完整的StringRequest請求(1)的第二篇。上一篇文章两疚,主要介紹了NetworkDispatcher以及BasicNetwork等舆驶,如果沒有讀過上篇文章的建議先讀上一篇文章再讀這個熊昌,才能更好地連貫起來婿屹。接下來我們將繼續(xù)講解CacheDispatcherRequestQueue.add昂利。看看他們分別都干了些什么窝撵!

CacheDispatcher

在上一篇文章中我們提到碌奉,RequestQueueStart方法中開啟了CacheDispatcher赐劣。在我看來魁兼,它是用來協(xié)助緩存請求的咐汞。先來看看它的構(gòu)造函數(shù):

 /**
   * Creates a new cache triage dispatcher thread.  You must call {@link #start()}
   * in order to begin processing.
   *
   * @param cacheQueue Queue of incoming requests for triage
   * @param networkQueue Queue to post requests that require network to
   * @param cache Cache interface to use for resolution
   * @param delivery Delivery interface to use for posting responses
   */
  public CacheDispatcher(
          BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
          Cache cache, ResponseDelivery delivery) {
      mCacheQueue = cacheQueue;
      mNetworkQueue = networkQueue;
      mCache = cache;
      mDelivery = delivery;
  }

可以看到他其實就是比NetworkDispatcher多了一個mCacheQueue的阻塞隊列化撕,其他三個參數(shù)的意義和NetworkDispatcher的參數(shù)含義是一樣的植阴。這里我就不重復(fù)啰嗦了掠手。

接著我們看看它的run方法喷鸽。

 @Override
  public void run() {
      if (DEBUG) VolleyLog.v("start new dispatcher");
      //設(shè)置線程的優(yōu)先級
      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()) {
                  //判斷是否已經(jīng)被取消,如果被取消了那么就直接finish掉本次請求翻诉,進(jìn)行下一次請求
                  request.finish("cache-discard-canceled");
                  continue;
              }

              // Attempt to retrieve this item from cache. 根據(jù)緩存的key從緩存里面獲取緩存的數(shù)據(jù)
              Cache.Entry entry = mCache.get(request.getCacheKey());
              if (entry == null) {
                  //如果緩存中沒有找到key對應(yīng)的數(shù)據(jù),那么就將本次請求放入mNetworkQueue
                  request.addMarker("cache-miss");
                  // Cache miss; send off to the network dispatcher.
                  mNetworkQueue.put(request);
                  continue;
              }
              //緩存數(shù)據(jù)不為空芦圾。接下來做相應(yīng)的操作
              // If it is completely expired, just send it to the network.
              if (entry.isExpired()) {
                  //判斷緩存是否過期俄认,如果過期了个少,同樣將請求添加到mNetworkQueue但是同時給請求設(shè)置CacheEntry
                  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");
              //緩存命中眯杏,將緩存數(shù)據(jù)轉(zhuǎn)換成Response對象
              Response<?> response = request.parseNetworkResponse(
                      new NetworkResponse(entry.data, entry.responseHeaders));
              request.addMarker("cache-hit-parsed");
              //判斷緩存是否需要刷新夜焦。如果不需要刷新岂贩,直接通過mDelivery將請求結(jié)果回調(diào)給請求調(diào)用者
              if (!entry.refreshNeeded()) {
                  // Completely unexpired cache hit. Just deliver the response.
                  mDelivery.postResponse(request, response);
              } else {
                  //如果需要刷新茫经,則先直接將請求的結(jié)果回調(diào)給請求調(diào)用者萎津,但是同時將請求加入mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求
                  // 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;
              }
          }
      }
  }

同樣的上面我在必要的地方都添加了注釋卸伞,然后我們開始講解一個一個的重點锉屈。

  • 第一個mCache.initialize();從方法名可以知道是用來初始化什么東西的荤傲,那么這個方法都初始化了什么呢弃酌。讓我們跳到方法里面看看,注默認(rèn)的Cache是DiskBasedCache.
/**
    * 掃描當(dāng)前所有的緩存文件乌询,初始化DiskBasedCache,如果根目錄不存在就創(chuàng)建根目錄
    * Initializes the DiskBasedCache by scanning for all files currently in the
    * specified root directory. Creates the root directory if necessary.
    */
   @Override
   public synchronized void initialize() {
       if (!mRootDirectory.exists()) {
           if (!mRootDirectory.mkdirs()) {
               VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
           }
           return;
       }

       File[] files = mRootDirectory.listFiles();
       if (files == null) {
           return;
       }
       for (File file : files) {
           BufferedInputStream fis = null;
           try {
               fis = new BufferedInputStream(new FileInputStream(file));
               CacheHeader entry = CacheHeader.readHeader(fis);
               entry.size = file.length();
               putEntry(entry.key, entry);
           } catch (IOException e) {
               if (file != null) {
                  file.delete();
               }
           } finally {
               try {
                   if (fis != null) {
                       fis.close();
                   }
               } catch (IOException ignored) { }
           }
       }
   }

從上面的代碼我們可以看到,initialize就是通過遍歷緩存目錄中的文件鬼佣,將緩存的請求頭讀取到內(nèi)存中即mEntries變量中驶拱,以供之后查詢緩存使用。那么CacheHeader都包括了那些字段呢晶衷,一起來看看CacheHeader.readHeader(fis)方法蓝纲。

  /**
        * Reads the header off of an InputStream and returns a CacheHeader object.
        * @param is The InputStream to read from.
        * @throws IOException
        */
       public static CacheHeader readHeader(InputStream is) throws IOException {
           CacheHeader entry = new CacheHeader();
           int magic = readInt(is);
           if (magic != CACHE_MAGIC) {
               // don't bother deleting, it'll get pruned eventually
               throw new IOException();
           }
           //緩存標(biāo)示
           entry.key = readString(is);
           //資源的唯一標(biāo)示
           entry.etag = readString(is);
           if (entry.etag.equals("")) {
               entry.etag = null;
           }
           //
           entry.serverDate = readLong(is);
           //上一次修改的時間
           entry.lastModified = readLong(is);
           //硬過期時間,緩存無效
           entry.ttl = readLong(is);
           //軟過期時間晌纫,雖然過期了税迷,但是緩存還能使用
           entry.softTtl = readLong(is);
           //響應(yīng)頭
           entry.responseHeaders = readStringStringMap(is);

           return entry;
       }

可以看到CacheHeader除了保存了響應(yīng)頭之外,還保存了ttl時間以及softTtl時間锹漱,這兩個時間都是和緩存過期期限有關(guān)系的箭养,等會我們會更詳細(xì)的解釋,先記住有這么一個東西哥牍,lastModified是上一次資源的時間毕泌,我們可以利用這個來判斷服務(wù)器上的資源是否真的改變了。Etag是資源的唯一標(biāo)示砂心,也可以用來判斷資源是否過期懈词。關(guān)于EtaglastModified在什么情況下用來判斷資源是否過期以及如何判斷我們在上面一篇文章中已經(jīng)有詳細(xì)的說明,沒有看的同學(xué)找到這篇文章看一看哦辩诞,當(dāng)然如果你感興趣的話坎弯。想要知道緩存文件是如何存儲緩存數(shù)據(jù)的可以找到目錄里的緩存文件看看。

就這樣完成了初始化译暂,那么繼續(xù)抠忘,跟NetworkDispatcher一樣首先從阻塞隊列里面中取出一個請求,不過這個是``mCacheQueue里面取出Request外永,不是從mNetworkQueue里面取崎脉,因為這是緩存請求。同樣的由于是個阻塞隊列伯顶,所以如果沒有請求 那么就阻塞等待囚灼。跟NetworkDispatcher`一樣在處理請求之前骆膝,先判斷一下請求是否被取消,如果已經(jīng)被取消了灶体,那么就finish掉整個請求阅签,進(jìn)行下一次請求。代碼如下:

  // If the request has been canceled, don't bother dispatching it.
               if (request.isCanceled()) {
                   //判斷是否已經(jīng)被取消蝎抽,如果被取消了那么就直接finish掉本次請求政钟,進(jìn)行下一次請求
                   request.finish("cache-discard-canceled");
                   continue;
               }

當(dāng)獲取了請求之后,根據(jù)請求的CacheKey從緩存中取數(shù)據(jù)樟结,如果緩存命中养交,則使用緩存的數(shù)據(jù),如果命中緩存失敗瓢宦,則將請求添加進(jìn)mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求碎连。

// Attempt to retrieve this item from cache. 根據(jù)緩存的key從緩存里面獲取緩存的數(shù)據(jù)
               Cache.Entry entry = mCache.get(request.getCacheKey());
               if (entry == null) {
                   //如果緩存中沒有找到key對應(yīng)的數(shù)據(jù),那么就將本次請求放入mNetworkQueue
                   request.addMarker("cache-miss");
                   // Cache miss; send off to the network dispatcher.
                   mNetworkQueue.put(request);
                   continue;
               }

緩存命中(即有該請求的緩存數(shù)據(jù))驮履,則判斷緩存是否過期 entry.isExpired(),如果過期了那么就將請求加入mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求破花。首先讓我們來看看,entry.isExpired()這個方法疲吸。

      /** True if the entry is expired. */
       boolean isExpired() {
           return this.ttl < System.currentTimeMillis();
       }

原來這個方法就是判斷一下座每,ttl小于當(dāng)前時間,如果小于這說明緩存過期啦摘悴,應(yīng)該重新請求新的數(shù)據(jù)了峭梳。那么ttl是哪里來的,接下來我們還會看到softTtl,那么它又是什么決定的蹂喻,接下來我們一起看一下葱椭。 還記得我們這個方法Response<JSONObject> parseNetworkResponse(NetworkResponse response)嗎,這是我們將NetworkResponse轉(zhuǎn)換成Response的方法口四。因為我們是講解StringRequest請求孵运,所以來看看在轉(zhuǎn)換的過程中都干了些什么。

    @Override
   protected Response<String> parseNetworkResponse(NetworkResponse response) {
       String parsed;
       try {
           parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
       } catch (UnsupportedEncodingException e) {
           parsed = new String(response.data);
       }
       return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
   }

注意啦蔓彩。在return的時候調(diào)用了HttpHeaderParser.parseCacheHeaders(response)函數(shù)治笨。正如其名這個就是將Response的轉(zhuǎn)成CacheHeader。接下來一起來看看這個方法:

/**
    * Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}.
    *
    * @param response The network response to parse headers from
    * @return a cache entry for the given response, or null if the response is not cacheable.
    */
   public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
       long now = System.currentTimeMillis();

       Map<String, String> headers = response.headers;

       long serverDate = 0;
       long lastModified = 0;
       long serverExpires = 0;
       long softExpire = 0;
       long finalExpire = 0;
       long maxAge = 0;
       long staleWhileRevalidate = 0;
       boolean hasCacheControl = false;
       boolean mustRevalidate = false;

       String serverEtag = null;
       String headerValue;
       //獲取服務(wù)器時間
       headerValue = headers.get("Date");
       if (headerValue != null) {
           serverDate = parseDateAsEpoch(headerValue);
       }
       //獲取緩存控制字段
       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")) {
                   //如果`no-cache`或者`no-store`都是控制不使用緩存直接向服務(wù)器請求赤嚼,都表示則直接return null
                   return null;
               } else if (token.startsWith("max-age=")) {
                   //表示緩存在xxx秒之后過期旷赖。
                   try {
                       maxAge = Long.parseLong(token.substring(8));
                   } catch (Exception e) {
                   }
               } else if (token.startsWith("stale-while-revalidate=")) {
                   //緩存過期之后還能使用該緩存的時間額度
                   try {
                       staleWhileRevalidate = Long.parseLong(token.substring(23));
                   } catch (Exception e) {
                   }
               } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                   //必須在緩存過期之后立馬重新請求數(shù)據(jù)
                   mustRevalidate = true;
               }
           }
       }
       //獲取超期時間,不過這個是返回的服務(wù)器上面的時間為基準(zhǔn)的更卒,
       // 所以如果客戶端和服務(wù)器端時間相差很大等孵,那么就會很不標(biāo)準(zhǔn),
       // 所以后來在Cache-Control里面添加max-age來控制蹂空,max-age優(yōu)先級更高
       headerValue = headers.get("Expires");
       if (headerValue != null) {
           serverExpires = parseDateAsEpoch(headerValue);
       }
      //資源上一次修改的時間
       headerValue = headers.get("Last-Modified");
       if (headerValue != null) {
           lastModified = parseDateAsEpoch(headerValue);
       }
       //資源標(biāo)示
       serverEtag = headers.get("ETag");

       // Cache-Control takes precedence over an Expires header, even if both exist and Expires
       // is more restrictive.
       //Cache-Control優(yōu)先于Expires Header俯萌,所以先判斷是否存在CacheControl 并且沒有no-cache或no-store字段
       if (hasCacheControl) {
           //軟過期時間(即雖然緩存過期了但是仍然可以使用緩存的時間范圍)果录,軟超期時間等于現(xiàn)在的時間 + max-age * 1000
           softExpire = now + maxAge * 1000;
           //如果mustRevalidate存在,那么這個時候真正超期時間就等于軟過期時間
           //不存在的話咐熙,真正超期時間 = 軟過期時間 + staleWhileRevalidate * 1000
           finalExpire = mustRevalidate
                   ? softExpire
                   : softExpire + staleWhileRevalidate * 1000;
       } else if (serverDate > 0 && serverExpires >= serverDate) {
           //Expire頭部在HTTP協(xié)議中就是軟超期時間雕憔,所以這個時候真正超期時間 == 軟超期時間
           // Default semantic for Expire header in HTTP specification is softExpire.
           softExpire = now + (serverExpires - serverDate);
           finalExpire = softExpire;
       }

       Cache.Entry entry = new Cache.Entry();
       entry.data = response.data;
       entry.etag = serverEtag;
       entry.softTtl = softExpire;
       entry.ttl = finalExpire;
       entry.serverDate = serverDate;
       entry.lastModified = lastModified;
       entry.responseHeaders = headers;

       return entry;
   }

從上面這個方法我們可以看到緩存中每個字段是如何計算得到的,尤其是軟超期時間和真正超期的時間糖声。我總結(jié)一下:

  • 當(dāng)Response中有Cache-Control并且沒有no-cache以及no-store字段時,軟過期時間等于當(dāng)前時間+max-age * 1000 .如果存在must-revalidate或者proxy-revalidate時分瘦,則真正過期時間等于軟過期時間 + stale-while-revalidate
  • 如果CacheControl 不存在蘸泻,則軟過期時間 == 真正過期時間 == softExpire = now + (serverExpires - serverDate)
  • 不過值得注意的是,Expire的日期是根據(jù)服務(wù)器的時間來定的嘲玫,如果服務(wù)器和客戶端的時間相差很大的話那么時間就不一致了悦施。
    而且我們可以知道ttl是最終過期時間,softTtl是軟過期時間去团。所以entry.isExpired()true則說明緩存過期了抡诞,則需要重新請求網(wǎng)絡(luò),所以直接添加到mNetworkQueue里面進(jìn)行網(wǎng)絡(luò)請求土陪。有些同學(xué)可能覺得奇怪既然都要重新請求了為什么還要把緩存中的entry添加到reqeust這個對象呢昼汗,執(zhí)行request.setCacheEntry(entry);呢,在上一篇我們提到過雖然緩存過期了鬼雀,但是并不代表服務(wù)器上的資源真的改變了顷窒,所以這個時候?qū)⑸弦淮蔚?code>LastModified以及etag傳遞過去,可以用于服務(wù)器驗證資源是否過期的校驗源哩。接下來鞋吉,當(dāng)緩存沒有真正過期,則從緩存中拿出上一次響應(yīng)的數(shù)據(jù)励烦,然后轉(zhuǎn)換成Response對象谓着。但是現(xiàn)在還不能吧結(jié)果返回給請求調(diào)用者,還需要判斷一下坛掠,接著看entry.refreshNeeded();,從方法上來看赊锚,是判斷是否需要更新,具體看看是如何判斷的
 /** True if a refresh is needed from the original data source. */
        boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }

可以看到這個方法是根據(jù)軟過期時間(softTtl)來判斷是否需要刷新屉栓,上面我們提到過改抡,如果超過了軟過期時間,雖然緩存還是可以用的系瓢,但是需要同時請求服務(wù)器獲取新的數(shù)據(jù)阿纤,所以接下來就是根據(jù)是否需要刷新做不同的處理,如果不需要刷新那么就直接將Response回調(diào)給請求調(diào)用者夷陋,即StringRequestListener里面欠拾。同樣的 request.setCacheEntry(entry)便于服務(wù)器驗證胰锌,減少不必要的數(shù)據(jù)請求。
至此藐窄,我們把CacheDispatcherrun方法給理清楚了资昧。
接下來,我們還剩下一個分析點沒有分析了荆忍。那就是RequestQueue.add 對于這個方法格带,我自己一開始也有幾個疑問:

  1. add之后做了什么
  2. request添加進(jìn)去之后是不是通過CatchDispatcher來處理的似踱。

直接看代碼澳泵,才能找到答案束世,所以接下來我們一起來看看RequestQueue.add都做了什么吧炸卑。

RequestQueue

首先貼上add方法的源碼:

   /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        //設(shè)置這個請求屬于這個ReqeustQueue拳芙,并將這個請求添加到這個請求隊列的當(dāng)前請求隊列中
        //mCurrentRequests 是存放當(dāng)前所有的請求的一個集合
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        //給request設(shè)置序號
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        //判斷是否可以緩存囊颅,如果不可以湾戳,就直接將請求添加進(jìn)mNetworkQueue進(jìn)行網(wǎng)絡(luò)請求弟劲,默認(rèn)都是true蟋软,當(dāng)然你可以設(shè)成false
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        //將請求插入Map镶摘,如果這里已經(jīng)相同的cachekey的請求正在請求中
        synchronized (mWaitingRequests) {
            //mWaitingRequest 是存放同一個cacheKey請求的多個請求。
            String cacheKey = request.getCacheKey(); //獲取請求的cacheKey
            if (mWaitingRequests.containsKey(cacheKey)) {
                //已經(jīng)添加進(jìn)了隊列岳守,那么將request添加到 隊列中
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                //mWaitingRequests里面還不存在凄敢,cacheKey的請求,則將cacheKey放入mWaitingRequest并且將當(dāng)前請求加入mCacheQueue中
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

上面我已經(jīng)添加了對應(yīng)的注釋湿痢,首先我們將Request與當(dāng)前RequestQueue關(guān)聯(lián)起來贡未,然后將reqeust加入mCurrentRequests里面 我們可以看到因為每一個請求都會加進(jìn)去,所以這個集合就是記錄了所有的請求蒙袍。然后根據(jù)request是否可以緩存俊卤,如果不可緩存,那么直接添加到NetworkQueue進(jìn)行網(wǎng)絡(luò)請求害幅,否則繼續(xù)往下消恍。 可以緩存,那么先獲取到CacheKey,然后判斷mWaitingRequests中是否存在CacheKey.等等這個mWaitingRequests又是什么東東以现。在我看來狠怨,mWaitingRequests就是存儲相同Cachekey的Request,這樣可以避免同一種請求同時被添加進(jìn)mCacheQueue中邑遏,可以在第一個請求結(jié)束之后再添加進(jìn)cacheQueue隊列中佣赖,這樣之后的請求就可以直接從緩存中取,即快速又減少了網(wǎng)絡(luò)的重復(fù)訪問记盒。這樣是不是很好憎蛤。當(dāng)然如果現(xiàn)在mWaitingRequest里面不包括當(dāng)前請求CacheKey那么就添加進(jìn)mWaitingRequest,并且將請求添加到mCacheQueue進(jìn)行緩存請求。 其實Add方法很簡單俩檬,就是根據(jù) Request.shouldCache判斷應(yīng)該進(jìn)行網(wǎng)絡(luò)請求還是緩存請求萎胰。那么請求添加進(jìn)去了。你好不好奇棚辽,那些重復(fù)的Request什么時候加入CacheQueue或者怎么處理技竟,那么就應(yīng)該一起來看看RequestQueue.finish方法了。

    /**
     * Called from {@link Request#finish(String)}, indicating that processing of the given request
     * has finished.
     *
     * <p>Releases waiting requests for <code>request.getCacheKey()</code> if
     *      <code>request.shouldCache()</code>.</p>
     */
    <T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        //從當(dāng)前請求集合里面移除要finish掉的請求
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        //回調(diào)finish給Listener
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        //判斷request是否shouldCache
        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                //根據(jù)cacheKey 從mWaitingRequests中移除屈藐,同時將返回的相同cachekey的請求放入mCacheQueue中榔组,
                // 這樣只要緩存沒過期就可以從緩存中取數(shù)據(jù)
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    //從英文就可以知道,因為當(dāng)前request已經(jīng)請求過了 所以接下來的請求可以從緩存中拿響應(yīng)
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

看到了吧联逻,相同的CacheKey的請求就是在這里處理的哦搓扯。 終于終于把一次完整的StringRequest請求給講清楚了。在寫的過程中遣妥,我自己也對這個每一個點越來越理解,所以有時候如果你學(xué)習(xí)了一個新東西攀细,盡管網(wǎng)上有很多的資料了箫踩,但是你自己寫一下總結(jié)的文章,在寫的過程中谭贪,潛移默化中你掌握的更深本來以前不理解的地方也更加清晰境钟。所以鼓勵大家都寫起來,不為其他俭识,為了讓自己真正掌握慨削,俗話說好記性不如爛筆頭。
哈哈套媚,扯得多了點缚态,但是還沒完呢,最后一個流程圖堤瘤,給我和你們一起縷縷整個過程玫芦。見流程圖。

整體流程圖

TIM圖片20170807193054.png

CacheDispatcher

CacheDispatcher.png

NetworkDispatcher

NetworkDipatcher.png

好了本辐,各位Volley源碼的分析就到此結(jié)束了桥帆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市慎皱,隨后出現(xiàn)的幾起案子老虫,更是在濱河造成了極大的恐慌,老刑警劉巖茫多,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祈匙,死亡現(xiàn)場離奇詭異,居然都是意外死亡天揖,警方通過查閱死者的電腦和手機(jī)菊卷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門缔恳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洁闰,你說我怎么就攤上這事歉甚。” “怎么了扑眉?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵纸泄,是天一觀的道長。 經(jīng)常有香客問我腰素,道長聘裁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任弓千,我火速辦了婚禮衡便,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洋访。我一直安慰自己镣陕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布姻政。 她就那樣靜靜地躺著呆抑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汁展。 梳的紋絲不亂的頭發(fā)上鹊碍,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音食绿,去河邊找鬼侈咕。 笑死,一個胖子當(dāng)著我的面吹牛器紧,可吹牛的內(nèi)容都是我干的乎完。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼品洛,長吁一口氣:“原來是場噩夢啊……” “哼树姨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起桥状,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤帽揪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辅斟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體转晰,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了查邢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔗崎。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扰藕,靈堂內(nèi)的尸體忽然破棺而出缓苛,到底是詐尸還是另有隱情,我是刑警寧澤邓深,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布未桥,位于F島的核電站,受9級特大地震影響芥备,放射性物質(zhì)發(fā)生泄漏冬耿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一萌壳、第九天 我趴在偏房一處隱蔽的房頂上張望亦镶。 院中可真熱鬧,春花似錦袱瓮、人聲如沸缤骨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荷憋。三九已至台颠,卻和暖如春褐望,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背串前。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工瘫里, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荡碾。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓谨读,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坛吁。 傳聞我的和親對象是個殘疾皇子劳殖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容