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