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.

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

mercurial