diff --git a/library/src/main/java/com/bumptech/glide/RequestBuilder.java b/library/src/main/java/com/bumptech/glide/RequestBuilder.java index 366da0bbec..3a559a9b2a 100644 --- a/library/src/main/java/com/bumptech/glide/RequestBuilder.java +++ b/library/src/main/java/com/bumptech/glide/RequestBuilder.java @@ -859,6 +859,13 @@ public FutureTarget submit(int width, int height) { *

Pre-loading is useful for making sure that resources you are going to to want in the near * future are available quickly. * + *

Note - Any thumbnail request that does not complete before the primary request will be + * cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after + * the primary request succeeds is a common behavior of all Glide requests. We do not try to + * prevent that behavior here. If you absolutely need all thumbnails to be preloaded individually, + * make separate preload() requests for each thumbnail (you can still combine them into one call + * when loading the image(s) into the UI in a subsequent request). + * * @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be * overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if * previously called. diff --git a/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java b/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java index 9521005322..6d3cb587b3 100644 --- a/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java +++ b/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.Request; import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.util.Synthetic; @@ -53,7 +54,17 @@ private PreloadTarget(RequestManager requestManager, int width, int height) { @Override public void onResourceReady(@NonNull Z resource, @Nullable Transition transition) { - HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget(); + // If a thumbnail request is set and the thumbnail completes, we don't want to cancel the + // primary load. Instead we wait until the primary request (the one set on the target) says + // that it is complete. + // Note - Any thumbnail request that does not complete before the primary request will be + // cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after + // the primary request succeeds is a common behavior of all Glide requests and we're not trying + // to override it here. + Request request = getRequest(); + if (request != null && request.isComplete()) { + HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget(); + } } @Override diff --git a/library/test/src/test/java/com/bumptech/glide/request/target/PreloadTargetTest.java b/library/test/src/test/java/com/bumptech/glide/request/target/PreloadTargetTest.java index 6c46169510..63cacaca87 100644 --- a/library/test/src/test/java/com/bumptech/glide/request/target/PreloadTargetTest.java +++ b/library/test/src/test/java/com/bumptech/glide/request/target/PreloadTargetTest.java @@ -2,9 +2,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.robolectric.annotation.LooperMode.Mode.LEGACY; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; +import android.os.Looper; import com.bumptech.glide.RequestManager; import com.bumptech.glide.request.Request; import org.junit.Before; @@ -14,9 +17,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.annotation.LooperMode; -@LooperMode(LEGACY) @RunWith(RobolectricTestRunner.class) @Config(sdk = 18) public class PreloadTargetTest { @@ -26,6 +27,7 @@ public class PreloadTargetTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + shadowOf(Looper.getMainLooper()).pause(); } @Test @@ -39,13 +41,58 @@ public void testCallsSizeReadyWithGivenDimensions() { verify(cb).onSizeReady(eq(width), eq(height)); } + // This isn't really supposed to happen, but just to double check... @Test - public void testClearsTargetInOnResourceReady() { + public void onResourceReady_withNullRequest_doesNotClearTarget() { + PreloadTarget target = PreloadTarget.obtain(requestManager, 100, 100); + target.setRequest(null); + + callOnResourceReadyAndRunUiRunnables(target); + + verify(requestManager, never()).clear(target); + } + + @Test + public void onResourceReady_withNotYetCompleteRequest_doesNotClearTarget() { + Request request = mock(Request.class); + when(request.isComplete()).thenReturn(false); + + PreloadTarget target = PreloadTarget.obtain(requestManager, 100, 100); + target.setRequest(request); + + callOnResourceReadyAndRunUiRunnables(target); + + verify(requestManager, never()).clear(target); + } + + @Test + public void onResourceReady_withCompleteRequest_postsToClearTarget() { + Request request = mock(Request.class); + when(request.isComplete()).thenReturn(true); + + PreloadTarget target = PreloadTarget.obtain(requestManager, 100, 100); + target.setRequest(request); + + callOnResourceReadyAndRunUiRunnables(target); + + verify(requestManager).clear(target); + } + + @Test + public void onResourceReady_withCompleteRequest_doesNotImmediatelyClearTarget() { Request request = mock(Request.class); + when(request.isComplete()).thenReturn(true); + PreloadTarget target = PreloadTarget.obtain(requestManager, 100, 100); target.setRequest(request); - target.onResourceReady(new Object(), null); - verify(requestManager).clear(eq(target)); + target.onResourceReady(new Object(), /* transition= */ null); + + verify(requestManager, never()).clear(target); + } + + private void callOnResourceReadyAndRunUiRunnables(Target target) { + target.onResourceReady(new Object(), /* transition= */ null); + shadowOf(Looper.getMainLooper()).idle(); } }