1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley; 18 19 import android.os.Handler; 20 import android.os.Looper; 21 import androidx.annotation.IntDef; 22 import java.lang.annotation.Retention; 23 import java.lang.annotation.RetentionPolicy; 24 import java.util.ArrayList; 25 import java.util.HashSet; 26 import java.util.List; 27 import java.util.Set; 28 import java.util.concurrent.PriorityBlockingQueue; 29 import java.util.concurrent.atomic.AtomicInteger; 30 31 /** 32 * A request dispatch queue with a thread pool of dispatchers. 33 * 34 * <p>Calling {@link #add(Request)} will enqueue the given Request for dispatch, resolving from 35 * either cache or network on a worker thread, and then delivering a parsed response on the main 36 * thread. 37 */ 38 public class RequestQueue { 39 40 /** Callback interface for completed requests. */ 41 // TODO: This should not be a generic class, because the request type can't be determined at 42 // compile time, so all calls to onRequestFinished are unsafe. However, changing this would be 43 // an API-breaking change. See also: https://github.com/google/volley/pull/109 44 @Deprecated // Use RequestEventListener instead. 45 public interface RequestFinishedListener<T> { 46 /** Called when a request has finished processing. */ onRequestFinished(Request<T> request)47 void onRequestFinished(Request<T> request); 48 } 49 50 /** Request event types the listeners {@link RequestEventListener} will be notified about. */ 51 @Retention(RetentionPolicy.SOURCE) 52 @IntDef({ 53 RequestEvent.REQUEST_QUEUED, 54 RequestEvent.REQUEST_CACHE_LOOKUP_STARTED, 55 RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED, 56 RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED, 57 RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED, 58 RequestEvent.REQUEST_FINISHED 59 }) 60 public @interface RequestEvent { 61 /** The request was added to the queue. */ 62 public static final int REQUEST_QUEUED = 0; 63 /** Cache lookup started for the request. */ 64 public static final int REQUEST_CACHE_LOOKUP_STARTED = 1; 65 /** 66 * Cache lookup finished for the request and cached response is delivered or request is 67 * queued for network dispatching. 68 */ 69 public static final int REQUEST_CACHE_LOOKUP_FINISHED = 2; 70 /** Network dispatch started for the request. */ 71 public static final int REQUEST_NETWORK_DISPATCH_STARTED = 3; 72 /** The network dispatch finished for the request and response (if any) is delivered. */ 73 public static final int REQUEST_NETWORK_DISPATCH_FINISHED = 4; 74 /** 75 * All the work associated with the request is finished and request is removed from all the 76 * queues. 77 */ 78 public static final int REQUEST_FINISHED = 5; 79 } 80 81 /** Callback interface for request life cycle events. */ 82 public interface RequestEventListener { 83 /** 84 * Called on every request lifecycle event. Can be called from different threads. The call 85 * is blocking request processing, so any processing should be kept at minimum or moved to 86 * another thread. 87 */ onRequestEvent(Request<?> request, @RequestEvent int event)88 void onRequestEvent(Request<?> request, @RequestEvent int event); 89 } 90 91 /** Used for generating monotonically-increasing sequence numbers for requests. */ 92 private final AtomicInteger mSequenceGenerator = new AtomicInteger(); 93 94 /** 95 * The set of all requests currently being processed by this RequestQueue. A Request will be in 96 * this set if it is waiting in any queue or currently being processed by any dispatcher. 97 */ 98 private final Set<Request<?>> mCurrentRequests = new HashSet<>(); 99 100 /** The cache triage queue. */ 101 private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>(); 102 103 /** The queue of requests that are actually going out to the network. */ 104 private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>(); 105 106 /** Number of network request dispatcher threads to start. */ 107 private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; 108 109 /** Cache interface for retrieving and storing responses. */ 110 private final Cache mCache; 111 112 /** Network interface for performing requests. */ 113 private final Network mNetwork; 114 115 /** Response delivery mechanism. */ 116 private final ResponseDelivery mDelivery; 117 118 /** The network dispatchers. */ 119 private final NetworkDispatcher[] mDispatchers; 120 121 /** The cache dispatcher. */ 122 private CacheDispatcher mCacheDispatcher; 123 124 private final List<RequestFinishedListener> mFinishedListeners = new ArrayList<>(); 125 126 /** Collection of listeners for request life cycle events. */ 127 private final List<RequestEventListener> mEventListeners = new ArrayList<>(); 128 129 /** 130 * Creates the worker pool. Processing will not begin until {@link #start()} is called. 131 * 132 * @param cache A Cache to use for persisting responses to disk 133 * @param network A Network interface for performing HTTP requests 134 * @param threadPoolSize Number of network dispatcher threads to create 135 * @param delivery A ResponseDelivery interface for posting responses and errors 136 */ RequestQueue( Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery)137 public RequestQueue( 138 Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { 139 mCache = cache; 140 mNetwork = network; 141 mDispatchers = new NetworkDispatcher[threadPoolSize]; 142 mDelivery = delivery; 143 } 144 145 /** 146 * Creates the worker pool. Processing will not begin until {@link #start()} is called. 147 * 148 * @param cache A Cache to use for persisting responses to disk 149 * @param network A Network interface for performing HTTP requests 150 * @param threadPoolSize Number of network dispatcher threads to create 151 */ RequestQueue(Cache cache, Network network, int threadPoolSize)152 public RequestQueue(Cache cache, Network network, int threadPoolSize) { 153 this( 154 cache, 155 network, 156 threadPoolSize, 157 new ExecutorDelivery(new Handler(Looper.getMainLooper()))); 158 } 159 160 /** 161 * Creates the worker pool. Processing will not begin until {@link #start()} is called. 162 * 163 * @param cache A Cache to use for persisting responses to disk 164 * @param network A Network interface for performing HTTP requests 165 */ RequestQueue(Cache cache, Network network)166 public RequestQueue(Cache cache, Network network) { 167 this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); 168 } 169 170 /** Starts the dispatchers in this queue. */ start()171 public void start() { 172 stop(); // Make sure any currently running dispatchers are stopped. 173 // Create the cache dispatcher and start it. 174 mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); 175 mCacheDispatcher.start(); 176 177 // Create network dispatchers (and corresponding threads) up to the pool size. 178 for (int i = 0; i < mDispatchers.length; i++) { 179 NetworkDispatcher networkDispatcher = 180 new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); 181 mDispatchers[i] = networkDispatcher; 182 networkDispatcher.start(); 183 } 184 } 185 186 /** Stops the cache and network dispatchers. */ stop()187 public void stop() { 188 if (mCacheDispatcher != null) { 189 mCacheDispatcher.quit(); 190 } 191 for (final NetworkDispatcher mDispatcher : mDispatchers) { 192 if (mDispatcher != null) { 193 mDispatcher.quit(); 194 } 195 } 196 } 197 198 /** Gets a sequence number. */ getSequenceNumber()199 public int getSequenceNumber() { 200 return mSequenceGenerator.incrementAndGet(); 201 } 202 203 /** Gets the {@link Cache} instance being used. */ getCache()204 public Cache getCache() { 205 return mCache; 206 } 207 208 /** 209 * A simple predicate or filter interface for Requests, for use by {@link 210 * RequestQueue#cancelAll(RequestFilter)}. 211 */ 212 public interface RequestFilter { apply(Request<?> request)213 boolean apply(Request<?> request); 214 } 215 216 /** 217 * Cancels all requests in this queue for which the given filter applies. 218 * 219 * @param filter The filtering function to use 220 */ cancelAll(RequestFilter filter)221 public void cancelAll(RequestFilter filter) { 222 synchronized (mCurrentRequests) { 223 for (Request<?> request : mCurrentRequests) { 224 if (filter.apply(request)) { 225 request.cancel(); 226 } 227 } 228 } 229 } 230 231 /** 232 * Cancels all requests in this queue with the given tag. Tag must be non-null and equality is 233 * by identity. 234 */ cancelAll(final Object tag)235 public void cancelAll(final Object tag) { 236 if (tag == null) { 237 throw new IllegalArgumentException("Cannot cancelAll with a null tag"); 238 } 239 cancelAll( 240 new RequestFilter() { 241 @Override 242 public boolean apply(Request<?> request) { 243 return request.getTag() == tag; 244 } 245 }); 246 } 247 248 /** 249 * Adds a Request to the dispatch queue. 250 * 251 * @param request The request to service 252 * @return The passed-in request 253 */ add(Request<T> request)254 public <T> Request<T> add(Request<T> request) { 255 // Tag the request as belonging to this queue and add it to the set of current requests. 256 request.setRequestQueue(this); 257 synchronized (mCurrentRequests) { 258 mCurrentRequests.add(request); 259 } 260 261 // Process requests in the order they are added. 262 request.setSequence(getSequenceNumber()); 263 request.addMarker("add-to-queue"); 264 sendRequestEvent(request, RequestEvent.REQUEST_QUEUED); 265 266 // If the request is uncacheable, skip the cache queue and go straight to the network. 267 if (!request.shouldCache()) { 268 mNetworkQueue.add(request); 269 return request; 270 } 271 mCacheQueue.add(request); 272 return request; 273 } 274 275 /** 276 * Called from {@link Request#finish(String)}, indicating that processing of the given request 277 * has finished. 278 */ 279 @SuppressWarnings("unchecked") // see above note on RequestFinishedListener finish(Request<T> request)280 <T> void finish(Request<T> request) { 281 // Remove from the set of requests currently being processed. 282 synchronized (mCurrentRequests) { 283 mCurrentRequests.remove(request); 284 } 285 synchronized (mFinishedListeners) { 286 for (RequestFinishedListener<T> listener : mFinishedListeners) { 287 listener.onRequestFinished(request); 288 } 289 } 290 sendRequestEvent(request, RequestEvent.REQUEST_FINISHED); 291 } 292 293 /** Sends a request life cycle event to the listeners. */ sendRequestEvent(Request<?> request, @RequestEvent int event)294 void sendRequestEvent(Request<?> request, @RequestEvent int event) { 295 synchronized (mEventListeners) { 296 for (RequestEventListener listener : mEventListeners) { 297 listener.onRequestEvent(request, event); 298 } 299 } 300 } 301 302 /** Add a listener for request life cycle events. */ addRequestEventListener(RequestEventListener listener)303 public void addRequestEventListener(RequestEventListener listener) { 304 synchronized (mEventListeners) { 305 mEventListeners.add(listener); 306 } 307 } 308 309 /** Remove a listener for request life cycle events. */ removeRequestEventListener(RequestEventListener listener)310 public void removeRequestEventListener(RequestEventListener listener) { 311 synchronized (mEventListeners) { 312 mEventListeners.remove(listener); 313 } 314 } 315 316 @Deprecated // Use RequestEventListener instead. addRequestFinishedListener(RequestFinishedListener<T> listener)317 public <T> void addRequestFinishedListener(RequestFinishedListener<T> listener) { 318 synchronized (mFinishedListeners) { 319 mFinishedListeners.add(listener); 320 } 321 } 322 323 /** Remove a RequestFinishedListener. Has no effect if listener was not previously added. */ 324 @Deprecated // Use RequestEventListener instead. removeRequestFinishedListener(RequestFinishedListener<T> listener)325 public <T> void removeRequestFinishedListener(RequestFinishedListener<T> listener) { 326 synchronized (mFinishedListeners) { 327 mFinishedListeners.remove(listener); 328 } 329 } 330 } 331