Home | History | Annotate | Download | only in request
      1 package com.bumptech.glide.request;
      2 
      3 import android.content.Context;
      4 import android.graphics.drawable.Drawable;
      5 import android.util.Log;
      6 
      7 import com.bumptech.glide.Priority;
      8 import com.bumptech.glide.load.Key;
      9 import com.bumptech.glide.load.Transformation;
     10 import com.bumptech.glide.load.data.DataFetcher;
     11 import com.bumptech.glide.load.engine.DiskCacheStrategy;
     12 import com.bumptech.glide.load.engine.Engine;
     13 import com.bumptech.glide.load.engine.Resource;
     14 import com.bumptech.glide.load.model.ModelLoader;
     15 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
     16 import com.bumptech.glide.provider.LoadProvider;
     17 import com.bumptech.glide.request.animation.GlideAnimation;
     18 import com.bumptech.glide.request.animation.GlideAnimationFactory;
     19 import com.bumptech.glide.request.target.SizeReadyCallback;
     20 import com.bumptech.glide.request.target.Target;
     21 import com.bumptech.glide.util.LogTime;
     22 import com.bumptech.glide.util.Util;
     23 
     24 import java.util.Queue;
     25 
     26 /**
     27  * A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given {@link Target}.
     28  *
     29  * @param <A> The type of the model that the resource will be loaded from.
     30  * @param <T> The type of the data that the resource will be loaded from.
     31  * @param <Z> The type of the resource that will be loaded.
     32  * @param <R> The type of the resource that will be transcoded from the loaded resource.
     33  */
     34 public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
     35         ResourceCallback {
     36     private static final String TAG = "GenericRequest";
     37     private static final Queue<GenericRequest<?, ?, ?, ?>> REQUEST_POOL = Util.createQueue(0);
     38     private static final double TO_MEGABYTE = 1d / (1024d * 1024d);
     39 
     40     private enum Status {
     41         /** Created but not yet running. */
     42         PENDING,
     43         /** In the process of fetching media. */
     44         RUNNING,
     45         /** Waiting for a callback given to the Target to be called to determine target dimensions. */
     46         WAITING_FOR_SIZE,
     47         /** Finished loading media successfully. */
     48         COMPLETE,
     49         /** Failed to load media. */
     50         FAILED,
     51         /** Cancelled by the user, may not be restarted. */
     52         CANCELLED,
     53         /** Temporarily paused by the system, may be restarted. */
     54         PAUSED,
     55     }
     56 
     57     private final String tag = String.valueOf(hashCode());
     58 
     59     private Key signature;
     60     private int placeholderResourceId;
     61     private int errorResourceId;
     62     private Context context;
     63     private Transformation<Z> transformation;
     64     private LoadProvider<A, T, Z, R> loadProvider;
     65     private RequestCoordinator requestCoordinator;
     66     private A model;
     67     private Class<R> transcodeClass;
     68     private boolean isMemoryCacheable;
     69     private Priority priority;
     70     private Target<R> target;
     71     private RequestListener<? super A, R> requestListener;
     72     private float sizeMultiplier;
     73     private Engine engine;
     74     private GlideAnimationFactory<R> animationFactory;
     75     private int overrideWidth;
     76     private int overrideHeight;
     77     private DiskCacheStrategy diskCacheStrategy;
     78 
     79     private Drawable placeholderDrawable;
     80     private Drawable errorDrawable;
     81     private boolean loadedFromMemoryCache;
     82     // doing our own type check
     83     private Resource<?> resource;
     84     private Engine.LoadStatus loadStatus;
     85     private long startTime;
     86     private Status status;
     87 
     88     public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
     89             LoadProvider<A, T, Z, R> loadProvider,
     90             A model,
     91             Key signature,
     92             Context context,
     93             Priority priority,
     94             Target<R> target,
     95             float sizeMultiplier,
     96             Drawable placeholderDrawable,
     97             int placeholderResourceId,
     98             Drawable errorDrawable,
     99             int errorResourceId,
    100             RequestListener<? super A, R> requestListener,
    101             RequestCoordinator requestCoordinator,
    102             Engine engine,
    103             Transformation<Z> transformation,
    104             Class<R> transcodeClass,
    105             boolean isMemoryCacheable,
    106             GlideAnimationFactory<R> animationFactory,
    107             int overrideWidth,
    108             int overrideHeight,
    109             DiskCacheStrategy diskCacheStrategy) {
    110         @SuppressWarnings("unchecked")
    111         GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
    112         if (request == null) {
    113             request = new GenericRequest<A, T, Z, R>();
    114         }
    115         request.init(loadProvider,
    116                 model,
    117                 signature,
    118                 context,
    119                 priority,
    120                 target,
    121                 sizeMultiplier,
    122                 placeholderDrawable,
    123                 placeholderResourceId,
    124                 errorDrawable,
    125                 errorResourceId,
    126                 requestListener,
    127                 requestCoordinator,
    128                 engine,
    129                 transformation,
    130                 transcodeClass,
    131                 isMemoryCacheable,
    132                 animationFactory,
    133                 overrideWidth,
    134                 overrideHeight,
    135                 diskCacheStrategy);
    136         return request;
    137     }
    138 
    139     private GenericRequest() {
    140         // just create, instances are reused with recycle/init
    141     }
    142 
    143     @Override
    144     public void recycle() {
    145         loadProvider = null;
    146         model = null;
    147         context = null;
    148         target = null;
    149         placeholderDrawable = null;
    150         errorDrawable = null;
    151         requestListener = null;
    152         requestCoordinator = null;
    153         transformation = null;
    154         animationFactory = null;
    155         loadedFromMemoryCache = false;
    156         loadStatus = null;
    157         REQUEST_POOL.offer(this);
    158     }
    159 
    160     private void init(
    161             LoadProvider<A, T, Z, R> loadProvider,
    162             A model,
    163             Key signature,
    164             Context context,
    165             Priority priority,
    166             Target<R> target,
    167             float sizeMultiplier,
    168             Drawable placeholderDrawable,
    169             int placeholderResourceId,
    170             Drawable errorDrawable,
    171             int errorResourceId,
    172             RequestListener<? super A, R> requestListener,
    173             RequestCoordinator requestCoordinator,
    174             Engine engine,
    175             Transformation<Z> transformation,
    176             Class<R> transcodeClass,
    177             boolean isMemoryCacheable,
    178             GlideAnimationFactory<R> animationFactory,
    179             int overrideWidth,
    180             int overrideHeight,
    181             DiskCacheStrategy diskCacheStrategy) {
    182         this.loadProvider = loadProvider;
    183         this.model = model;
    184         this.signature = signature;
    185         this.context = context.getApplicationContext();
    186         this.priority = priority;
    187         this.target = target;
    188         this.sizeMultiplier = sizeMultiplier;
    189         this.placeholderDrawable = placeholderDrawable;
    190         this.placeholderResourceId = placeholderResourceId;
    191         this.errorDrawable = errorDrawable;
    192         this.errorResourceId = errorResourceId;
    193         this.requestListener = requestListener;
    194         this.requestCoordinator = requestCoordinator;
    195         this.engine = engine;
    196         this.transformation = transformation;
    197         this.transcodeClass = transcodeClass;
    198         this.isMemoryCacheable = isMemoryCacheable;
    199         this.animationFactory = animationFactory;
    200         this.overrideWidth = overrideWidth;
    201         this.overrideHeight = overrideHeight;
    202         this.diskCacheStrategy = diskCacheStrategy;
    203         status = Status.PENDING;
    204 
    205         // We allow null models by just setting an error drawable. Null models will always have empty providers, we
    206         // simply skip our sanity checks in that unusual case.
    207         if (model != null) {
    208             check("ModelLoader", loadProvider.getModelLoader(), "try .using(ModelLoader)");
    209             check("Transcoder", loadProvider.getTranscoder(), "try .as*(Class).transcode(ResourceTranscoder)");
    210             check("Transformation", transformation, "try .transform(UnitTransformation.get())");
    211             if (diskCacheStrategy.cacheSource()) {
    212                 check("SourceEncoder", loadProvider.getSourceEncoder(),
    213                         "try .sourceEncoder(Encoder) or .diskCacheStrategy(NONE/RESULT)");
    214             } else {
    215                 check("SourceDecoder", loadProvider.getSourceDecoder(),
    216                         "try .decoder/.imageDecoder/.videoDecoder(ResourceDecoder) or .diskCacheStrategy(ALL/SOURCE)");
    217             }
    218             if (diskCacheStrategy.cacheSource() || diskCacheStrategy.cacheResult()) {
    219                 // TODO if(resourceClass.isAssignableFrom(InputStream.class) it is possible to wrap sourceDecoder
    220                 // and use it instead of cacheDecoder: new FileToStreamDecoder<Z>(sourceDecoder)
    221                 // in that case this shouldn't throw
    222                 check("CacheDecoder", loadProvider.getCacheDecoder(),
    223                         "try .cacheDecoder(ResouceDecoder) or .diskCacheStrategy(NONE)");
    224             }
    225             if (diskCacheStrategy.cacheResult()) {
    226                 check("Encoder", loadProvider.getEncoder(),
    227                         "try .encode(ResourceEncoder) or .diskCacheStrategy(NONE/SOURCE)");
    228             }
    229         }
    230     }
    231 
    232     private static void check(String name, Object object, String suggestion) {
    233         if (object == null) {
    234             StringBuilder message = new StringBuilder(name);
    235             message.append(" must not be null");
    236             if (suggestion != null) {
    237                 message.append(", ");
    238                 message.append(suggestion);
    239             }
    240             throw new NullPointerException(message.toString());
    241         }
    242     }
    243 
    244     /**
    245      * {@inheritDoc}
    246      */
    247     @Override
    248     public void begin() {
    249         startTime = LogTime.getLogTime();
    250         if (model == null) {
    251             onException(null);
    252             return;
    253         }
    254 
    255         status = Status.WAITING_FOR_SIZE;
    256         if (overrideWidth > 0 && overrideHeight > 0) {
    257             onSizeReady(overrideWidth, overrideHeight);
    258         } else {
    259             target.getSize(this);
    260         }
    261 
    262         if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
    263             target.onLoadStarted(getPlaceholderDrawable());
    264         }
    265         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    266             logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    267         }
    268     }
    269 
    270     /**
    271      * Cancels the current load but does not release any resources held by the request and continues to display
    272      * the loaded resource if the load completed before the call to cancel.
    273      *
    274      * <p>
    275      *     Cancelled requests can be restarted with a subsequent call to {@link #begin()}.
    276      * </p>
    277      *
    278      * @see #clear()
    279      */
    280     void cancel() {
    281         status = Status.CANCELLED;
    282         if (loadStatus != null) {
    283             loadStatus.cancel();
    284             loadStatus = null;
    285         }
    286     }
    287 
    288     /**
    289      * Cancels the current load if it is in progress, clears any resources held onto by the request and replaces
    290      * the loaded resource if the load completed with the placeholder.
    291      *
    292      * <p>
    293      *     Cleared requests can be restarted with a subsequent call to {@link #begin()}
    294      * </p>
    295      *
    296      * @see #cancel()
    297      */
    298     @Override
    299     public void clear() {
    300         Util.assertMainThread();
    301         cancel();
    302         // Resource must be released before canNotifyStatusChanged is called.
    303         if (resource != null) {
    304             releaseResource(resource);
    305         }
    306         if (canNotifyStatusChanged()) {
    307             target.onLoadCleared(getPlaceholderDrawable());
    308         }
    309     }
    310 
    311     @Override
    312     public boolean isPaused() {
    313         return status == Status.PAUSED;
    314     }
    315 
    316     @Override
    317     public void pause() {
    318         clear();
    319         status = Status.PAUSED;
    320     }
    321 
    322     private void releaseResource(Resource resource) {
    323         engine.release(resource);
    324         this.resource = null;
    325     }
    326 
    327     /**
    328      * {@inheritDoc}
    329      */
    330     @Override
    331     public boolean isRunning() {
    332         return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
    333     }
    334 
    335     /**
    336      * {@inheritDoc}
    337      */
    338     @Override
    339     public boolean isComplete() {
    340         return status == Status.COMPLETE;
    341     }
    342 
    343     /**
    344      * {@inheritDoc}
    345      */
    346     @Override
    347     public boolean isResourceSet() {
    348         return isComplete();
    349     }
    350 
    351     /**
    352      * {@inheritDoc}
    353      */
    354     @Override
    355     public boolean isCancelled() {
    356         return status == Status.CANCELLED;
    357     }
    358 
    359     /**
    360      * {@inheritDoc}
    361      */
    362     @Override
    363     public boolean isFailed() {
    364         return status == Status.FAILED;
    365     }
    366 
    367     private void setErrorPlaceholder(Exception e) {
    368         if (!canNotifyStatusChanged()) {
    369             return;
    370         }
    371 
    372         Drawable error = getErrorDrawable();
    373         if (error == null) {
    374             error = getPlaceholderDrawable();
    375         }
    376         target.onLoadFailed(e, error);
    377     }
    378 
    379     private Drawable getErrorDrawable() {
    380         if (errorDrawable == null && errorResourceId > 0) {
    381             errorDrawable = context.getResources().getDrawable(errorResourceId);
    382         }
    383         return errorDrawable;
    384     }
    385 
    386     private Drawable getPlaceholderDrawable() {
    387         if (placeholderDrawable == null && placeholderResourceId > 0) {
    388             placeholderDrawable = context.getResources().getDrawable(placeholderResourceId);
    389         }
    390         return placeholderDrawable;
    391     }
    392 
    393     /**
    394      * A callback method that should never be invoked directly.
    395      */
    396     @Override
    397     public void onSizeReady(int width, int height) {
    398         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    399             logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    400         }
    401         if (status != Status.WAITING_FOR_SIZE) {
    402             return;
    403         }
    404         status = Status.RUNNING;
    405 
    406         width = Math.round(sizeMultiplier * width);
    407         height = Math.round(sizeMultiplier * height);
    408 
    409         ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
    410         final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
    411 
    412         if (dataFetcher == null) {
    413             onException(new Exception("Got null fetcher from model loader"));
    414             return;
    415         }
    416         ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    417         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    418             logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    419         }
    420         loadedFromMemoryCache = true;
    421         loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
    422                 priority, isMemoryCacheable, diskCacheStrategy, this);
    423         loadedFromMemoryCache = resource != null;
    424         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    425             logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    426         }
    427     }
    428 
    429     private boolean canSetResource() {
    430         return requestCoordinator == null || requestCoordinator.canSetImage(this);
    431     }
    432 
    433     private boolean canNotifyStatusChanged() {
    434         return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this);
    435     }
    436 
    437     private boolean isFirstReadyResource() {
    438         return requestCoordinator == null || !requestCoordinator.isAnyResourceSet();
    439     }
    440 
    441     /**
    442      * A callback method that should never be invoked directly.
    443      */
    444     @SuppressWarnings("unchecked")
    445     @Override
    446     public void onResourceReady(Resource<?> resource) {
    447         if (resource == null) {
    448             onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
    449                     + " inside, but instead got null."));
    450             return;
    451         }
    452 
    453         Object received = resource.get();
    454         if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
    455             releaseResource(resource);
    456             onException(new Exception("Expected to receive an object of " + transcodeClass
    457                     + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
    458                     + " inside Resource{" + resource + "}."
    459                     + (received != null ? "" : " "
    460                         + "To indicate failure return a null Resource object, "
    461                         + "rather than a Resource object containing null data.")
    462             ));
    463             return;
    464         }
    465 
    466         if (!canSetResource()) {
    467             releaseResource(resource);
    468             // We can't set the status to complete before asking canSetResource().
    469             status = Status.COMPLETE;
    470             return;
    471         }
    472 
    473         onResourceReady(resource, (R) received);
    474     }
    475 
    476     /**
    477      * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
    478      *
    479      * @param resource original {@link Resource}, never <code>null</code>
    480      * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
    481      */
    482     private void onResourceReady(Resource<?> resource, R result) {
    483         if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
    484                 isFirstReadyResource())) {
    485             GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstReadyResource());
    486             target.onResourceReady(result, animation);
    487         }
    488 
    489         status = Status.COMPLETE;
    490         this.resource = resource;
    491 
    492         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    493             logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
    494                     + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
    495         }
    496     }
    497 
    498     /**
    499      * A callback method that should never be invoked directly.
    500      */
    501     @Override
    502     public void onException(Exception e) {
    503         if (Log.isLoggable(TAG, Log.DEBUG)) {
    504             Log.d(TAG, "load failed", e);
    505         }
    506 
    507         status = Status.FAILED;
    508         //TODO: what if this is a thumbnail request?
    509         if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
    510             setErrorPlaceholder(e);
    511         }
    512     }
    513 
    514     private void logV(String message) {
    515         Log.v(TAG, message + " this: " + tag);
    516     }
    517 }
    518