diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java index f200fa96cc..89b950cd8f 100644 --- a/library/src/main/java/com/bumptech/glide/Glide.java +++ b/library/src/main/java/com/bumptech/glide/Glide.java @@ -22,6 +22,7 @@ import android.view.View; import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.load.DecodeFormat; +import com.bumptech.glide.load.ImageHeaderParser; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.data.InputStreamRewinder; import com.bumptech.glide.load.engine.Engine; @@ -76,6 +77,7 @@ import com.bumptech.glide.manager.ConnectivityMonitorFactory; import com.bumptech.glide.manager.RequestManagerRetriever; import com.bumptech.glide.module.ManifestParser; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.ImageViewTargetFactory; import com.bumptech.glide.request.target.Target; @@ -319,7 +321,8 @@ private static void throwIncorrectGlideModule(Exception e) { @NonNull ConnectivityMonitorFactory connectivityMonitorFactory, int logLevel, @NonNull RequestOptions defaultRequestOptions, - @NonNull Map, TransitionOptions> defaultTransitionOptions) { + @NonNull Map, TransitionOptions> defaultTransitionOptions, + @NonNull List> defaultRequestListeners) { this.engine = engine; this.bitmapPool = bitmapPool; this.arrayPool = arrayPool; @@ -342,10 +345,15 @@ private static void throwIncorrectGlideModule(Exception e) { } registry.register(new DefaultImageHeaderParser()); - Downsampler downsampler = new Downsampler(registry.getImageHeaderParsers(), - resources.getDisplayMetrics(), bitmapPool, arrayPool); + List imageHeaderParsers = registry.getImageHeaderParsers(); + Downsampler downsampler = + new Downsampler( + imageHeaderParsers, + resources.getDisplayMetrics(), + bitmapPool, + arrayPool); ByteBufferGifDecoder byteBufferGifDecoder = - new ByteBufferGifDecoder(context, registry.getImageHeaderParsers(), bitmapPool, arrayPool); + new ByteBufferGifDecoder(context, imageHeaderParsers, bitmapPool, arrayPool); ResourceDecoder parcelFileDescriptorVideoDecoder = VideoDecoder.parcel(bitmapPool); ByteBufferBitmapDecoder byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler); @@ -409,7 +417,7 @@ Registry.BUCKET_BITMAP, Bitmap.class, Bitmap.class, new UnitBitmapDecoder()) Registry.BUCKET_GIF, InputStream.class, GifDrawable.class, - new StreamGifDecoder(registry.getImageHeaderParsers(), byteBufferGifDecoder, arrayPool)) + new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool)) .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder) .append(GifDrawable.class, new GifDrawableEncoder()) /* GIF Frames */ @@ -512,6 +520,7 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, + defaultRequestListeners, engine, logLevel); } diff --git a/library/src/main/java/com/bumptech/glide/GlideBuilder.java b/library/src/main/java/com/bumptech/glide/GlideBuilder.java index 3112582078..bdfae965b3 100644 --- a/library/src/main/java/com/bumptech/glide/GlideBuilder.java +++ b/library/src/main/java/com/bumptech/glide/GlideBuilder.java @@ -6,7 +6,9 @@ import android.support.annotation.Nullable; import android.support.v4.util.ArrayMap; import android.util.Log; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.Engine; +import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter; @@ -22,8 +24,12 @@ import com.bumptech.glide.manager.DefaultConnectivityMonitorFactory; import com.bumptech.glide.manager.RequestManagerRetriever; import com.bumptech.glide.manager.RequestManagerRetriever.RequestManagerFactory; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -46,6 +52,8 @@ public final class GlideBuilder { private RequestManagerFactory requestManagerFactory; private GlideExecutor animationExecutor; private boolean isActiveResourceRetentionAllowed; + @Nullable + private List> defaultRequestListeners; /** * Sets the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation to use @@ -372,6 +380,32 @@ public GlideBuilder setIsActiveResourceRetentionAllowed( return this; } + /** + * Adds a global {@link RequestListener} that will be added to every request started with Glide. + * + *

Multiple {@link RequestListener}s can be added here, in {@link RequestManager} scopes or + * to individual {@link RequestBuilder}s. {@link RequestListener}s are called in the order they're + * added. Even if an earlier {@link RequestListener} returns {@code true} from + * {@link RequestListener#onLoadFailed(GlideException, Object, Target, boolean)} or + * {@link RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}, it will + * not prevent subsequent {@link RequestListener}s from being called. + * + *

Because Glide requests can be started for any number of individual resource types, any + * listener added here has to accept any generic resource type in + * {@link RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}. If you + * must base the behavior of the listener on the resource type, you will need to use + * {@code instanceof} to do so. It's not safe to cast resource types without first checking + * with {@code instanceof}. + */ + @NonNull + public GlideBuilder addGlobalRequestListener(@NonNull RequestListener listener) { + if (defaultRequestListeners == null) { + defaultRequestListeners = new ArrayList<>(); + } + defaultRequestListeners.add(listener); + return this; + } + void setRequestManagerFactory(@Nullable RequestManagerFactory factory) { this.requestManagerFactory = factory; } @@ -437,6 +471,12 @@ Glide build(@NonNull Context context) { isActiveResourceRetentionAllowed); } + if (defaultRequestListeners == null) { + defaultRequestListeners = Collections.emptyList(); + } else { + defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners); + } + RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory); @@ -450,6 +490,7 @@ Glide build(@NonNull Context context) { connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), - defaultTransitionOptions); + defaultTransitionOptions, + defaultRequestListeners); } } diff --git a/library/src/main/java/com/bumptech/glide/GlideContext.java b/library/src/main/java/com/bumptech/glide/GlideContext.java index eeb98cc5f7..a587b19bd4 100644 --- a/library/src/main/java/com/bumptech/glide/GlideContext.java +++ b/library/src/main/java/com/bumptech/glide/GlideContext.java @@ -9,9 +9,11 @@ import android.widget.ImageView; import com.bumptech.glide.load.engine.Engine; import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.ImageViewTargetFactory; import com.bumptech.glide.request.target.ViewTarget; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -28,6 +30,7 @@ public class GlideContext extends ContextWrapper { private final Registry registry; private final ImageViewTargetFactory imageViewTargetFactory; private final RequestOptions defaultRequestOptions; + private final List> defaultRequestListeners; private final Map, TransitionOptions> defaultTransitionOptions; private final Engine engine; private final int logLevel; @@ -39,6 +42,7 @@ public GlideContext( @NonNull ImageViewTargetFactory imageViewTargetFactory, @NonNull RequestOptions defaultRequestOptions, @NonNull Map, TransitionOptions> defaultTransitionOptions, + @NonNull List> defaultRequestListeners, @NonNull Engine engine, int logLevel) { super(context.getApplicationContext()); @@ -46,6 +50,7 @@ public GlideContext( this.registry = registry; this.imageViewTargetFactory = imageViewTargetFactory; this.defaultRequestOptions = defaultRequestOptions; + this.defaultRequestListeners = defaultRequestListeners; this.defaultTransitionOptions = defaultTransitionOptions; this.engine = engine; this.logLevel = logLevel; @@ -53,6 +58,10 @@ public GlideContext( mainHandler = new Handler(Looper.getMainLooper()); } + public List> getDefaultRequestListeners() { + return defaultRequestListeners; + } + public RequestOptions getDefaultRequestOptions() { return defaultRequestOptions; } diff --git a/library/src/main/java/com/bumptech/glide/RequestBuilder.java b/library/src/main/java/com/bumptech/glide/RequestBuilder.java index 1438311e2d..54c520e0aa 100644 --- a/library/src/main/java/com/bumptech/glide/RequestBuilder.java +++ b/library/src/main/java/com/bumptech/glide/RequestBuilder.java @@ -4,6 +4,7 @@ import static com.bumptech.glide.request.RequestOptions.signatureOf; import static com.bumptech.glide.request.RequestOptions.skipMemoryCacheOf; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; @@ -76,8 +77,12 @@ public class RequestBuilder implements Cloneable, private boolean isModelSet; private boolean isThumbnailBuilt; - protected RequestBuilder(Glide glide, RequestManager requestManager, - Class transcodeClass, Context context) { + @SuppressLint("CheckResult") + protected RequestBuilder( + @NonNull Glide glide, + RequestManager requestManager, + Class transcodeClass, + Context context) { this.glide = glide; this.requestManager = requestManager; this.transcodeClass = transcodeClass; @@ -86,6 +91,8 @@ protected RequestBuilder(Glide glide, RequestManager requestManager, this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass); this.requestOptions = defaultRequestOptions; this.glideContext = glide.getGlideContext(); + + initRequestListeners(requestManager.getDefaultRequestListeners()); } protected RequestBuilder(Class transcodeClass, RequestBuilder other) { @@ -95,6 +102,16 @@ protected RequestBuilder(Class transcodeClass, RequestBuilder requestOptions = other.requestOptions; } + // Casting from Object to a specific type is always safe. + @SuppressWarnings("unchecked") + // addListener always returns the same instance. + @SuppressLint("CheckResult") + private void initRequestListeners(List> requestListeners) { + for (RequestListener listener : requestListeners) { + addListener((RequestListener) listener); + } + } + /** * Applies the given options to the request. * diff --git a/library/src/main/java/com/bumptech/glide/RequestManager.java b/library/src/main/java/com/bumptech/glide/RequestManager.java index 139df6f433..9e0cdb6c33 100644 --- a/library/src/main/java/com/bumptech/glide/RequestManager.java +++ b/library/src/main/java/com/bumptech/glide/RequestManager.java @@ -16,7 +16,9 @@ import android.support.annotation.Nullable; import android.support.annotation.RawRes; import android.view.View; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.manager.ConnectivityMonitor; import com.bumptech.glide.manager.ConnectivityMonitorFactory; @@ -26,6 +28,7 @@ import com.bumptech.glide.manager.RequestTracker; import com.bumptech.glide.manager.TargetTracker; import com.bumptech.glide.request.Request; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.ViewTarget; @@ -34,6 +37,8 @@ import com.bumptech.glide.util.Util; import java.io.File; import java.net.URL; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity @@ -69,6 +74,10 @@ public void run() { }; private final Handler mainHandler = new Handler(Looper.getMainLooper()); private final ConnectivityMonitor connectivityMonitor; + // Adding default listeners should be much less common than starting new requests. We want + // some way of making sure that requests don't mutate our listeners without creating a new copy of + // the list each time a request is started. + private final CopyOnWriteArrayList> defaultRequestListeners; private RequestOptions requestOptions; @@ -115,6 +124,8 @@ public RequestManager( } lifecycle.addListener(connectivityMonitor); + defaultRequestListeners = + new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners()); setRequestOptions(glide.getGlideContext().getDefaultRequestOptions()); glide.registerRequestManager(this); @@ -174,6 +185,29 @@ public RequestManager setDefaultRequestOptions(@NonNull RequestOptions requestOp return this; } + /** + * Adds a default {@link RequestListener} that will be added to every request started with this + * {@link RequestManager}. + * + *

Multiple {@link RequestListener}s can be added here, in {@link RequestManager} scopes or + * to individual {@link RequestBuilder}s. {@link RequestListener}s are called in the order they're + * added. Even if an earlier {@link RequestListener} returns {@code true} from + * {@link RequestListener#onLoadFailed(GlideException, Object, Target, boolean)} or + * {@link RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}, it will + * not prevent subsequent {@link RequestListener}s from being called. + * + *

Because Glide requests can be started for any number of individual resource types, any + * listener added here has to accept any generic resource type in + * {@link RequestListener#onResourceReady(Object, Object, Target, DataSource, boolean)}. If you + * must base the behavior of the listener on the resource type, you will need to use + * {@code instanceof} to do so. It's not safe to cast resource types without first checking + * with {@code instanceof}. + */ + public RequestManager addDefaultRequestListener(RequestListener requestListener) { + defaultRequestListeners.add(requestListener); + return this; + } + /** * Returns true if loads for this {@link RequestManager} are currently paused. * @@ -614,6 +648,10 @@ void track(@NonNull Target target, @NonNull Request request) { requestTracker.runRequest(request); } + List> getDefaultRequestListeners() { + return defaultRequestListeners; + } + RequestOptions getDefaultRequestOptions() { return requestOptions; } diff --git a/library/test/src/test/java/com/bumptech/glide/GlideContextTest.java b/library/test/src/test/java/com/bumptech/glide/GlideContextTest.java index 2281d57069..dc5df1cbd3 100644 --- a/library/test/src/test/java/com/bumptech/glide/GlideContextTest.java +++ b/library/test/src/test/java/com/bumptech/glide/GlideContextTest.java @@ -14,6 +14,7 @@ import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.ImageViewTargetFactory; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.Before; @@ -41,6 +42,7 @@ public void setUp() { new ImageViewTargetFactory(), new RequestOptions(), transitionOptions, + /*defaultRequestListeners=*/ Collections.emptyList(), mock(Engine.class), Log.DEBUG); }