Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /*
2 * Copyright (C) 2013 Square, Inc.
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 package com.squareup.picasso;
18 import android.Manifest;
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.net.ConnectivityManager;
24 import android.net.NetworkInfo;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.Looper;
29 import android.os.Message;
30 import java.util.ArrayList;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.ExecutorService;
36 import static android.content.Context.CONNECTIVITY_SERVICE;
37 import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED;
38 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
39 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
40 import static com.squareup.picasso.BitmapHunter.forRequest;
42 class Dispatcher {
43 private static final int RETRY_DELAY = 500;
44 private static final int AIRPLANE_MODE_ON = 1;
45 private static final int AIRPLANE_MODE_OFF = 0;
47 static final int REQUEST_SUBMIT = 1;
48 static final int REQUEST_CANCEL = 2;
49 static final int REQUEST_GCED = 3;
50 static final int HUNTER_COMPLETE = 4;
51 static final int HUNTER_RETRY = 5;
52 static final int HUNTER_DECODE_FAILED = 6;
53 static final int HUNTER_DELAY_NEXT_BATCH = 7;
54 static final int HUNTER_BATCH_COMPLETE = 8;
55 static final int NETWORK_STATE_CHANGE = 9;
56 static final int AIRPLANE_MODE_CHANGE = 10;
58 private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
59 private static final int BATCH_DELAY = 200; // ms
61 final DispatcherThread dispatcherThread;
62 final Context context;
63 final ExecutorService service;
64 final Downloader downloader;
65 final Map<String, BitmapHunter> hunterMap;
66 final Handler handler;
67 final Handler mainThreadHandler;
68 final Cache cache;
69 final Stats stats;
70 final List<BitmapHunter> batch;
71 final NetworkBroadcastReceiver receiver;
73 NetworkInfo networkInfo;
74 boolean airplaneMode;
76 Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
77 Downloader downloader, Cache cache, Stats stats) {
78 this.dispatcherThread = new DispatcherThread();
79 this.dispatcherThread.start();
80 this.context = context;
81 this.service = service;
82 this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
83 this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
84 this.downloader = downloader;
85 this.mainThreadHandler = mainThreadHandler;
86 this.cache = cache;
87 this.stats = stats;
88 this.batch = new ArrayList<BitmapHunter>(4);
89 this.airplaneMode = Utils.isAirplaneModeOn(this.context);
90 this.receiver = new NetworkBroadcastReceiver(this.context);
91 receiver.register();
92 }
94 void shutdown() {
95 service.shutdown();
96 dispatcherThread.quit();
97 receiver.unregister();
98 }
100 void dispatchSubmit(Action action) {
101 handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
102 }
104 void dispatchCancel(Action action) {
105 handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
106 }
108 void dispatchComplete(BitmapHunter hunter) {
109 handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
110 }
112 void dispatchRetry(BitmapHunter hunter) {
113 handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY);
114 }
116 void dispatchFailed(BitmapHunter hunter) {
117 handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter));
118 }
120 void dispatchNetworkStateChange(NetworkInfo info) {
121 handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));
122 }
124 void dispatchAirplaneModeChange(boolean airplaneMode) {
125 handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE,
126 airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0));
127 }
129 void performSubmit(Action action) {
130 BitmapHunter hunter = hunterMap.get(action.getKey());
131 if (hunter != null) {
132 hunter.attach(action);
133 return;
134 }
136 if (service.isShutdown()) {
137 return;
138 }
140 hunter = forRequest(context, action.getPicasso(), this, cache, stats, action, downloader);
141 hunter.future = service.submit(hunter);
142 hunterMap.put(action.getKey(), hunter);
143 }
145 void performCancel(Action action) {
146 String key = action.getKey();
147 BitmapHunter hunter = hunterMap.get(key);
148 if (hunter != null) {
149 hunter.detach(action);
150 if (hunter.cancel()) {
151 hunterMap.remove(key);
152 }
153 }
154 }
156 void performRetry(BitmapHunter hunter) {
157 if (hunter.isCancelled()) return;
159 if (service.isShutdown()) {
160 performError(hunter);
161 return;
162 }
164 if (hunter.shouldRetry(airplaneMode, networkInfo)) {
165 hunter.future = service.submit(hunter);
166 } else {
167 performError(hunter);
168 }
169 }
171 void performComplete(BitmapHunter hunter) {
172 if (!hunter.shouldSkipMemoryCache()) {
173 cache.set(hunter.getKey(), hunter.getResult());
174 }
175 hunterMap.remove(hunter.getKey());
176 batch(hunter);
177 }
179 void performBatchComplete() {
180 List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
181 batch.clear();
182 mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
183 }
185 void performError(BitmapHunter hunter) {
186 hunterMap.remove(hunter.getKey());
187 batch(hunter);
188 }
190 void performAirplaneModeChange(boolean airplaneMode) {
191 this.airplaneMode = airplaneMode;
192 }
194 void performNetworkStateChange(NetworkInfo info) {
195 networkInfo = info;
196 if (service instanceof PicassoExecutorService) {
197 ((PicassoExecutorService) service).adjustThreadCount(info);
198 }
199 }
201 private void batch(BitmapHunter hunter) {
202 if (hunter.isCancelled()) {
203 return;
204 }
205 batch.add(hunter);
206 if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
207 handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
208 }
209 }
211 private static class DispatcherHandler extends Handler {
212 private final Dispatcher dispatcher;
214 public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
215 super(looper);
216 this.dispatcher = dispatcher;
217 }
219 @Override public void handleMessage(final Message msg) {
220 switch (msg.what) {
221 case REQUEST_SUBMIT: {
222 Action action = (Action) msg.obj;
223 dispatcher.performSubmit(action);
224 break;
225 }
226 case REQUEST_CANCEL: {
227 Action action = (Action) msg.obj;
228 dispatcher.performCancel(action);
229 break;
230 }
231 case HUNTER_COMPLETE: {
232 BitmapHunter hunter = (BitmapHunter) msg.obj;
233 dispatcher.performComplete(hunter);
234 break;
235 }
236 case HUNTER_RETRY: {
237 BitmapHunter hunter = (BitmapHunter) msg.obj;
238 dispatcher.performRetry(hunter);
239 break;
240 }
241 case HUNTER_DECODE_FAILED: {
242 BitmapHunter hunter = (BitmapHunter) msg.obj;
243 dispatcher.performError(hunter);
244 break;
245 }
246 case HUNTER_DELAY_NEXT_BATCH: {
247 dispatcher.performBatchComplete();
248 break;
249 }
250 case NETWORK_STATE_CHANGE: {
251 NetworkInfo info = (NetworkInfo) msg.obj;
252 dispatcher.performNetworkStateChange(info);
253 break;
254 }
255 case AIRPLANE_MODE_CHANGE: {
256 dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
257 break;
258 }
259 default:
260 Picasso.HANDLER.post(new Runnable() {
261 @Override public void run() {
262 throw new AssertionError("Unknown handler message received: " + msg.what);
263 }
264 });
265 }
266 }
267 }
269 static class DispatcherThread extends HandlerThread {
270 DispatcherThread() {
271 super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
272 }
273 }
275 private class NetworkBroadcastReceiver extends BroadcastReceiver {
276 private static final String EXTRA_AIRPLANE_STATE = "state";
278 private final ConnectivityManager connectivityManager;
280 NetworkBroadcastReceiver(Context context) {
281 connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
282 }
284 void register() {
285 boolean shouldScanState = service instanceof PicassoExecutorService && //
286 Utils.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
287 IntentFilter filter = new IntentFilter();
288 filter.addAction(ACTION_AIRPLANE_MODE_CHANGED);
289 if (shouldScanState) {
290 filter.addAction(CONNECTIVITY_ACTION);
291 }
292 context.registerReceiver(this, filter);
293 }
295 void unregister() {
296 context.unregisterReceiver(this);
297 }
299 @Override public void onReceive(Context context, Intent intent) {
300 // On some versions of Android this may be called with a null Intent
301 if (null == intent) {
302 return;
303 }
305 String action = intent.getAction();
306 Bundle extras = intent.getExtras();
308 if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
309 dispatchAirplaneModeChange(extras.getBoolean(EXTRA_AIRPLANE_STATE, false));
310 } else if (CONNECTIVITY_ACTION.equals(action)) {
311 dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo());
312 }
313 }
314 }
315 }