• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
obtain( LoadProvider<A, T, Z, R> loadProvider, A model, Key signature, Context context, Priority priority, Target<R> target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, RequestListener<? super A, R> requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation<Z> transformation, Class<R> transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory<R> animationFactory, int overrideWidth, int overrideHeight, DiskCacheStrategy diskCacheStrategy)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 
GenericRequest()139     private GenericRequest() {
140         // just create, instances are reused with recycle/init
141     }
142 
143     @Override
recycle()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 
init( LoadProvider<A, T, Z, R> loadProvider, A model, Key signature, Context context, Priority priority, Target<R> target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, RequestListener<? super A, R> requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation<Z> transformation, Class<R> transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory<R> animationFactory, int overrideWidth, int overrideHeight, DiskCacheStrategy diskCacheStrategy)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 
check(String name, Object object, String suggestion)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
begin()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      */
cancel()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
clear()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
isPaused()312     public boolean isPaused() {
313         return status == Status.PAUSED;
314     }
315 
316     @Override
pause()317     public void pause() {
318         clear();
319         status = Status.PAUSED;
320     }
321 
releaseResource(Resource resource)322     private void releaseResource(Resource resource) {
323         engine.release(resource);
324         this.resource = null;
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
330     @Override
isRunning()331     public boolean isRunning() {
332         return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
333     }
334 
335     /**
336      * {@inheritDoc}
337      */
338     @Override
isComplete()339     public boolean isComplete() {
340         return status == Status.COMPLETE;
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @Override
isResourceSet()347     public boolean isResourceSet() {
348         return isComplete();
349     }
350 
351     /**
352      * {@inheritDoc}
353      */
354     @Override
isCancelled()355     public boolean isCancelled() {
356         return status == Status.CANCELLED;
357     }
358 
359     /**
360      * {@inheritDoc}
361      */
362     @Override
isFailed()363     public boolean isFailed() {
364         return status == Status.FAILED;
365     }
366 
setErrorPlaceholder(Exception e)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 
getErrorDrawable()379     private Drawable getErrorDrawable() {
380         if (errorDrawable == null && errorResourceId > 0) {
381             errorDrawable = context.getResources().getDrawable(errorResourceId);
382         }
383         return errorDrawable;
384     }
385 
getPlaceholderDrawable()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
onSizeReady(int width, int height)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 
canSetResource()429     private boolean canSetResource() {
430         return requestCoordinator == null || requestCoordinator.canSetImage(this);
431     }
432 
canNotifyStatusChanged()433     private boolean canNotifyStatusChanged() {
434         return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this);
435     }
436 
isFirstReadyResource()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
onResourceReady(Resource<?> resource)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      */
onResourceReady(Resource<?> resource, R result)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
onException(Exception e)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 
logV(String message)514     private void logV(String message) {
515         Log.v(TAG, message + " this: " + tag);
516     }
517 }
518