Skip to content

Commit 856fd82

Browse files
falhassenglide-copybara-robot
authored andcommitted
Add new functionality to Glide for posting Glide onResourceReady callback to the front of the main thread queue after job completion
PiperOrigin-RevId: 767679744
1 parent 8a23b1e commit 856fd82

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

library/src/main/java/com/bumptech/glide/RequestBuilder.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,19 @@ public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
818818
return into(target, /* targetListener= */ null, Executors.mainThreadExecutor());
819819
}
820820

821+
/**
822+
* Set the target the resource will be loaded into; the callback will be set at the front of the
823+
* queue.
824+
*
825+
* @param target The target to load the resource into.
826+
* @return The given target.
827+
* @see RequestManager#clear(Target)
828+
*/
829+
@NonNull
830+
public <Y extends Target<TranscodeType>> Y experimentalIntoFront(@NonNull Y target) {
831+
return into(target, /* targetListener= */ null, Executors.mainThreadExecutorFront());
832+
}
833+
821834
@NonNull
822835
<Y extends Target<TranscodeType>> Y into(
823836
@NonNull Y target,
@@ -922,6 +935,57 @@ public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
922935
Executors.mainThreadExecutor());
923936
}
924937

938+
/**
939+
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
940+
* the view, and frees any resources Glide may have previously loaded into the view so they may be
941+
* reused; the callback will be set at the front of the queue.
942+
*
943+
* @see RequestManager#clear(Target)
944+
* @param view The view to cancel previous loads for and load the new resource into.
945+
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link
946+
* ImageView}.
947+
*/
948+
@NonNull
949+
public ViewTarget<ImageView, TranscodeType> experimentalIntoFront(@NonNull ImageView view) {
950+
Util.assertMainThread();
951+
Preconditions.checkNotNull(view);
952+
953+
BaseRequestOptions<?> requestOptions = this;
954+
if (!requestOptions.isTransformationSet()
955+
&& requestOptions.isTransformationAllowed()
956+
&& view.getScaleType() != null) {
957+
// Clone in this method so that if we use this RequestBuilder to load into a View and then
958+
// into a different target, we don't retain the transformation applied based on the previous
959+
// View's scale type.
960+
switch (view.getScaleType()) {
961+
case CENTER_CROP:
962+
requestOptions = requestOptions.clone().optionalCenterCrop();
963+
break;
964+
case CENTER_INSIDE:
965+
requestOptions = requestOptions.clone().optionalCenterInside();
966+
break;
967+
case FIT_CENTER:
968+
case FIT_START:
969+
case FIT_END:
970+
requestOptions = requestOptions.clone().optionalFitCenter();
971+
break;
972+
case FIT_XY:
973+
requestOptions = requestOptions.clone().optionalCenterInside();
974+
break;
975+
case CENTER:
976+
case MATRIX:
977+
default:
978+
// Do nothing.
979+
}
980+
}
981+
982+
return into(
983+
glideContext.buildImageViewTarget(view, transcodeClass),
984+
/* targetListener= */ null,
985+
requestOptions,
986+
Executors.mainThreadExecutorFront());
987+
}
988+
925989
/**
926990
* Returns a future that can be used to do a blocking get on a background thread.
927991
*
@@ -1001,6 +1065,36 @@ public Target<TranscodeType> preload(int width, int height) {
10011065
return into(target);
10021066
}
10031067

1068+
/**
1069+
* Preloads the resource into the cache using the given width and height; the callback will be set
1070+
* at the front of the queue.
1071+
*
1072+
* <p>Pre-loading is useful for making sure that resources you are going to to want in the near
1073+
* future are available quickly.
1074+
*
1075+
* <p>Note - Any thumbnail request that does not complete before the primary request will be
1076+
* cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after
1077+
* the primary request succeeds is a common behavior of all Glide requests. We do not try to
1078+
* prevent that behavior here. If you absolutely need all thumbnails to be preloaded individually,
1079+
* make separate preload() requests for each thumbnail (you can still combine them into one call
1080+
* when loading the image(s) into the UI in a subsequent request).
1081+
*
1082+
* @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be
1083+
* overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if
1084+
* previously called.
1085+
* @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be
1086+
* overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if
1087+
* previously called).
1088+
* @return A {@link Target} that can be used to cancel the load via {@link
1089+
* RequestManager#clear(Target)}.
1090+
* @see com.bumptech.glide.ListPreloader
1091+
*/
1092+
@NonNull
1093+
public Target<TranscodeType> experimentalPreloadFront(int width, int height) {
1094+
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
1095+
return experimentalIntoFront(target);
1096+
}
1097+
10041098
/**
10051099
* Preloads the resource into the cache using {@link Target#SIZE_ORIGINAL} as the target width and
10061100
* height. Equivalent to calling {@link #preload(int, int)} with {@link Target#SIZE_ORIGINAL} as

library/src/main/java/com/bumptech/glide/util/Executors.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public void execute(@NonNull Runnable command) {
1919
Util.postOnUiThread(command);
2020
}
2121
};
22+
private static final Executor MAIN_THREAD_EXECUTOR_FRONT =
23+
new Executor() {
24+
@Override
25+
public void execute(@NonNull Runnable command) {
26+
Util.postAtFrontOfQueueOnUiThread(command);
27+
}
28+
};
2229
private static final Executor DIRECT_EXECUTOR =
2330
new Executor() {
2431
@Override
@@ -32,6 +39,11 @@ public static Executor mainThreadExecutor() {
3239
return MAIN_THREAD_EXECUTOR;
3340
}
3441

42+
/** Posts executions to the main thread at the front of the queue. */
43+
public static Executor mainThreadExecutorFront() {
44+
return MAIN_THREAD_EXECUTOR_FRONT;
45+
}
46+
3547
/** Immediately calls {@link Runnable#run()} on the current thread. */
3648
public static Executor directExecutor() {
3749
return DIRECT_EXECUTOR;

library/src/main/java/com/bumptech/glide/util/Util.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ public static void postOnUiThread(Runnable runnable) {
151151
getUiThreadHandler().post(runnable);
152152
}
153153

154+
/**
155+
* Posts the given {@code runnable} to the front of the queue on the UI thread using a shared
156+
* {@link Handler}.
157+
*/
158+
public static void postAtFrontOfQueueOnUiThread(Runnable runnable) {
159+
getUiThreadHandler().postAtFrontOfQueue(runnable);
160+
}
161+
154162
/** Removes the given {@code runnable} from the UI threads queue if it is still queued. */
155163
public static void removeCallbacksOnUiThread(Runnable runnable) {
156164
getUiThreadHandler().removeCallbacks(runnable);

library/test/src/test/java/com/bumptech/glide/RequestBuilderTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,25 @@ public void testDoesNotThrowWithNullModelWhenRequestIsBuilt() {
7777
getNullModelRequest().into(target);
7878
}
7979

80+
@Test
81+
public void testDoesNotThrowWithNullModelWhenRequestIsBuiltFront() {
82+
getNullModelRequest().experimentalIntoFront(target);
83+
}
84+
8085
@Test
8186
public void testAddsNewRequestToRequestTracker() {
8287
getNullModelRequest().into(target);
8388

8489
verify(requestManager).track(eq(target), isA(Request.class));
8590
}
8691

92+
@Test
93+
public void testAddsNewRequestToRequestTrackerFront() {
94+
getNullModelRequest().experimentalIntoFront(target);
95+
96+
verify(requestManager).track(eq(target), isA(Request.class));
97+
}
98+
8799
@Test
88100
public void testRemovesPreviousRequestFromRequestTracker() {
89101
Request previous = mock(Request.class);
@@ -94,17 +106,38 @@ public void testRemovesPreviousRequestFromRequestTracker() {
94106
verify(requestManager).clear(eq(target));
95107
}
96108

109+
@Test
110+
public void testRemovesPreviousRequestFromRequestTrackerFront() {
111+
Request previous = mock(Request.class);
112+
when(target.getRequest()).thenReturn(previous);
113+
114+
getNullModelRequest().experimentalIntoFront(target);
115+
116+
verify(requestManager).clear(eq(target));
117+
}
118+
97119
@Test(expected = NullPointerException.class)
98120
public void testThrowsIfGivenNullTarget() {
99121
//noinspection ConstantConditions testing if @NonNull is enforced
100122
getNullModelRequest().into((Target<Object>) null);
101123
}
102124

125+
@Test(expected = NullPointerException.class)
126+
public void testThrowsIfGivenNullTargetFront() {
127+
//noinspection ConstantConditions testing if @NonNull is enforced
128+
getNullModelRequest().experimentalIntoFront((Target<Object>) null);
129+
}
130+
103131
@Test(expected = NullPointerException.class)
104132
public void testThrowsIfGivenNullView() {
105133
getNullModelRequest().into((ImageView) null);
106134
}
107135

136+
@Test(expected = NullPointerException.class)
137+
public void testThrowsIfGivenNullViewFront() {
138+
getNullModelRequest().experimentalIntoFront((ImageView) null);
139+
}
140+
108141
@Test(expected = RuntimeException.class)
109142
public void testThrowsIfIntoViewCalledOnBackgroundThread() throws InterruptedException {
110143
final ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());
@@ -117,6 +150,18 @@ public void runTest() {
117150
});
118151
}
119152

153+
@Test(expected = RuntimeException.class)
154+
public void testThrowsIfIntoViewCalledOnBackgroundThreadFront() throws InterruptedException {
155+
final ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());
156+
testInBackground(
157+
new BackgroundTester() {
158+
@Override
159+
public void runTest() {
160+
getNullModelRequest().experimentalIntoFront(imageView);
161+
}
162+
});
163+
}
164+
120165
@Test
121166
public void doesNotThrowIfIntoTargetCalledOnBackgroundThread() throws InterruptedException {
122167
final Target<Object> target = mock(Target.class);
@@ -129,6 +174,18 @@ public void runTest() {
129174
});
130175
}
131176

177+
@Test
178+
public void doesNotThrowIfIntoTargetCalledOnBackgroundThreadFront() throws InterruptedException {
179+
final Target<Object> target = mock(Target.class);
180+
testInBackground(
181+
new BackgroundTester() {
182+
@Override
183+
public void runTest() {
184+
getNullModelRequest().experimentalIntoFront(target);
185+
}
186+
});
187+
}
188+
132189
@Test
133190
public void testMultipleRequestListeners() {
134191
getNullModelRequest().addListener(listener1).addListener(listener2).into(target);
@@ -146,6 +203,26 @@ public void testMultipleRequestListeners() {
146203
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
147204
}
148205

206+
@Test
207+
public void testMultipleRequestListenersFront() {
208+
getNullModelRequest()
209+
.addListener(listener1)
210+
.addListener(listener2)
211+
.experimentalIntoFront(target);
212+
verify(requestManager).track(any(Target.class), requestCaptor.capture());
213+
requestCaptor
214+
.getValue()
215+
.onResourceReady(
216+
new SimpleResource<>(new Object()),
217+
DataSource.LOCAL,
218+
/* isLoadedFromAlternateCacheKey= */ false);
219+
220+
verify(listener1)
221+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
222+
verify(listener2)
223+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
224+
}
225+
149226
@Test
150227
public void testListenerApiOverridesListeners() {
151228
getNullModelRequest().addListener(listener1).listener(listener2).into(target);
@@ -164,6 +241,24 @@ public void testListenerApiOverridesListeners() {
164241
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
165242
}
166243

244+
@Test
245+
public void testListenerApiOverridesListenersFront() {
246+
getNullModelRequest().addListener(listener1).listener(listener2).experimentalIntoFront(target);
247+
verify(requestManager).track(any(Target.class), requestCaptor.capture());
248+
requestCaptor
249+
.getValue()
250+
.onResourceReady(
251+
new SimpleResource<>(new Object()),
252+
DataSource.LOCAL,
253+
/* isLoadedFromAlternateCacheKey= */ false);
254+
255+
// The #listener API removes any previous listeners, so the first listener should not be called.
256+
verify(listener1, never())
257+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
258+
verify(listener2)
259+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
260+
}
261+
167262
@Test
168263
public void testEquals() {
169264
Object firstModel = new Object();

0 commit comments

Comments
 (0)