Skip to content

Commit d32af66

Browse files
falhassenglide-copybara-robot
authored andcommitted
...text exposed to open source public git repo...
PiperOrigin-RevId: 767344325
1 parent 8a23b1e commit d32af66

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

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

Lines changed: 43 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 intoFront(@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,
@@ -1001,6 +1014,36 @@ public Target<TranscodeType> preload(int width, int height) {
10011014
return into(target);
10021015
}
10031016

1017+
/**
1018+
* Preloads the resource into the cache using the given width and height; the callback will be set
1019+
* at the front of the queue.
1020+
*
1021+
* <p>Pre-loading is useful for making sure that resources you are going to to want in the near
1022+
* future are available quickly.
1023+
*
1024+
* <p>Note - Any thumbnail request that does not complete before the primary request will be
1025+
* cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after
1026+
* the primary request succeeds is a common behavior of all Glide requests. We do not try to
1027+
* prevent that behavior here. If you absolutely need all thumbnails to be preloaded individually,
1028+
* make separate preload() requests for each thumbnail (you can still combine them into one call
1029+
* when loading the image(s) into the UI in a subsequent request).
1030+
*
1031+
* @param width The desired width in pixels, or {@link Target#SIZE_ORIGINAL}. This will be
1032+
* overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)} if
1033+
* previously called.
1034+
* @param height The desired height in pixels, or {@link Target#SIZE_ORIGINAL}. This will be
1035+
* overridden by {@link com.bumptech.glide.request.RequestOptions#override(int, int)}} if
1036+
* previously called).
1037+
* @return A {@link Target} that can be used to cancel the load via {@link
1038+
* RequestManager#clear(Target)}.
1039+
* @see com.bumptech.glide.ListPreloader
1040+
*/
1041+
@NonNull
1042+
public Target<TranscodeType> preloadFront(int width, int height) {
1043+
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
1044+
return intoFront(target);
1045+
}
1046+
10041047
/**
10051048
* Preloads the resource into the cache using {@link Target#SIZE_ORIGINAL} as the target width and
10061049
* height. Equivalent to calling {@link #preload(int, int)} with {@link Target#SIZE_ORIGINAL} as

library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.bumptech.glide.load.engine;
22

33
import android.os.Build;
4+
import android.os.Process;
45
import android.util.Log;
56
import androidx.annotation.NonNull;
67
import androidx.core.util.Pools;
@@ -10,6 +11,7 @@
1011
import com.bumptech.glide.load.DataSource;
1112
import com.bumptech.glide.load.EncodeStrategy;
1213
import com.bumptech.glide.load.Key;
14+
import com.bumptech.glide.load.Option;
1315
import com.bumptech.glide.load.Options;
1416
import com.bumptech.glide.load.ResourceEncoder;
1517
import com.bumptech.glide.load.Transformation;
@@ -25,6 +27,7 @@
2527
import java.util.ArrayList;
2628
import java.util.List;
2729
import java.util.Map;
30+
import java.util.function.Supplier;
2831

2932
/**
3033
* A class responsible for decoding resources either from cached data or from the original source
@@ -41,6 +44,10 @@ class DecodeJob<R>
4144
Comparable<DecodeJob<?>>,
4245
Poolable {
4346
private static final String TAG = "DecodeJob";
47+
private static final Option<Supplier<Integer>> GLIDE_THREAD_PRIORITY =
48+
Option.memory("glide_thread_priority");
49+
private static final int DEFAULT_THREAD_PRIORITY =
50+
Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE;
4451

4552
private final DecodeHelper<R> decodeHelper = new DecodeHelper<>();
4653
private final List<Throwable> throwables = new ArrayList<>();
@@ -49,6 +56,7 @@ class DecodeJob<R>
4956
private final Pools.Pool<DecodeJob<?>> pool;
5057
private final DeferredEncodeManager<?> deferredEncodeManager = new DeferredEncodeManager<>();
5158
private final ReleaseManager releaseManager = new ReleaseManager();
59+
private final int threadPriority = Process.getThreadPriority(Process.myTid());
5260

5361
private GlideContext glideContext;
5462
private Key signature;
@@ -65,6 +73,7 @@ class DecodeJob<R>
6573
private long startFetchTime;
6674
private boolean onlyRetrieveFromCache;
6775
private Object model;
76+
private Supplier<Integer> glideThreadPriorityOverride;
6877

6978
private Thread currentThread;
7079
private Key currentSourceKey;
@@ -129,6 +138,7 @@ DecodeJob<R> init(
129138
this.order = order;
130139
this.runReason = RunReason.INITIALIZE;
131140
this.model = model;
141+
this.glideThreadPriorityOverride = options.get(GLIDE_THREAD_PRIORITY);
132142
return this;
133143
}
134144

@@ -326,7 +336,17 @@ private void runGenerators() {
326336
// onDataFetcherReady.
327337
}
328338

339+
private void restoreThreadPriority() {
340+
if (glideThreadPriorityOverride != null && glideThreadPriorityOverride.get() != null) {
341+
// Setting to default instead of original priority because threads can run multiple jobs at
342+
// once so if a new job is started before a previous higher priority job completes, that new
343+
// job will start with a higher priority.
344+
Process.setThreadPriority(Process.myTid(), DEFAULT_THREAD_PRIORITY);
345+
}
346+
}
347+
329348
private void notifyFailed() {
349+
restoreThreadPriority();
330350
setNotifiedOrThrow();
331351
GlideException e = new GlideException("Failed to load resource", new ArrayList<>(throwables));
332352
callback.onLoadFailed(e);
@@ -335,6 +355,7 @@ private void notifyFailed() {
335355

336356
private void notifyComplete(
337357
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
358+
restoreThreadPriority();
338359
setNotifiedOrThrow();
339360
callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
340361
}
@@ -429,6 +450,17 @@ private void decodeFromRetrievedData() {
429450
+ ", fetcher: "
430451
+ currentFetcher);
431452
}
453+
if (glideThreadPriorityOverride != null && glideThreadPriorityOverride.get() != null) {
454+
Log.d(
455+
"BigDawg",
456+
"Setting thread priority for thread "
457+
+ Process.myTid()
458+
+ " from "
459+
+ threadPriority
460+
+ " to "
461+
+ glideThreadPriorityOverride.get().intValue());
462+
Process.setThreadPriority(Process.myTid(), glideThreadPriorityOverride.get().intValue());
463+
}
432464
Resource<R> resource = null;
433465
try {
434466
resource = decodeFromData(currentFetcher, currentData, currentDataSource);

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: 92 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 testDoesNotThrowWithNullModelWhenRequestIsBuilt_Front() {
82+
getNullModelRequest().intoFront(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 testAddsNewRequestToRequestTracker_Front() {
94+
getNullModelRequest().intoFront(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 testRemovesPreviousRequestFromRequestTracker_Front() {
111+
Request previous = mock(Request.class);
112+
when(target.getRequest()).thenReturn(previous);
113+
114+
getNullModelRequest().intoFront(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 testThrowsIfGivenNullTarget_Front() {
127+
//noinspection ConstantConditions testing if @NonNull is enforced
128+
getNullModelRequest().intoFront((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 testThrowsIfGivenNullView_Front() {
138+
getNullModelRequest().intoFront((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 testThrowsIfIntoViewCalledOnBackgroundThread_Front() throws InterruptedException {
155+
final ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());
156+
testInBackground(
157+
new BackgroundTester() {
158+
@Override
159+
public void runTest() {
160+
getNullModelRequest().intoFront(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 doesNotThrowIfIntoTargetCalledOnBackgroundThread_Front() throws InterruptedException {
179+
final Target<Object> target = mock(Target.class);
180+
testInBackground(
181+
new BackgroundTester() {
182+
@Override
183+
public void runTest() {
184+
getNullModelRequest().intoFront(target);
185+
}
186+
});
187+
}
188+
132189
@Test
133190
public void testMultipleRequestListeners() {
134191
getNullModelRequest().addListener(listener1).addListener(listener2).into(target);
@@ -146,6 +203,23 @@ public void testMultipleRequestListeners() {
146203
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
147204
}
148205

206+
@Test
207+
public void testMultipleRequestListeners_Front() {
208+
getNullModelRequest().addListener(listener1).addListener(listener2).intoFront(target);
209+
verify(requestManager).track(any(Target.class), requestCaptor.capture());
210+
requestCaptor
211+
.getValue()
212+
.onResourceReady(
213+
new SimpleResource<>(new Object()),
214+
DataSource.LOCAL,
215+
/* isLoadedFromAlternateCacheKey= */ false);
216+
217+
verify(listener1)
218+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
219+
verify(listener2)
220+
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
221+
}
222+
149223
@Test
150224
public void testListenerApiOverridesListeners() {
151225
getNullModelRequest().addListener(listener1).listener(listener2).into(target);
@@ -164,6 +238,24 @@ public void testListenerApiOverridesListeners() {
164238
.onResourceReady(any(), any(), isA(Target.class), isA(DataSource.class), anyBoolean());
165239
}
166240

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

0 commit comments

Comments
 (0)