diff options
Diffstat (limited to 'src/main/java/com/android/volley/CacheDispatcher.java')
-rw-r--r-- | src/main/java/com/android/volley/CacheDispatcher.java | 151 |
1 files changed, 137 insertions, 14 deletions
diff --git a/src/main/java/com/android/volley/CacheDispatcher.java b/src/main/java/com/android/volley/CacheDispatcher.java index 1e7dfc4..51dfd9c 100644 --- a/src/main/java/com/android/volley/CacheDispatcher.java +++ b/src/main/java/com/android/volley/CacheDispatcher.java @@ -18,6 +18,10 @@ package com.android.volley; import android.os.Process; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.BlockingQueue; /** @@ -48,6 +52,9 @@ public class CacheDispatcher extends Thread { /** Used for telling us to die. */ private volatile boolean mQuit = false; + /** Manage list of waiting requests and de-duplicate requests with same cache key. */ + private final WaitingRequestManager mWaitingRequestManager; + /** * Creates a new cache triage dispatcher thread. You must call {@link #start()} * in order to begin processing. @@ -64,6 +71,7 @@ public class CacheDispatcher extends Thread { mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; + mWaitingRequestManager = new WaitingRequestManager(this); } /** @@ -101,7 +109,9 @@ public class CacheDispatcher extends Thread { if (entry == null) { request.addMarker("cache-miss"); // Cache miss; send off to the network dispatcher. - mNetworkQueue.put(request); + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + mNetworkQueue.put(request); + } continue; } @@ -109,7 +119,9 @@ public class CacheDispatcher extends Thread { if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); - mNetworkQueue.put(request); + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + mNetworkQueue.put(request); + } continue; } @@ -128,22 +140,28 @@ public class CacheDispatcher extends Thread { // 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. + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + // 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) { + // Restore the interrupted status + Thread.currentThread().interrupt(); + } } - } - }); + }); + } else { + // request has been added to list of waiting requests + // to receive the network response from the first request once it returns. + mDelivery.postResponse(request, response); + } } } catch (InterruptedException e) { @@ -154,4 +172,109 @@ public class CacheDispatcher extends Thread { } } } + + private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener { + + /** + * Staging area for requests that already have a duplicate request in flight. + * + * <ul> + * <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache + * key.</li> + * <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request + * is <em>not</em> contained in that list. Is null if no requests are staged.</li> + * </ul> + */ + private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>(); + + private final CacheDispatcher mCacheDispatcher; + + WaitingRequestManager(CacheDispatcher cacheDispatcher) { + mCacheDispatcher = cacheDispatcher; + } + + /** Request received a valid response that can be used by other waiting requests. */ + @Override + public void onResponseReceived(Request<?> request, Response<?> response) { + if (response.cacheEntry == null || response.cacheEntry.isExpired()) { + onNoUsableResponseReceived(request); + return; + } + String cacheKey = request.getCacheKey(); + List<Request<?>> waitingRequests; + synchronized (this) { + 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. + for (Request<?> waiting : waitingRequests) { + mCacheDispatcher.mDelivery.postResponse(waiting, response); + } + } + } + + /** No valid response received from network, release waiting requests. */ + @Override + public synchronized void onNoUsableResponseReceived(Request<?> request) { + String cacheKey = request.getCacheKey(); + List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); + if (waitingRequests != null && !waitingRequests.isEmpty()) { + if (VolleyLog.DEBUG) { + VolleyLog.v("%d waiting requests for cacheKey=%s; resend to network", + waitingRequests.size(), cacheKey); + } + Request<?> nextInLine = waitingRequests.remove(0); + mWaitingRequests.put(cacheKey, waitingRequests); + try { + mCacheDispatcher.mNetworkQueue.put(nextInLine); + } catch (InterruptedException iex) { + VolleyLog.e("Couldn't add request to queue. %s", iex.toString()); + // Restore the interrupted status of the calling thread (i.e. NetworkDispatcher) + Thread.currentThread().interrupt(); + // Quit the current CacheDispatcher thread. + mCacheDispatcher.quit(); + } + } + } + + /** + * For cacheable requests, if a request for the same cache key is already in flight, + * add it to a queue to wait for that in-flight request to finish. + * @return whether the request was queued. If false, we should continue issuing the request + * over the network. If true, we should put the request on hold to be processed when + * the in-flight request finishes. + */ + private synchronized boolean maybeAddToWaitingRequests(Request<?> request) { + String cacheKey = request.getCacheKey(); + // Insert request into stage if there's already a request with the same cache key + // in flight. + if (mWaitingRequests.containsKey(cacheKey)) { + // There is already a request in flight. Queue up. + List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); + if (stagedRequests == null) { + stagedRequests = new ArrayList<Request<?>>(); + } + request.addMarker("waiting-for-response"); + stagedRequests.add(request); + mWaitingRequests.put(cacheKey, stagedRequests); + if (VolleyLog.DEBUG) { + VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); + } + return true; + } else { + // Insert 'null' queue for this cacheKey, indicating there is now a request in + // flight. + mWaitingRequests.put(cacheKey, null); + request.setNetworkRequestCompleteListener(this); + if (VolleyLog.DEBUG) { + VolleyLog.d("new request, sending to network %s", cacheKey); + } + return false; + } + } + } } |