RequestManager.java revision 237ad5590666f641db5762c599be7c7edbfd0645
1package com.bumptech.glide;
2
3import android.content.Context;
4import android.net.Uri;
5import android.os.Handler;
6import android.os.Looper;
7import android.os.ParcelFileDescriptor;
8
9import com.bumptech.glide.load.model.ModelLoader;
10import com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader;
11import com.bumptech.glide.load.model.stream.MediaStoreStreamLoader;
12import com.bumptech.glide.load.model.stream.StreamByteArrayLoader;
13import com.bumptech.glide.load.model.stream.StreamModelLoader;
14import com.bumptech.glide.manager.ConnectivityMonitor;
15import com.bumptech.glide.manager.ConnectivityMonitorFactory;
16import com.bumptech.glide.manager.Lifecycle;
17import com.bumptech.glide.manager.LifecycleListener;
18import com.bumptech.glide.manager.RequestTracker;
19import com.bumptech.glide.util.Util;
20
21import java.io.File;
22import java.io.InputStream;
23import java.net.URL;
24import java.util.UUID;
25
26/**
27 * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity lifecycle events to
28 * intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage
29 * built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity.
30 *
31 * @see Glide#with(android.app.Activity)
32 * @see Glide#with(android.support.v4.app.FragmentActivity)
33 * @see Glide#with(android.app.Fragment)
34 * @see Glide#with(android.support.v4.app.Fragment)
35 * @see Glide#with(Context)
36 */
37public class RequestManager implements LifecycleListener {
38    private final Context context;
39    private Lifecycle lifecycle;
40    private final RequestTracker requestTracker;
41    private final Glide glide;
42    private final OptionsApplier optionsApplier;
43    private DefaultOptions options;
44
45    public RequestManager(Context context, Lifecycle lifecycle) {
46        this(context, lifecycle, new RequestTracker(), new ConnectivityMonitorFactory());
47    }
48
49    RequestManager(Context context, final Lifecycle lifecycle, RequestTracker requestTracker,
50            ConnectivityMonitorFactory factory) {
51        this.context = context;
52        this.lifecycle = lifecycle;
53        this.requestTracker = requestTracker;
54        this.glide = Glide.get(context);
55        this.optionsApplier = new OptionsApplier();
56
57        ConnectivityMonitor connectivityMonitor = factory.build(context,
58                new RequestManagerConnectivityListener(requestTracker));
59
60        // If we're the application level request manager, we may be created on a background thread. In that case we
61        // cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding
62        // ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.
63        if (Util.isOnBackgroundThread()) {
64            new Handler(Looper.getMainLooper()).post(new Runnable() {
65                @Override
66                public void run() {
67                    lifecycle.addListener(RequestManager.this);
68                }
69            });
70        } else {
71            lifecycle.addListener(this);
72        }
73        lifecycle.addListener(connectivityMonitor);
74    }
75
76    /**
77     * An interface that allows a default set of options to be applied to all requests started from an
78     * {@link com.bumptech.glide.RequestManager}.
79     */
80    public interface DefaultOptions {
81        /**
82         * Allows the implementor to apply some options to the given request.
83         *
84         * @param model The model that is being loaded.
85         * @param requestBuilder The request builder being used to construct the load.
86         * @param <T> The type of the model.
87         */
88        public <T> void apply(T model, GenericRequestBuilder<T, ?, ?, ?> requestBuilder);
89    }
90
91    /**
92     * Sets an interface that can apply some default options to all Requests started using this {@link RequestManager}.
93     *
94     * <p>
95     *     Note - These options will be retained for the life the of this {@link com.bumptech.glide.RequestManager}
96     *     so be wary of using
97     *     {@link com.bumptech.glide.GenericRequestBuilder#listener(com.bumptech.glide.request.RequestListener)}} when
98     *     starting requests using an {@link android.content.Context} or {@link android.app.Application} to avoid
99     *     leaking memory. Any option that does not use an anonymous inner class is generally safe.
100     * </p>
101     *
102     * @param options The default options to apply to all requests.
103     */
104    public void setDefaultOptions(DefaultOptions options) {
105        this.options = options;
106    }
107
108    /**
109     * Returns true if loads for this {@link RequestManager} are currently paused.
110     *
111     * @see #pauseRequests()
112     * @see #resumeRequests()
113     */
114    public boolean isPaused() {
115        Util.assertMainThread();
116        return requestTracker.isPaused();
117    }
118
119    /**
120     * Cancels any in progress loads, but does not clear resources of completed loads.
121     *
122     * @see #isPaused()
123     * @see #resumeRequests()
124     */
125    public void pauseRequests() {
126        Util.assertMainThread();
127        requestTracker.pauseRequests();
128    }
129
130    /**
131     * Restarts any loads that have not yet completed.
132     *
133     * @see #isPaused()
134     * @see #pauseRequests()
135     */
136    public void resumeRequests() {
137        Util.assertMainThread();
138        requestTracker.resumeRequests();
139    }
140
141    /**
142     * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
143     * permission is present) and restarts failed or paused requests.
144     */
145    @Override
146    public void onStart() {
147        // onStart might not be called because this object may be created after the fragment/activity's onStart method.
148        resumeRequests();
149    }
150
151    /**
152     * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
153     * permission is present) and pauses in progress loads.
154     */
155    @Override
156    public void onStop() {
157        pauseRequests();
158    }
159
160    /**
161     * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed
162     * requests.
163     */
164    @Override
165    public void onDestroy() {
166        requestTracker.clearRequests();
167    }
168
169    /**
170     * Use the given generic model loader to load the given generic data class.
171     * <p>
172     *     Warning - This is an experimental api that may change without a change in major version.
173     * </p>
174     * @param modelLoader The {@link ModelLoader} class to use to load the model.
175     * @param dataClass The type of data the {@link ModelLoader} will load.
176     * @param <A> The type of the model to be loaded.
177     * @param <T> The type of the data to be loaded from the mode.
178     * @return A {@link GenericModelRequest} to set options for the load and ultimately the target to load the model
179     * into.
180     */
181    public <A, T> GenericModelRequest<A, T> using(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
182        return new GenericModelRequest<A, T>(modelLoader, dataClass);
183    }
184
185    /**
186     * Set the {@link ModelLoader} to use for for a new load where the model loader translates from a model to an
187     * {@link InputStream} resource for loading images.
188     *
189     * @param modelLoader The model loader to use.
190     * @param <T> The type of the model.
191     * @return A new {@link ImageModelRequest}.
192     */
193    public <T> ImageModelRequest<T> using(final StreamModelLoader<T> modelLoader) {
194        return new ImageModelRequest<T>(modelLoader);
195    }
196
197    /**
198     * A convenience method to use a {@link StreamByteArrayLoader} to decode an image from a byte array.
199     *
200     * @param modelLoader The byte array loader.
201     * @return A new {@link ImageModelRequest}.
202     */
203    public ImageModelRequest<byte[]> using(StreamByteArrayLoader modelLoader) {
204        return new ImageModelRequest<byte[]>(modelLoader);
205    }
206
207    /**
208     * Set the {@link ModelLoader} to use for a new load where the model loader translates from a model to an
209     * {@link ParcelFileDescriptor} resource for loading video thumbnails.
210     *
211     * @param modelLoader The model loader to use.
212     * @param <T> The type of the model.
213     * @return A new {@link VideoModelRequest}.
214     */
215    public <T> VideoModelRequest<T> using(final FileDescriptorModelLoader<T> modelLoader) {
216        return new VideoModelRequest<T>(modelLoader);
217    }
218
219    /**
220     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently registered for {@link String} to load
221     * the image represented by the given {@link String}. Defaults to
222     * {@link com.bumptech.glide.load.model.stream.StreamStringLoader.Factory} and
223     * {@link com.bumptech.glide.load.model.stream.StreamStringLoader} to load the given model.
224     *
225     * @see #using(StreamModelLoader)
226     *
227     * @param string The string representing the image. Must be either a path, or a uri handled by
228     *      {@link com.bumptech.glide.load.model.stream.StreamUriLoader}
229     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
230     * into.
231     */
232    public DrawableTypeRequest<String> load(String string) {
233        return loadGeneric(string);
234    }
235
236    /**
237     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently registered for {@link Uri} to load the
238     * image at the given uri. Defaults to {@link com.bumptech.glide.load.model.stream.StreamUriLoader.Factory} and
239     * {@link com.bumptech.glide.load.model.stream.StreamUriLoader}.
240     *
241     * @see #using(StreamModelLoader)
242     *
243     * @param uri The uri representing the image. Must be a uri handled by
244     *      {@link com.bumptech.glide.load.model.stream.StreamUriLoader}
245     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
246     * into.
247     */
248    public DrawableTypeRequest<Uri> load(Uri uri) {
249        return loadGeneric(uri);
250    }
251
252    /**
253     * Use {@link android.provider.MediaStore.Images.Thumbnails} and
254     * {@link android.provider.MediaStore.Video.Thumbnails} to retrieve pre-generated thumbnails for the given uri.
255     *
256     * <p>
257     *  Falls back to the registered {@link com.bumptech.glide.load.model.ModelLoaderFactory} registered for
258     *  {@link Uri}s if the given uri is not a media store uri or if no pre-generated thumbnail exists for the given
259     *  uri. In addition, mixes the given mimeType, dateModified, and orientation into the cache key to detect and
260     *  invalidate thumbnails if content is changed locally.
261     * </p>
262     *
263     * @param uri The uri representing the media.
264     * @param mimeType The mime type of the media store media. Ok to default to empty string "". See
265     *      {@link android.provider.MediaStore.Images.ImageColumns#MIME_TYPE} or
266     *      {@link android.provider.MediaStore.Video.VideoColumns#MIME_TYPE}.
267     * @param dateModified The date modified time of the media store media. Ok to default to 0. See
268     *      {@link android.provider.MediaStore.Images.ImageColumns#DATE_MODIFIED} or
269     *      {@link android.provider.MediaStore.Video.VideoColumns#DATE_MODIFIED}.
270     * @param orientation The orientation of the media store media. Ok to default to 0. See
271     *      {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION}.
272     * @return A new {@link DrawableRequestBuilder} to set options for the load and ultimately the target to load the
273     *      uri into.
274     */
275    public DrawableTypeRequest<Uri> loadFromMediaStore(Uri uri, String mimeType, long dateModified, int orientation) {
276        ModelLoader<Uri, InputStream> genericStreamLoader = Glide.buildStreamModelLoader(uri, context);
277        ModelLoader<Uri, InputStream> mediaStoreLoader = new MediaStoreStreamLoader(context, genericStreamLoader,
278                mimeType, dateModified, orientation);
279        ModelLoader<Uri, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(uri,
280                context);
281        return optionsApplier.apply(uri, new DrawableTypeRequest<Uri>(uri, mediaStoreLoader, fileDescriptorModelLoader,
282                context, glide, requestTracker, lifecycle, optionsApplier));
283    }
284
285    /**
286     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently registered for {@link File} to load
287     * the image represented by the given {@link File}. Defaults to
288     * {@link com.bumptech.glide.load.model.stream.StreamFileLoader.Factory} and
289     * {@link com.bumptech.glide.load.model.stream.StreamFileLoader} to load the given model.
290     *
291     * @see #using(StreamModelLoader)
292     *
293     * @param file The File containing the image
294     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
295     * into.
296     */
297    public DrawableTypeRequest<File> load(File file) {
298        return loadGeneric(file);
299    }
300
301    /**
302     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently registered for {@link Integer} to load
303     * the image represented by the given {@link Integer} resource id. Defaults to
304     * {@link com.bumptech.glide.load.model.stream.StreamResourceLoader.Factory} and
305     * {@link com.bumptech.glide.load.model.stream.StreamResourceLoader} to load the given model.
306     *
307     * @see #using(StreamModelLoader)
308     *
309     * @param resourceId the id of the resource containing the image
310     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
311     * into.
312     */
313    public DrawableTypeRequest<Integer> load(Integer resourceId) {
314        return loadGeneric(resourceId);
315    }
316
317    /**
318     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently registered for {@link URL} to load the
319     * image represented by the given {@link URL}. Defaults to
320     * {@link com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader} and
321     * {@link com.bumptech.glide.load.data.HttpUrlFetcher} to load the given model.
322     *
323     * @see #using(StreamModelLoader)
324     *
325     * @param url The URL representing the image.
326     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
327     * into.
328     */
329    public DrawableTypeRequest<URL> load(URL url) {
330        return loadGeneric(url);
331    }
332
333    /**
334     * Use a new {@link StreamByteArrayLoader} to load an image from the given model.
335     *
336     * @see #load(byte[])
337     *
338     * @param model The data to load.
339     * @param id A unique id that identifies the image represented by the model suitable for use as a cache key
340     *           (url, filepath etc). If there is no suitable id, use {@link #load(byte[])} instaed.
341     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
342     * into.
343     */
344    public DrawableTypeRequest<byte[]> load(byte[] model, final String id) {
345        final StreamByteArrayLoader loader = new StreamByteArrayLoader(id);
346        return optionsApplier.apply(model,
347                new DrawableTypeRequest<byte[]>(model, loader, null, context, glide, requestTracker, lifecycle,
348                        optionsApplier));
349    }
350
351    /**
352     * Use a new {@link StreamByteArrayLoader} to load an image from the given model. Suitable when there is no
353     * simple id that represents the given data.
354     *
355     * @param model the data to load.
356     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
357     * into.
358     */
359    public DrawableTypeRequest<byte[]> load(byte[] model) {
360        return load(model, UUID.randomUUID().toString());
361    }
362
363    /**
364     * Use the {@link com.bumptech.glide.load.model.ModelLoaderFactory}s currently registered for the given model type
365     * for {@link InputStream}s and {@link ParcelFileDescriptor}s to load a thumbnail from either the image or the video
366     * represented by the given model.
367     *
368     * @param model The model the load.
369     * @param <T> The type of the model to load.
370     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
371     * into.
372     */
373    public <T> DrawableTypeRequest<T> load(T model) {
374        return loadGeneric(model);
375    }
376
377    private <T> DrawableTypeRequest<T> loadGeneric(T model) {
378        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(model, context);
379        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
380                Glide.buildFileDescriptorModelLoader(model, context);
381        if (model != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
382            throw new IllegalArgumentException("Unknown type " + model + ". You must provide a Model of a type for"
383                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
384                    + " Glide#register with a ModelLoaderFactory for your custom model class");
385        }
386        return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, streamModelLoader,
387                fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier));
388    }
389
390    /**
391     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
392     * {@link ParcelFileDescriptor} resources for loading video thumbnails.
393     *
394     * @param <T> The type of the model.
395     */
396    public final class VideoModelRequest<T> {
397        private final ModelLoader<T, ParcelFileDescriptor> loader;
398
399        private VideoModelRequest(ModelLoader<T, ParcelFileDescriptor> loader) {
400            this.loader = loader;
401        }
402
403        public DrawableTypeRequest<T> load(T model) {
404            return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, null, loader, context, glide,
405                    requestTracker, lifecycle, optionsApplier));
406        }
407    }
408
409    /**
410     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
411     * {@link InputStream} resources for loading images.
412     *
413     * @param <T> The type of the model.
414     */
415    public final class ImageModelRequest<T> {
416        private final ModelLoader<T, InputStream> loader;
417
418        private ImageModelRequest(ModelLoader<T, InputStream> loader) {
419            this.loader = loader;
420        }
421
422        public DrawableTypeRequest<T> load(T model) {
423            return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, loader, null, context, glide,
424                    requestTracker, lifecycle, optionsApplier));
425        }
426    }
427
428    /**
429     * A helper class for building requests with custom {@link ModelLoader}s that requires the user to provide a
430     * specific model.
431     *
432     * @param <A> The type of the model.
433     * @param <T> The type of data the {@link com.bumptech.glide.load.model.ModelLoader} provides an
434     * {@link com.bumptech.glide.load.data.DataFetcher} to convert the model to.
435     */
436    public final class GenericModelRequest<A, T> {
437        private final ModelLoader<A, T> modelLoader;
438        private final Class<T> dataClass;
439
440        private GenericModelRequest(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
441            this.modelLoader = modelLoader;
442            this.dataClass = dataClass;
443        }
444
445        /**
446         * Sets the specific model that will be loaded.
447         *
448         * @param model The model to use.
449         * @return This request builder.
450         */
451        public GenericTypeRequest load(A model) {
452            return new GenericTypeRequest(model);
453        }
454
455        /**
456         * A helper class for building requests with custom {@link com.bumptech.glide.load.model.ModelLoader}s that
457         * requires the user to specify a specific resource class that will be loaded.
458         *
459         */
460        public final class GenericTypeRequest {
461            private final A model;
462
463            private GenericTypeRequest(A model) {
464                this.model = model;
465            }
466
467            /**
468             * Sets the resource class that will be loaded.
469             *
470             * @param resourceClass The class of the resource that will be loaded.
471             * @param <Z> The type of the resource that will be loaded.
472             * @return This request builder.
473             */
474            public <Z> GenericTranscodeRequest<A, T, Z> as(Class<Z> resourceClass) {
475                return optionsApplier.apply(model, new GenericTranscodeRequest<A, T, Z>(context, glide, model,
476                        modelLoader, dataClass, resourceClass, requestTracker, lifecycle, optionsApplier));
477            }
478        }
479    }
480
481    class OptionsApplier {
482
483        public <A, X extends GenericRequestBuilder<A, ?, ?, ?>> X apply(A model, X builder) {
484            if (options != null) {
485                options.apply(model, builder);
486            }
487            return builder;
488        }
489
490    }
491
492    private static class RequestManagerConnectivityListener implements ConnectivityMonitor.ConnectivityListener {
493        private RequestTracker requestTracker;
494
495        public RequestManagerConnectivityListener(RequestTracker requestTracker) {
496            this.requestTracker = requestTracker;
497        }
498
499        @Override
500        public void onConnectivityChanged(boolean isConnected) {
501            if (isConnected) {
502                requestTracker.restartRequests();
503            }
504        }
505    }
506}
507