aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/volley/CacheDispatcher.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/android/volley/CacheDispatcher.java')
-rw-r--r--src/main/java/com/android/volley/CacheDispatcher.java151
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;
+ }
+ }
+ }
}