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