mobile/android/thirdparty/com/squareup/picasso/Picasso.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

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.content.Context;
michael@0 19 import android.graphics.Bitmap;
michael@0 20 import android.graphics.Color;
michael@0 21 import android.net.Uri;
michael@0 22 import android.os.Handler;
michael@0 23 import android.os.Looper;
michael@0 24 import android.os.Message;
michael@0 25 import android.os.Process;
michael@0 26 import android.widget.ImageView;
michael@0 27 import java.io.File;
michael@0 28 import java.lang.ref.ReferenceQueue;
michael@0 29 import java.util.List;
michael@0 30 import java.util.Map;
michael@0 31 import java.util.WeakHashMap;
michael@0 32 import java.util.concurrent.ExecutorService;
michael@0 33
michael@0 34 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
michael@0 35 import static com.squareup.picasso.Action.RequestWeakReference;
michael@0 36 import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE;
michael@0 37 import static com.squareup.picasso.Dispatcher.REQUEST_GCED;
michael@0 38 import static com.squareup.picasso.Utils.THREAD_PREFIX;
michael@0 39
michael@0 40 /**
michael@0 41 * Image downloading, transformation, and caching manager.
michael@0 42 * <p/>
michael@0 43 * Use {@link #with(android.content.Context)} for the global singleton instance or construct your
michael@0 44 * own instance with {@link Builder}.
michael@0 45 */
michael@0 46 public class Picasso {
michael@0 47
michael@0 48 /** Callbacks for Picasso events. */
michael@0 49 public interface Listener {
michael@0 50 /**
michael@0 51 * Invoked when an image has failed to load. This is useful for reporting image failures to a
michael@0 52 * remote analytics service, for example.
michael@0 53 */
michael@0 54 void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception);
michael@0 55 }
michael@0 56
michael@0 57 /**
michael@0 58 * A transformer that is called immediately before every request is submitted. This can be used to
michael@0 59 * modify any information about a request.
michael@0 60 * <p>
michael@0 61 * For example, if you use a CDN you can change the hostname for the image based on the current
michael@0 62 * location of the user in order to get faster download speeds.
michael@0 63 * <p>
michael@0 64 * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
michael@0 65 * way at any time.
michael@0 66 */
michael@0 67 public interface RequestTransformer {
michael@0 68 /**
michael@0 69 * Transform a request before it is submitted to be processed.
michael@0 70 *
michael@0 71 * @return The original request or a new request to replace it. Must not be null.
michael@0 72 */
michael@0 73 Request transformRequest(Request request);
michael@0 74
michael@0 75 /** A {@link RequestTransformer} which returns the original request. */
michael@0 76 RequestTransformer IDENTITY = new RequestTransformer() {
michael@0 77 @Override public Request transformRequest(Request request) {
michael@0 78 return request;
michael@0 79 }
michael@0 80 };
michael@0 81 }
michael@0 82
michael@0 83 static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
michael@0 84 @Override public void handleMessage(Message msg) {
michael@0 85 switch (msg.what) {
michael@0 86 case HUNTER_BATCH_COMPLETE: {
michael@0 87 @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
michael@0 88 for (BitmapHunter hunter : batch) {
michael@0 89 hunter.picasso.complete(hunter);
michael@0 90 }
michael@0 91 break;
michael@0 92 }
michael@0 93 case REQUEST_GCED: {
michael@0 94 Action action = (Action) msg.obj;
michael@0 95 action.picasso.cancelExistingRequest(action.getTarget());
michael@0 96 break;
michael@0 97 }
michael@0 98 default:
michael@0 99 throw new AssertionError("Unknown handler message received: " + msg.what);
michael@0 100 }
michael@0 101 }
michael@0 102 };
michael@0 103
michael@0 104 static Picasso singleton = null;
michael@0 105
michael@0 106 private final Listener listener;
michael@0 107 private final RequestTransformer requestTransformer;
michael@0 108 private final CleanupThread cleanupThread;
michael@0 109
michael@0 110 final Context context;
michael@0 111 final Dispatcher dispatcher;
michael@0 112 final Cache cache;
michael@0 113 final Stats stats;
michael@0 114 final Map<Object, Action> targetToAction;
michael@0 115 final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
michael@0 116 final ReferenceQueue<Object> referenceQueue;
michael@0 117
michael@0 118 boolean debugging;
michael@0 119 boolean shutdown;
michael@0 120
michael@0 121 Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
michael@0 122 RequestTransformer requestTransformer, Stats stats, boolean debugging) {
michael@0 123 this.context = context;
michael@0 124 this.dispatcher = dispatcher;
michael@0 125 this.cache = cache;
michael@0 126 this.listener = listener;
michael@0 127 this.requestTransformer = requestTransformer;
michael@0 128 this.stats = stats;
michael@0 129 this.targetToAction = new WeakHashMap<Object, Action>();
michael@0 130 this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
michael@0 131 this.debugging = debugging;
michael@0 132 this.referenceQueue = new ReferenceQueue<Object>();
michael@0 133 this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
michael@0 134 this.cleanupThread.start();
michael@0 135 }
michael@0 136
michael@0 137 /** Cancel any existing requests for the specified target {@link ImageView}. */
michael@0 138 public void cancelRequest(ImageView view) {
michael@0 139 cancelExistingRequest(view);
michael@0 140 }
michael@0 141
michael@0 142 /** Cancel any existing requests for the specified {@link Target} instance. */
michael@0 143 public void cancelRequest(Target target) {
michael@0 144 cancelExistingRequest(target);
michael@0 145 }
michael@0 146
michael@0 147 /**
michael@0 148 * Start an image request using the specified URI.
michael@0 149 * <p>
michael@0 150 * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
michael@0 151 * if one is specified.
michael@0 152 *
michael@0 153 * @see #load(File)
michael@0 154 * @see #load(String)
michael@0 155 * @see #load(int)
michael@0 156 */
michael@0 157 public RequestCreator load(Uri uri) {
michael@0 158 return new RequestCreator(this, uri, 0);
michael@0 159 }
michael@0 160
michael@0 161 /**
michael@0 162 * Start an image request using the specified path. This is a convenience method for calling
michael@0 163 * {@link #load(Uri)}.
michael@0 164 * <p>
michael@0 165 * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
michael@0 166 * (prefixed with {@code content:}), or android resource (prefixed with {@code
michael@0 167 * android.resource:}.
michael@0 168 * <p>
michael@0 169 * Passing {@code null} as a {@code path} will not trigger any request but will set a
michael@0 170 * placeholder, if one is specified.
michael@0 171 *
michael@0 172 * @see #load(Uri)
michael@0 173 * @see #load(File)
michael@0 174 * @see #load(int)
michael@0 175 */
michael@0 176 public RequestCreator load(String path) {
michael@0 177 if (path == null) {
michael@0 178 return new RequestCreator(this, null, 0);
michael@0 179 }
michael@0 180 if (path.trim().length() == 0) {
michael@0 181 throw new IllegalArgumentException("Path must not be empty.");
michael@0 182 }
michael@0 183 return load(Uri.parse(path));
michael@0 184 }
michael@0 185
michael@0 186 /**
michael@0 187 * Start an image request using the specified image file. This is a convenience method for
michael@0 188 * calling {@link #load(Uri)}.
michael@0 189 * <p>
michael@0 190 * Passing {@code null} as a {@code file} will not trigger any request but will set a
michael@0 191 * placeholder, if one is specified.
michael@0 192 *
michael@0 193 * @see #load(Uri)
michael@0 194 * @see #load(String)
michael@0 195 * @see #load(int)
michael@0 196 */
michael@0 197 public RequestCreator load(File file) {
michael@0 198 if (file == null) {
michael@0 199 return new RequestCreator(this, null, 0);
michael@0 200 }
michael@0 201 return load(Uri.fromFile(file));
michael@0 202 }
michael@0 203
michael@0 204 /**
michael@0 205 * Start an image request using the specified drawable resource ID.
michael@0 206 *
michael@0 207 * @see #load(Uri)
michael@0 208 * @see #load(String)
michael@0 209 * @see #load(File)
michael@0 210 */
michael@0 211 public RequestCreator load(int resourceId) {
michael@0 212 if (resourceId == 0) {
michael@0 213 throw new IllegalArgumentException("Resource ID must not be zero.");
michael@0 214 }
michael@0 215 return new RequestCreator(this, null, resourceId);
michael@0 216 }
michael@0 217
michael@0 218 /** {@code true} if debug display, logging, and statistics are enabled. */
michael@0 219 @SuppressWarnings("UnusedDeclaration") public boolean isDebugging() {
michael@0 220 return debugging;
michael@0 221 }
michael@0 222
michael@0 223 /** Toggle whether debug display, logging, and statistics are enabled. */
michael@0 224 @SuppressWarnings("UnusedDeclaration") public void setDebugging(boolean debugging) {
michael@0 225 this.debugging = debugging;
michael@0 226 }
michael@0 227
michael@0 228 /** Creates a {@link StatsSnapshot} of the current stats for this instance. */
michael@0 229 @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() {
michael@0 230 return stats.createSnapshot();
michael@0 231 }
michael@0 232
michael@0 233 /** Stops this instance from accepting further requests. */
michael@0 234 public void shutdown() {
michael@0 235 if (this == singleton) {
michael@0 236 throw new UnsupportedOperationException("Default singleton instance cannot be shutdown.");
michael@0 237 }
michael@0 238 if (shutdown) {
michael@0 239 return;
michael@0 240 }
michael@0 241 cache.clear();
michael@0 242 cleanupThread.shutdown();
michael@0 243 stats.shutdown();
michael@0 244 dispatcher.shutdown();
michael@0 245 for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) {
michael@0 246 deferredRequestCreator.cancel();
michael@0 247 }
michael@0 248 targetToDeferredRequestCreator.clear();
michael@0 249 shutdown = true;
michael@0 250 }
michael@0 251
michael@0 252 Request transformRequest(Request request) {
michael@0 253 Request transformed = requestTransformer.transformRequest(request);
michael@0 254 if (transformed == null) {
michael@0 255 throw new IllegalStateException("Request transformer "
michael@0 256 + requestTransformer.getClass().getCanonicalName()
michael@0 257 + " returned null for "
michael@0 258 + request);
michael@0 259 }
michael@0 260 return transformed;
michael@0 261 }
michael@0 262
michael@0 263 void defer(ImageView view, DeferredRequestCreator request) {
michael@0 264 targetToDeferredRequestCreator.put(view, request);
michael@0 265 }
michael@0 266
michael@0 267 void enqueueAndSubmit(Action action) {
michael@0 268 Object target = action.getTarget();
michael@0 269 if (target != null) {
michael@0 270 cancelExistingRequest(target);
michael@0 271 targetToAction.put(target, action);
michael@0 272 }
michael@0 273 submit(action);
michael@0 274 }
michael@0 275
michael@0 276 void submit(Action action) {
michael@0 277 dispatcher.dispatchSubmit(action);
michael@0 278 }
michael@0 279
michael@0 280 Bitmap quickMemoryCacheCheck(String key) {
michael@0 281 Bitmap cached = cache.get(key);
michael@0 282 if (cached != null) {
michael@0 283 stats.dispatchCacheHit();
michael@0 284 } else {
michael@0 285 stats.dispatchCacheMiss();
michael@0 286 }
michael@0 287 return cached;
michael@0 288 }
michael@0 289
michael@0 290 void complete(BitmapHunter hunter) {
michael@0 291 List<Action> joined = hunter.getActions();
michael@0 292 if (joined.isEmpty()) {
michael@0 293 return;
michael@0 294 }
michael@0 295
michael@0 296 Uri uri = hunter.getData().uri;
michael@0 297 Exception exception = hunter.getException();
michael@0 298 Bitmap result = hunter.getResult();
michael@0 299 LoadedFrom from = hunter.getLoadedFrom();
michael@0 300
michael@0 301 for (Action join : joined) {
michael@0 302 if (join.isCancelled()) {
michael@0 303 continue;
michael@0 304 }
michael@0 305 targetToAction.remove(join.getTarget());
michael@0 306 if (result != null) {
michael@0 307 if (from == null) {
michael@0 308 throw new AssertionError("LoadedFrom cannot be null.");
michael@0 309 }
michael@0 310 join.complete(result, from);
michael@0 311 } else {
michael@0 312 join.error();
michael@0 313 }
michael@0 314 }
michael@0 315
michael@0 316 if (listener != null && exception != null) {
michael@0 317 listener.onImageLoadFailed(this, uri, exception);
michael@0 318 }
michael@0 319 }
michael@0 320
michael@0 321 private void cancelExistingRequest(Object target) {
michael@0 322 Action action = targetToAction.remove(target);
michael@0 323 if (action != null) {
michael@0 324 action.cancel();
michael@0 325 dispatcher.dispatchCancel(action);
michael@0 326 }
michael@0 327 if (target instanceof ImageView) {
michael@0 328 ImageView targetImageView = (ImageView) target;
michael@0 329 DeferredRequestCreator deferredRequestCreator =
michael@0 330 targetToDeferredRequestCreator.remove(targetImageView);
michael@0 331 if (deferredRequestCreator != null) {
michael@0 332 deferredRequestCreator.cancel();
michael@0 333 }
michael@0 334 }
michael@0 335 }
michael@0 336
michael@0 337 private static class CleanupThread extends Thread {
michael@0 338 private final ReferenceQueue<?> referenceQueue;
michael@0 339 private final Handler handler;
michael@0 340
michael@0 341 CleanupThread(ReferenceQueue<?> referenceQueue, Handler handler) {
michael@0 342 this.referenceQueue = referenceQueue;
michael@0 343 this.handler = handler;
michael@0 344 setDaemon(true);
michael@0 345 setName(THREAD_PREFIX + "refQueue");
michael@0 346 }
michael@0 347
michael@0 348 @Override public void run() {
michael@0 349 Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
michael@0 350 while (true) {
michael@0 351 try {
michael@0 352 RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove();
michael@0 353 handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action));
michael@0 354 } catch (InterruptedException e) {
michael@0 355 break;
michael@0 356 } catch (final Exception e) {
michael@0 357 handler.post(new Runnable() {
michael@0 358 @Override public void run() {
michael@0 359 throw new RuntimeException(e);
michael@0 360 }
michael@0 361 });
michael@0 362 break;
michael@0 363 }
michael@0 364 }
michael@0 365 }
michael@0 366
michael@0 367 void shutdown() {
michael@0 368 interrupt();
michael@0 369 }
michael@0 370 }
michael@0 371
michael@0 372 /**
michael@0 373 * The global default {@link Picasso} instance.
michael@0 374 * <p>
michael@0 375 * This instance is automatically initialized with defaults that are suitable to most
michael@0 376 * implementations.
michael@0 377 * <ul>
michael@0 378 * <li>LRU memory cache of 15% the available application RAM</li>
michael@0 379 * <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
michael@0 380 * available on API 14+ <em>or</em> if you are using a standalone library that provides a disk
michael@0 381 * cache on all API levels like OkHttp)</li>
michael@0 382 * <li>Three download threads for disk and network access.</li>
michael@0 383 * </ul>
michael@0 384 * <p>
michael@0 385 * If these settings do not meet the requirements of your application you can construct your own
michael@0 386 * instance with full control over the configuration by using {@link Picasso.Builder}.
michael@0 387 */
michael@0 388 public static Picasso with(Context context) {
michael@0 389 if (singleton == null) {
michael@0 390 singleton = new Builder(context).build();
michael@0 391 }
michael@0 392 return singleton;
michael@0 393 }
michael@0 394
michael@0 395 /** Fluent API for creating {@link Picasso} instances. */
michael@0 396 @SuppressWarnings("UnusedDeclaration") // Public API.
michael@0 397 public static class Builder {
michael@0 398 private final Context context;
michael@0 399 private Downloader downloader;
michael@0 400 private ExecutorService service;
michael@0 401 private Cache cache;
michael@0 402 private Listener listener;
michael@0 403 private RequestTransformer transformer;
michael@0 404 private boolean debugging;
michael@0 405
michael@0 406 /** Start building a new {@link Picasso} instance. */
michael@0 407 public Builder(Context context) {
michael@0 408 if (context == null) {
michael@0 409 throw new IllegalArgumentException("Context must not be null.");
michael@0 410 }
michael@0 411 this.context = context.getApplicationContext();
michael@0 412 }
michael@0 413
michael@0 414 /** Specify the {@link Downloader} that will be used for downloading images. */
michael@0 415 public Builder downloader(Downloader downloader) {
michael@0 416 if (downloader == null) {
michael@0 417 throw new IllegalArgumentException("Downloader must not be null.");
michael@0 418 }
michael@0 419 if (this.downloader != null) {
michael@0 420 throw new IllegalStateException("Downloader already set.");
michael@0 421 }
michael@0 422 this.downloader = downloader;
michael@0 423 return this;
michael@0 424 }
michael@0 425
michael@0 426 /** Specify the executor service for loading images in the background. */
michael@0 427 public Builder executor(ExecutorService executorService) {
michael@0 428 if (executorService == null) {
michael@0 429 throw new IllegalArgumentException("Executor service must not be null.");
michael@0 430 }
michael@0 431 if (this.service != null) {
michael@0 432 throw new IllegalStateException("Executor service already set.");
michael@0 433 }
michael@0 434 this.service = executorService;
michael@0 435 return this;
michael@0 436 }
michael@0 437
michael@0 438 /** Specify the memory cache used for the most recent images. */
michael@0 439 public Builder memoryCache(Cache memoryCache) {
michael@0 440 if (memoryCache == null) {
michael@0 441 throw new IllegalArgumentException("Memory cache must not be null.");
michael@0 442 }
michael@0 443 if (this.cache != null) {
michael@0 444 throw new IllegalStateException("Memory cache already set.");
michael@0 445 }
michael@0 446 this.cache = memoryCache;
michael@0 447 return this;
michael@0 448 }
michael@0 449
michael@0 450 /** Specify a listener for interesting events. */
michael@0 451 public Builder listener(Listener listener) {
michael@0 452 if (listener == null) {
michael@0 453 throw new IllegalArgumentException("Listener must not be null.");
michael@0 454 }
michael@0 455 if (this.listener != null) {
michael@0 456 throw new IllegalStateException("Listener already set.");
michael@0 457 }
michael@0 458 this.listener = listener;
michael@0 459 return this;
michael@0 460 }
michael@0 461
michael@0 462 /**
michael@0 463 * Specify a transformer for all incoming requests.
michael@0 464 * <p>
michael@0 465 * <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
michael@0 466 * way at any time.
michael@0 467 */
michael@0 468 public Builder requestTransformer(RequestTransformer transformer) {
michael@0 469 if (transformer == null) {
michael@0 470 throw new IllegalArgumentException("Transformer must not be null.");
michael@0 471 }
michael@0 472 if (this.transformer != null) {
michael@0 473 throw new IllegalStateException("Transformer already set.");
michael@0 474 }
michael@0 475 this.transformer = transformer;
michael@0 476 return this;
michael@0 477 }
michael@0 478
michael@0 479 /** Whether debugging is enabled or not. */
michael@0 480 public Builder debugging(boolean debugging) {
michael@0 481 this.debugging = debugging;
michael@0 482 return this;
michael@0 483 }
michael@0 484
michael@0 485 /** Create the {@link Picasso} instance. */
michael@0 486 public Picasso build() {
michael@0 487 Context context = this.context;
michael@0 488
michael@0 489 if (downloader == null) {
michael@0 490 downloader = Utils.createDefaultDownloader(context);
michael@0 491 }
michael@0 492 if (cache == null) {
michael@0 493 cache = new LruCache(context);
michael@0 494 }
michael@0 495 if (service == null) {
michael@0 496 service = new PicassoExecutorService();
michael@0 497 }
michael@0 498 if (transformer == null) {
michael@0 499 transformer = RequestTransformer.IDENTITY;
michael@0 500 }
michael@0 501
michael@0 502 Stats stats = new Stats(cache);
michael@0 503
michael@0 504 Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
michael@0 505
michael@0 506 return new Picasso(context, dispatcher, cache, listener, transformer, stats, debugging);
michael@0 507 }
michael@0 508 }
michael@0 509
michael@0 510 /** Describes where the image was loaded from. */
michael@0 511 public enum LoadedFrom {
michael@0 512 MEMORY(Color.GREEN),
michael@0 513 DISK(Color.YELLOW),
michael@0 514 NETWORK(Color.RED);
michael@0 515
michael@0 516 final int debugColor;
michael@0 517
michael@0 518 private LoadedFrom(int debugColor) {
michael@0 519 this.debugColor = debugColor;
michael@0 520 }
michael@0 521 }
michael@0 522 }

mercurial