diff options
Diffstat (limited to 'core/src/main/java/com/android/volley/CacheDispatcher.java')
-rw-r--r-- | core/src/main/java/com/android/volley/CacheDispatcher.java | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/core/src/main/java/com/android/volley/CacheDispatcher.java b/core/src/main/java/com/android/volley/CacheDispatcher.java new file mode 100644 index 0000000..4443143 --- /dev/null +++ b/core/src/main/java/com/android/volley/CacheDispatcher.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.volley; + +import android.os.Process; +import androidx.annotation.VisibleForTesting; +import java.util.concurrent.BlockingQueue; + +/** + * Provides a thread for performing cache triage on a queue of requests. + * + * <p>Requests added to the specified cache queue are resolved from cache. Any deliverable response + * is posted back to the caller via a {@link ResponseDelivery}. Cache misses and responses that + * require refresh are enqueued on the specified network queue for processing by a {@link + * NetworkDispatcher}. + */ +public class CacheDispatcher extends Thread { + + private static final boolean DEBUG = VolleyLog.DEBUG; + + /** The queue of requests coming in for triage. */ + private final BlockingQueue<Request<?>> mCacheQueue; + + /** The queue of requests going out to the network. */ + private final BlockingQueue<Request<?>> mNetworkQueue; + + /** The cache to read from. */ + private final Cache mCache; + + /** For posting responses. */ + private final ResponseDelivery mDelivery; + + /** 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. + * + * @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; + mWaitingRequestManager = new WaitingRequestManager(this, networkQueue, delivery); + } + + /** + * Forces this dispatcher to quit immediately. If any requests are still in the queue, they are + * not guaranteed to be processed. + */ + public void quit() { + mQuit = true; + interrupt(); + } + + @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 { + processRequest(); + } catch (InterruptedException e) { + // We may have been interrupted because it was time to quit. + if (mQuit) { + Thread.currentThread().interrupt(); + return; + } + VolleyLog.e( + "Ignoring spurious interrupt of CacheDispatcher thread; " + + "use quit() to terminate it"); + } + } + } + + // Extracted to its own method to ensure locals have a constrained liveness scope by the GC. + // This is needed to avoid keeping previous request references alive for an indeterminate amount + // of time. Update consumer-proguard-rules.pro when modifying this. See also + // https://github.com/google/volley/issues/114 + private void processRequest() throws InterruptedException { + // Get a request from the cache triage queue, blocking until + // at least one is available. + final Request<?> request = mCacheQueue.take(); + processRequest(request); + } + + @VisibleForTesting + void processRequest(final Request<?> request) throws InterruptedException { + request.addMarker("cache-queue-take"); + request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); + + try { + // If the request has been canceled, don't bother dispatching it. + if (request.isCanceled()) { + request.finish("cache-discard-canceled"); + return; + } + + // 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. + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + mNetworkQueue.put(request); + } + return; + } + + // Use a single instant to evaluate cache expiration. Otherwise, a cache entry with + // identical soft and hard TTL times may appear to be valid when checking isExpired but + // invalid upon checking refreshNeeded(), triggering a soft TTL refresh which should be + // impossible. + long currentTimeMillis = System.currentTimeMillis(); + + // If it is completely expired, just send it to the network. + if (entry.isExpired(currentTimeMillis)) { + request.addMarker("cache-hit-expired"); + request.setCacheEntry(entry); + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + mNetworkQueue.put(request); + } + return; + } + + // 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 (!response.isSuccess()) { + request.addMarker("cache-parsing-failed"); + mCache.invalidate(request.getCacheKey(), true); + request.setCacheEntry(null); + if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { + mNetworkQueue.put(request); + } + return; + } + if (!entry.refreshNeeded(currentTimeMillis)) { + // 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; + + 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); + } + } + } finally { + request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED); + } + } +} |