/* * 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.Handler; import android.os.Looper; import androidx.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; /** * A request dispatch queue with a thread pool of dispatchers. * *

Calling {@link #add(Request)} will enqueue the given Request for dispatch, resolving from * either cache or network on a worker thread, and then delivering a parsed response on the main * thread. */ public class RequestQueue { /** Callback interface for completed requests. */ // TODO: This should not be a generic class, because the request type can't be determined at // compile time, so all calls to onRequestFinished are unsafe. However, changing this would be // an API-breaking change. See also: https://github.com/google/volley/pull/109 @Deprecated // Use RequestEventListener instead. public interface RequestFinishedListener { /** Called when a request has finished processing. */ void onRequestFinished(Request request); } /** Request event types the listeners {@link RequestEventListener} will be notified about. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ RequestEvent.REQUEST_QUEUED, RequestEvent.REQUEST_CACHE_LOOKUP_STARTED, RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED, RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED, RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED, RequestEvent.REQUEST_FINISHED }) public @interface RequestEvent { /** The request was added to the queue. */ public static final int REQUEST_QUEUED = 0; /** Cache lookup started for the request. */ public static final int REQUEST_CACHE_LOOKUP_STARTED = 1; /** * Cache lookup finished for the request and cached response is delivered or request is * queued for network dispatching. */ public static final int REQUEST_CACHE_LOOKUP_FINISHED = 2; /** Network dispatch started for the request. */ public static final int REQUEST_NETWORK_DISPATCH_STARTED = 3; /** The network dispatch finished for the request and response (if any) is delivered. */ public static final int REQUEST_NETWORK_DISPATCH_FINISHED = 4; /** * All the work associated with the request is finished and request is removed from all the * queues. */ public static final int REQUEST_FINISHED = 5; } /** Callback interface for request life cycle events. */ public interface RequestEventListener { /** * Called on every request lifecycle event. Can be called from different threads. The call * is blocking request processing, so any processing should be kept at minimum or moved to * another thread. */ void onRequestEvent(Request request, @RequestEvent int event); } /** Used for generating monotonically-increasing sequence numbers for requests. */ private final AtomicInteger mSequenceGenerator = new AtomicInteger(); /** * The set of all requests currently being processed by this RequestQueue. A Request will be in * this set if it is waiting in any queue or currently being processed by any dispatcher. */ private final Set> mCurrentRequests = new HashSet<>(); /** The cache triage queue. */ private final PriorityBlockingQueue> mCacheQueue = new PriorityBlockingQueue<>(); /** The queue of requests that are actually going out to the network. */ private final PriorityBlockingQueue> mNetworkQueue = new PriorityBlockingQueue<>(); /** Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; /** Cache interface for retrieving and storing responses. */ private final Cache mCache; /** Network interface for performing requests. */ private final Network mNetwork; /** Response delivery mechanism. */ private final ResponseDelivery mDelivery; /** The network dispatchers. */ private final NetworkDispatcher[] mDispatchers; /** The cache dispatcher. */ private CacheDispatcher mCacheDispatcher; private final List mFinishedListeners = new ArrayList<>(); /** Collection of listeners for request life cycle events. */ private final List mEventListeners = new ArrayList<>(); /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create * @param delivery A ResponseDelivery interface for posting responses and errors */ public RequestQueue( Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create */ public RequestQueue(Cache cache, Network network, int threadPoolSize) { this( cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests */ public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } /** Starts the dispatchers in this queue. */ public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } /** Stops the cache and network dispatchers. */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (final NetworkDispatcher mDispatcher : mDispatchers) { if (mDispatcher != null) { mDispatcher.quit(); } } } /** Gets a sequence number. */ public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); } /** Gets the {@link Cache} instance being used. */ public Cache getCache() { return mCache; } /** * A simple predicate or filter interface for Requests, for use by {@link * RequestQueue#cancelAll(RequestFilter)}. */ public interface RequestFilter { boolean apply(Request request); } /** * Cancels all requests in this queue for which the given filter applies. * * @param filter The filtering function to use */ public void cancelAll(RequestFilter filter) { synchronized (mCurrentRequests) { for (Request request : mCurrentRequests) { if (filter.apply(request)) { request.cancel(); } } } } /** * Cancels all requests in this queue with the given tag. Tag must be non-null and equality is * by identity. */ public void cancelAll(final Object tag) { if (tag == null) { throw new IllegalArgumentException("Cannot cancelAll with a null tag"); } cancelAll( new RequestFilter() { @Override public boolean apply(Request request) { return request.getTag() == tag; } }); } /** * Adds a Request to the dispatch queue. * * @param request The request to service * @return The passed-in request */ public Request add(Request request) { // Tag the request as belonging to this queue and add it to the set of current requests. request.setRequestQueue(this); synchronized (mCurrentRequests) { mCurrentRequests.add(request); } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); sendRequestEvent(request, RequestEvent.REQUEST_QUEUED); beginRequest(request); return request; } void beginRequest(Request request) { // If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { sendRequestOverNetwork(request); } else { mCacheQueue.add(request); } } /** * Called from {@link Request#finish(String)}, indicating that processing of the given request * has finished. */ @SuppressWarnings("unchecked") // see above note on RequestFinishedListener void finish(Request request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } synchronized (mFinishedListeners) { for (RequestFinishedListener listener : mFinishedListeners) { listener.onRequestFinished(request); } } sendRequestEvent(request, RequestEvent.REQUEST_FINISHED); } /** Sends a request life cycle event to the listeners. */ void sendRequestEvent(Request request, @RequestEvent int event) { synchronized (mEventListeners) { for (RequestEventListener listener : mEventListeners) { listener.onRequestEvent(request, event); } } } /** Add a listener for request life cycle events. */ public void addRequestEventListener(RequestEventListener listener) { synchronized (mEventListeners) { mEventListeners.add(listener); } } /** Remove a listener for request life cycle events. */ public void removeRequestEventListener(RequestEventListener listener) { synchronized (mEventListeners) { mEventListeners.remove(listener); } } @Deprecated // Use RequestEventListener instead. public void addRequestFinishedListener(RequestFinishedListener listener) { synchronized (mFinishedListeners) { mFinishedListeners.add(listener); } } /** Remove a RequestFinishedListener. Has no effect if listener was not previously added. */ @Deprecated // Use RequestEventListener instead. public void removeRequestFinishedListener(RequestFinishedListener listener) { synchronized (mFinishedListeners) { mFinishedListeners.remove(listener); } } public ResponseDelivery getResponseDelivery() { return mDelivery; } void sendRequestOverNetwork(Request request) { mNetworkQueue.add(request); } }