Allow target preload status to be null to indicate not to preload

PiperOrigin-RevId: 619246259
This commit is contained in:
tianyifeng 2024-03-26 10:59:30 -07:00 committed by Copybara-Service
parent 98dac54816
commit 8f2f3bb7e4
5 changed files with 107 additions and 39 deletions

View File

@ -30,6 +30,9 @@
should be shown. Custom `SimpleDecoder` implementations can check should be shown. Custom `SimpleDecoder` implementations can check
`isAtLeastOutputStartTimeUs` if needed or mark other buffers with `isAtLeastOutputStartTimeUs` if needed or mark other buffers with
`DecoderOutputBuffer.shouldBeSkipped` to skip them. `DecoderOutputBuffer.shouldBeSkipped` to skip them.
* Allow a null value to be returned by
`TargetPreloadStatusControl.getTargetPreloadStatus(T)` to indicate not
to preload a `MediaSource` with the given `rankingData`.
* Transformer: * Transformer:
* Add `audioConversionProcess` and `videoConversionProcess` to * Add `audioConversionProcess` and `videoConversionProcess` to
`ExportResult` indicating how the respective track in the output file `ExportResult` indicating how the respective track in the output file

View File

@ -126,7 +126,9 @@ public abstract class BasePreloadManager<T> {
synchronized (lock) { synchronized (lock) {
sourceHolderPriorityQueue.clear(); sourceHolderPriorityQueue.clear();
sourceHolderPriorityQueue.addAll(mediaItemMediaSourceHolderMap.values()); sourceHolderPriorityQueue.addAll(mediaItemMediaSourceHolderMap.values());
maybeStartPreloadNextSource(); while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource()) {
sourceHolderPriorityQueue.poll();
}
} }
} }
@ -180,8 +182,9 @@ public abstract class BasePreloadManager<T> {
|| checkNotNull(sourceHolderPriorityQueue.peek()).mediaSource != source) { || checkNotNull(sourceHolderPriorityQueue.peek()).mediaSource != source) {
return; return;
} }
sourceHolderPriorityQueue.poll(); do {
maybeStartPreloadNextSource(); sourceHolderPriorityQueue.poll();
} while (!sourceHolderPriorityQueue.isEmpty() && !maybeStartPreloadNextSource());
} }
}); });
} }
@ -237,14 +240,26 @@ public abstract class BasePreloadManager<T> {
/** Releases the preload manager, see {@link #release()}. */ /** Releases the preload manager, see {@link #release()}. */
protected void releaseInternal() {} protected void releaseInternal() {}
/**
* Starts to preload the {@link MediaSource} at the head of the priority queue, if the {@linkplain
* TargetPreloadStatusControl.PreloadStatus target preload status} for that source is not null.
*
* @return {@code true} if the {@link MediaSource} at the head of the priority queue starts to
* preload, otherwise {@code false}.
* @throws NullPointerException if the priority queue is empty.
*/
@GuardedBy("lock") @GuardedBy("lock")
private void maybeStartPreloadNextSource() { private boolean maybeStartPreloadNextSource() {
if (!sourceHolderPriorityQueue.isEmpty() && shouldStartPreloadingNextSource()) { if (shouldStartPreloadingNextSource()) {
MediaSourceHolder preloadingHolder = checkNotNull(sourceHolderPriorityQueue.peek()); MediaSourceHolder preloadingHolder = checkNotNull(sourceHolderPriorityQueue.peek());
this.targetPreloadStatusOfCurrentPreloadingSource = this.targetPreloadStatusOfCurrentPreloadingSource =
targetPreloadStatusControl.getTargetPreloadStatus(preloadingHolder.rankingData); targetPreloadStatusControl.getTargetPreloadStatus(preloadingHolder.rankingData);
preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs); if (targetPreloadStatusOfCurrentPreloadingSource != null) {
preloadSourceInternal(preloadingHolder.mediaSource, preloadingHolder.startPositionUs);
return true;
}
} }
return false;
} }
/** A holder for information for preloading a single media source. */ /** A holder for information for preloading a single media source. */

View File

@ -17,7 +17,6 @@ package androidx.media3.exoplayer.source.preload;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.Math.abs; import static java.lang.Math.abs;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
@ -233,12 +232,13 @@ public final class DefaultPreloadManager extends BasePreloadManager<Integer> {
@Nullable @Nullable
TargetPreloadStatusControl.PreloadStatus targetPreloadStatus = TargetPreloadStatusControl.PreloadStatus targetPreloadStatus =
getTargetPreloadStatus(mediaSource); getTargetPreloadStatus(mediaSource);
checkState(targetPreloadStatus instanceof Status); if (targetPreloadStatus != null) {
Status status = (Status) targetPreloadStatus; Status status = (Status) targetPreloadStatus;
if (continueLoadingPredicate.apply(checkNotNull(status))) { if (continueLoadingPredicate.apply(checkNotNull(status))) {
return true; return true;
}
onPreloadCompleted(mediaSource);
} }
onPreloadCompleted(mediaSource);
return false; return false;
} }
} }

View File

@ -15,16 +15,22 @@
*/ */
package androidx.media3.exoplayer.source.preload; package androidx.media3.exoplayer.source.preload;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaSource;
/** Controls the target preload status. */ /** Controls the target preload status. */
@UnstableApi @UnstableApi
public interface TargetPreloadStatusControl<T> { public interface TargetPreloadStatusControl<T> {
/** Returns the target preload status for a source with the given {@code rankingData}. */ /**
* Returns the target preload status for a source with the given {@code rankingData}. May be null
* if a {@link MediaSource} with the given {@code rankingData} should not be preloaded.
*/
@Nullable
PreloadStatus getTargetPreloadStatus(T rankingData); PreloadStatus getTargetPreloadStatus(T rankingData);
/** Defines the status of the preloading for a source. */ /** Defines the status of the preloading for a {@link MediaSource}. */
interface PreloadStatus { interface PreloadStatus {
/** The stage of the preloading. */ /** The stage of the preloading. */

View File

@ -166,10 +166,10 @@ public class DefaultPreloadManagerTest {
@Test @Test
public void public void
invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { invalidate_withoutSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
@ -190,7 +190,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 = MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false); wrappedMediaSource0.setAllowPreparation(false);
@ -200,21 +199,22 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false); wrappedMediaSource2.setAllowPreparation(false);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0); assertThat(targetPreloadStatusControlCallStates).containsExactly(0);
wrappedMediaSource0.setAllowPreparation(true); wrappedMediaSource0.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
} }
@Test @Test
public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() { public void invalidate_withSettingCurrentPlayingIndex_sourcesPreloadedToTargetStatusesInOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
@ -235,7 +235,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 = MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build(); mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false); wrappedMediaSource0.setAllowPreparation(false);
@ -245,27 +244,27 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false); wrappedMediaSource2.setAllowPreparation(false);
PreloadMediaSource preloadMediaSource2 = PreloadMediaSource preloadMediaSource2 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem2); (PreloadMediaSource) preloadManager.getMediaSource(mediaItem2);
preloadMediaSource2.prepareSource( preloadMediaSource2.prepareSource(
(source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET); (source, timeline) -> {}, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(2); preloadManager.setCurrentPlayingIndex(2);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1);
wrappedMediaSource1.setAllowPreparation(true); wrappedMediaSource1.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1, 0).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder();
} }
@Test @Test
public void invalidate_sourceHandedOverToPlayerDuringPreloading_continuesPreloadingNextSource() { public void invalidate_sourceHandedOverToPlayerDuringPreloading_continuesPreloadingNextSource() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
@ -284,7 +283,6 @@ public class DefaultPreloadManagerTest {
mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build(); mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build();
MediaItem mediaItem1 = MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build(); mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0); preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false); wrappedMediaSource0.setAllowPreparation(false);
@ -292,7 +290,7 @@ public class DefaultPreloadManagerTest {
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false); wrappedMediaSource1.setAllowPreparation(false);
preloadManager.invalidate(); preloadManager.invalidate();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0); assertThat(targetPreloadStatusControlCallStates).containsExactly(0);
PreloadMediaSource preloadMediaSource0 = PreloadMediaSource preloadMediaSource0 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem0); (PreloadMediaSource) preloadManager.getMediaSource(mediaItem0);
@ -302,15 +300,15 @@ public class DefaultPreloadManagerTest {
// The preload of mediaItem0 should complete and the preload manager continues to preload // The preload of mediaItem0 should complete and the preload manager continues to preload
// mediaItem1, even when the preloadMediaSource0 hasn't finished preparation. // mediaItem1, even when the preloadMediaSource0 hasn't finished preparation.
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
} }
@Test @Test
public void invalidate_beforePreloadCompletedForLastInvalidate_preloadRespectsToLatestOrder() { public void invalidate_beforePreloadCompletedForLastInvalidate_preloadRespectsToLatestOrder() {
ArrayList<Integer> targetPreloadStatusControlCallReference = new ArrayList<>(); ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl = TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> { rankingData -> {
targetPreloadStatusControlCallReference.add(rankingData); targetPreloadStatusControlCallStates.add(rankingData);
return new DefaultPreloadManager.Status( return new DefaultPreloadManager.Status(
DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED); DefaultPreloadManager.Status.STAGE_TIMELINE_REFRESHED);
}; };
@ -340,18 +338,18 @@ public class DefaultPreloadManagerTest {
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource(); FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false); wrappedMediaSource2.setAllowPreparation(false);
MediaSource.MediaSourceCaller externalCaller = (source, timeline) -> {}; MediaSource.MediaSourceCaller externalCaller = (source, timeline) -> {};
PreloadMediaSource preloadMediaSource0 = PreloadMediaSource preloadMediaSource0 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem0); (PreloadMediaSource) preloadManager.getMediaSource(mediaItem0);
preloadMediaSource0.prepareSource( preloadMediaSource0.prepareSource(
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET); externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(0); preloadManager.setCurrentPlayingIndex(0);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(0, 1).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1).inOrder();
targetPreloadStatusControlCallReference.clear();
targetPreloadStatusControlCallStates.clear();
preloadMediaSource0.releaseSource(externalCaller); preloadMediaSource0.releaseSource(externalCaller);
PreloadMediaSource preloadMediaSource2 = PreloadMediaSource preloadMediaSource2 =
(PreloadMediaSource) preloadManager.getMediaSource(mediaItem2); (PreloadMediaSource) preloadManager.getMediaSource(mediaItem2);
@ -359,11 +357,57 @@ public class DefaultPreloadManagerTest {
externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET); externalCaller, bandwidthMeter.getTransferListener(), PlayerId.UNSET);
preloadManager.setCurrentPlayingIndex(2); preloadManager.setCurrentPlayingIndex(2);
preloadManager.invalidate(); preloadManager.invalidate();
// Simulate the delay of the preparation of wrappedMediaSource0, which was triggered at the
// first call of invalidate(). This is expected to result in nothing, as the whole flow of
// preloading should respect the priority order triggered by the latest call of invalidate().
wrappedMediaSource0.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1).inOrder();
wrappedMediaSource1.setAllowPreparation(true); wrappedMediaSource1.setAllowPreparation(true);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallReference).containsExactly(2, 1, 0).inOrder(); assertThat(targetPreloadStatusControlCallStates).containsExactly(2, 1, 0).inOrder();
}
@Test
public void invalidate_provideNullTargetPreloadStatus_sourcesSkippedForPreload() {
ArrayList<Integer> targetPreloadStatusControlCallStates = new ArrayList<>();
TargetPreloadStatusControl<Integer> targetPreloadStatusControl =
rankingData -> {
targetPreloadStatusControlCallStates.add(rankingData);
return null;
};
FakeMediaSourceFactory fakeMediaSourceFactory = new FakeMediaSourceFactory();
DefaultPreloadManager preloadManager =
new DefaultPreloadManager(
targetPreloadStatusControl,
fakeMediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
Util.getCurrentOrMainLooper());
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaItem mediaItem0 =
mediaItemBuilder.setMediaId("mediaId0").setUri("http://exoplayer.dev/video0").build();
MediaItem mediaItem1 =
mediaItemBuilder.setMediaId("mediaId1").setUri("http://exoplayer.dev/video1").build();
MediaItem mediaItem2 =
mediaItemBuilder.setMediaId("mediaId2").setUri("http://exoplayer.dev/video2").build();
preloadManager.add(mediaItem0, /* rankingData= */ 0);
FakeMediaSource wrappedMediaSource0 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource0.setAllowPreparation(false);
preloadManager.add(mediaItem1, /* rankingData= */ 1);
FakeMediaSource wrappedMediaSource1 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource1.setAllowPreparation(false);
preloadManager.add(mediaItem2, /* rankingData= */ 2);
FakeMediaSource wrappedMediaSource2 = fakeMediaSourceFactory.getLastCreatedSource();
wrappedMediaSource2.setAllowPreparation(false);
preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle();
assertThat(targetPreloadStatusControlCallStates).containsExactly(0, 1, 2);
} }
@Test @Test
@ -404,11 +448,11 @@ public class DefaultPreloadManagerTest {
} }
}; };
}); });
preloadManager.add(mediaItem1, /* rankingData= */ 1); preloadManager.add(mediaItem1, /* rankingData= */ 1);
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
preloadManager.remove(mediaItem1); preloadManager.remove(mediaItem1);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@ -477,11 +521,11 @@ public class DefaultPreloadManagerTest {
} }
}; };
}); });
preloadManager.add(mediaItem1, /* rankingData= */ 1); preloadManager.add(mediaItem1, /* rankingData= */ 1);
preloadManager.add(mediaItem2, /* rankingData= */ 2); preloadManager.add(mediaItem2, /* rankingData= */ 2);
preloadManager.invalidate(); preloadManager.invalidate();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
preloadManager.release(); preloadManager.release();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();