• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.TargetApi;
20 import android.net.TrafficStats;
21 import android.os.Build;
22 import android.os.Process;
23 import android.os.SystemClock;
24 import androidx.annotation.VisibleForTesting;
25 import java.util.concurrent.BlockingQueue;
26 
27 /**
28  * Provides a thread for performing network dispatch from a queue of requests.
29  *
30  * <p>Requests added to the specified queue are processed from the network via a specified {@link
31  * Network} interface. Responses are committed to cache, if eligible, using a specified {@link
32  * Cache} interface. Valid responses and errors are posted back to the caller via a {@link
33  * ResponseDelivery}.
34  */
35 public class NetworkDispatcher extends Thread {
36 
37     /** The queue of requests to service. */
38     private final BlockingQueue<Request<?>> mQueue;
39     /** The network interface for processing requests. */
40     private final Network mNetwork;
41     /** The cache to write to. */
42     private final Cache mCache;
43     /** For posting responses and errors. */
44     private final ResponseDelivery mDelivery;
45     /** Used for telling us to die. */
46     private volatile boolean mQuit = false;
47 
48     /**
49      * Creates a new network dispatcher thread. You must call {@link #start()} in order to begin
50      * processing.
51      *
52      * @param queue Queue of incoming requests for triage
53      * @param network Network interface to use for performing requests
54      * @param cache Cache interface to use for writing responses to cache
55      * @param delivery Delivery interface to use for posting responses
56      */
NetworkDispatcher( BlockingQueue<Request<?>> queue, Network network, Cache cache, ResponseDelivery delivery)57     public NetworkDispatcher(
58             BlockingQueue<Request<?>> queue,
59             Network network,
60             Cache cache,
61             ResponseDelivery delivery) {
62         mQueue = queue;
63         mNetwork = network;
64         mCache = cache;
65         mDelivery = delivery;
66     }
67 
68     /**
69      * Forces this dispatcher to quit immediately. If any requests are still in the queue, they are
70      * not guaranteed to be processed.
71      */
quit()72     public void quit() {
73         mQuit = true;
74         interrupt();
75     }
76 
77     @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
addTrafficStatsTag(Request<?> request)78     private void addTrafficStatsTag(Request<?> request) {
79         // Tag the request (if API >= 14)
80         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
81             TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
82         }
83     }
84 
85     @Override
run()86     public void run() {
87         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
88         while (true) {
89             try {
90                 processRequest();
91             } catch (InterruptedException e) {
92                 // We may have been interrupted because it was time to quit.
93                 if (mQuit) {
94                     Thread.currentThread().interrupt();
95                     return;
96                 }
97                 VolleyLog.e(
98                         "Ignoring spurious interrupt of NetworkDispatcher thread; "
99                                 + "use quit() to terminate it");
100             }
101         }
102     }
103 
104     // Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
105     // This is needed to avoid keeping previous request references alive for an indeterminate amount
106     // of time. Update consumer-proguard-rules.pro when modifying this. See also
107     // https://github.com/google/volley/issues/114
processRequest()108     private void processRequest() throws InterruptedException {
109         // Take a request from the queue.
110         Request<?> request = mQueue.take();
111         processRequest(request);
112     }
113 
114     @VisibleForTesting
processRequest(Request<?> request)115     void processRequest(Request<?> request) {
116         long startTimeMs = SystemClock.elapsedRealtime();
117         request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
118         try {
119             request.addMarker("network-queue-take");
120 
121             // If the request was cancelled already, do not perform the
122             // network request.
123             if (request.isCanceled()) {
124                 request.finish("network-discard-cancelled");
125                 request.notifyListenerResponseNotUsable();
126                 return;
127             }
128 
129             addTrafficStatsTag(request);
130 
131             // Perform the network request.
132             NetworkResponse networkResponse = mNetwork.performRequest(request);
133             request.addMarker("network-http-complete");
134 
135             // If the server returned 304 AND we delivered a response already,
136             // we're done -- don't deliver a second identical response.
137             if (networkResponse.notModified && request.hasHadResponseDelivered()) {
138                 request.finish("not-modified");
139                 request.notifyListenerResponseNotUsable();
140                 return;
141             }
142 
143             // Parse the response here on the worker thread.
144             Response<?> response = request.parseNetworkResponse(networkResponse);
145             request.addMarker("network-parse-complete");
146 
147             // Write to cache if applicable.
148             // TODO: Only update cache metadata instead of entire record for 304s.
149             if (request.shouldCache() && response.cacheEntry != null) {
150                 mCache.put(request.getCacheKey(), response.cacheEntry);
151                 request.addMarker("network-cache-written");
152             }
153 
154             // Post the response back.
155             request.markDelivered();
156             mDelivery.postResponse(request, response);
157             request.notifyListenerResponseReceived(response);
158         } catch (VolleyError volleyError) {
159             volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
160             parseAndDeliverNetworkError(request, volleyError);
161             request.notifyListenerResponseNotUsable();
162         } catch (Exception e) {
163             VolleyLog.e(e, "Unhandled exception %s", e.toString());
164             VolleyError volleyError = new VolleyError(e);
165             volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
166             mDelivery.postError(request, volleyError);
167             request.notifyListenerResponseNotUsable();
168         } finally {
169             request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
170         }
171     }
172 
parseAndDeliverNetworkError(Request<?> request, VolleyError error)173     private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
174         error = request.parseNetworkError(error);
175         mDelivery.postError(request, error);
176     }
177 }
178