blob: 7472a02d05e51b041fe04a9ea94bd49a19fe7528 [file] [log] [blame]
package com.bumptech.glide.request;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
import com.bumptech.glide.provider.LoadProvider;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.animation.GlideAnimationFactory;
import com.bumptech.glide.request.target.SizeReadyCallback;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.util.LogTime;
import com.bumptech.glide.util.Util;
import java.util.Queue;
/**
* A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given {@link Target}.
*
* @param <A> The type of the model that the resource will be loaded from.
* @param <T> The type of the data that the resource will be loaded from.
* @param <Z> The type of the resource that will be loaded.
* @param <R> The type of the resource that will be transcoded from the loaded resource.
*/
public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
ResourceCallback {
private static final String TAG = "GenericRequest";
private static final Queue<GenericRequest<?, ?, ?, ?>> REQUEST_POOL = Util.createQueue(0);
private static final double TO_MEGABYTE = 1d / (1024d * 1024d);
private enum Status {
/** Created but not yet running. */
PENDING,
/** In the process of fetching media. */
RUNNING,
/** Waiting for a callback given to the Target to be called to determine target dimensions. */
WAITING_FOR_SIZE,
/** Finished loading media successfully. */
COMPLETE,
/** Failed to load media. */
FAILED,
/** Cancelled by the user, may not be restarted. */
CANCELLED,
/** Temporarily paused by the system, may be restarted. */
PAUSED,
}
private final String tag = String.valueOf(hashCode());
private Key signature;
private int placeholderResourceId;
private int errorResourceId;
private Context context;
private Transformation<Z> transformation;
private LoadProvider<A, T, Z, R> loadProvider;
private RequestCoordinator requestCoordinator;
private A model;
private Class<R> transcodeClass;
private boolean isMemoryCacheable;
private Priority priority;
private Target<R> target;
private RequestListener<? super A, R> requestListener;
private float sizeMultiplier;
private Engine engine;
private GlideAnimationFactory<R> animationFactory;
private int overrideWidth;
private int overrideHeight;
private DiskCacheStrategy diskCacheStrategy;
private Drawable placeholderDrawable;
private Drawable errorDrawable;
private boolean loadedFromMemoryCache;
// doing our own type check
private Resource<?> resource;
private Engine.LoadStatus loadStatus;
private long startTime;
private Status status;
public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
LoadProvider<A, T, Z, R> loadProvider,
A model,
Key signature,
Context context,
Priority priority,
Target<R> target,
float sizeMultiplier,
Drawable placeholderDrawable,
int placeholderResourceId,
Drawable errorDrawable,
int errorResourceId,
RequestListener<? super A, R> requestListener,
RequestCoordinator requestCoordinator,
Engine engine,
Transformation<Z> transformation,
Class<R> transcodeClass,
boolean isMemoryCacheable,
GlideAnimationFactory<R> animationFactory,
int overrideWidth,
int overrideHeight,
DiskCacheStrategy diskCacheStrategy) {
@SuppressWarnings("unchecked")
GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
if (request == null) {
request = new GenericRequest<A, T, Z, R>();
}
request.init(loadProvider,
model,
signature,
context,
priority,
target,
sizeMultiplier,
placeholderDrawable,
placeholderResourceId,
errorDrawable,
errorResourceId,
requestListener,
requestCoordinator,
engine,
transformation,
transcodeClass,
isMemoryCacheable,
animationFactory,
overrideWidth,
overrideHeight,
diskCacheStrategy);
return request;
}
private GenericRequest() {
// just create, instances are reused with recycle/init
}
@Override
public void recycle() {
loadProvider = null;
model = null;
context = null;
target = null;
placeholderDrawable = null;
errorDrawable = null;
requestListener = null;
requestCoordinator = null;
transformation = null;
animationFactory = null;
loadedFromMemoryCache = false;
loadStatus = null;
REQUEST_POOL.offer(this);
}
private void init(
LoadProvider<A, T, Z, R> loadProvider,
A model,
Key signature,
Context context,
Priority priority,
Target<R> target,
float sizeMultiplier,
Drawable placeholderDrawable,
int placeholderResourceId,
Drawable errorDrawable,
int errorResourceId,
RequestListener<? super A, R> requestListener,
RequestCoordinator requestCoordinator,
Engine engine,
Transformation<Z> transformation,
Class<R> transcodeClass,
boolean isMemoryCacheable,
GlideAnimationFactory<R> animationFactory,
int overrideWidth,
int overrideHeight,
DiskCacheStrategy diskCacheStrategy) {
this.loadProvider = loadProvider;
this.model = model;
this.signature = signature;
this.context = context.getApplicationContext();
this.priority = priority;
this.target = target;
this.sizeMultiplier = sizeMultiplier;
this.placeholderDrawable = placeholderDrawable;
this.placeholderResourceId = placeholderResourceId;
this.errorDrawable = errorDrawable;
this.errorResourceId = errorResourceId;
this.requestListener = requestListener;
this.requestCoordinator = requestCoordinator;
this.engine = engine;
this.transformation = transformation;
this.transcodeClass = transcodeClass;
this.isMemoryCacheable = isMemoryCacheable;
this.animationFactory = animationFactory;
this.overrideWidth = overrideWidth;
this.overrideHeight = overrideHeight;
this.diskCacheStrategy = diskCacheStrategy;
status = Status.PENDING;
// We allow null models by just setting an error drawable. Null models will always have empty providers, we
// simply skip our sanity checks in that unusual case.
if (model != null) {
check("ModelLoader", loadProvider.getModelLoader(), "try .using(ModelLoader)");
check("Transcoder", loadProvider.getTranscoder(), "try .as*(Class).transcode(ResourceTranscoder)");
check("Transformation", transformation, "try .transform(UnitTransformation.get())");
if (diskCacheStrategy.cacheSource()) {
check("SourceEncoder", loadProvider.getSourceEncoder(),
"try .sourceEncoder(Encoder) or .diskCacheStrategy(NONE/RESULT)");
} else {
check("SourceDecoder", loadProvider.getSourceDecoder(),
"try .decoder/.imageDecoder/.videoDecoder(ResourceDecoder) or .diskCacheStrategy(ALL/SOURCE)");
}
if (diskCacheStrategy.cacheSource() || diskCacheStrategy.cacheResult()) {
// TODO if(resourceClass.isAssignableFrom(InputStream.class) it is possible to wrap sourceDecoder
// and use it instead of cacheDecoder: new FileToStreamDecoder<Z>(sourceDecoder)
// in that case this shouldn't throw
check("CacheDecoder", loadProvider.getCacheDecoder(),
"try .cacheDecoder(ResouceDecoder) or .diskCacheStrategy(NONE)");
}
if (diskCacheStrategy.cacheResult()) {
check("Encoder", loadProvider.getEncoder(),
"try .encode(ResourceEncoder) or .diskCacheStrategy(NONE/SOURCE)");
}
}
}
private static void check(String name, Object object, String suggestion) {
if (object == null) {
StringBuilder message = new StringBuilder(name);
message.append(" must not be null");
if (suggestion != null) {
message.append(", ");
message.append(suggestion);
}
throw new NullPointerException(message.toString());
}
}
/**
* {@inheritDoc}
*/
@Override
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE;
if (overrideWidth > 0 && overrideHeight > 0) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
/**
* Cancels the current load but does not release any resources held by the request and continues to display
* the loaded resource if the load completed before the call to cancel.
*
* <p>
* Cancelled requests can be restarted with a subsequent call to {@link #begin()}.
* </p>
*
* @see #clear()
*/
void cancel() {
status = Status.CANCELLED;
if (loadStatus != null) {
loadStatus.cancel();
loadStatus = null;
}
}
/**
* Cancels the current load if it is in progress, clears any resources held onto by the request and replaces
* the loaded resource if the load completed with the placeholder.
*
* <p>
* Cleared requests can be restarted with a subsequent call to {@link #begin()}
* </p>
*
* @see #cancel()
*/
@Override
public void clear() {
Util.assertMainThread();
cancel();
// Resource must be released before canNotifyStatusChanged is called.
if (resource != null) {
releaseResource(resource);
}
if (canNotifyStatusChanged()) {
target.onLoadCleared(getPlaceholderDrawable());
}
}
@Override
public boolean isPaused() {
return status == Status.PAUSED;
}
@Override
public void pause() {
clear();
status = Status.PAUSED;
}
private void releaseResource(Resource resource) {
engine.release(resource);
this.resource = null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isRunning() {
return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isComplete() {
return status == Status.COMPLETE;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isResourceSet() {
return isComplete();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCancelled() {
return status == Status.CANCELLED;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isFailed() {
return status == Status.FAILED;
}
private void setErrorPlaceholder(Exception e) {
if (!canNotifyStatusChanged()) {
return;
}
Drawable error = getErrorDrawable();
if (error == null) {
error = getPlaceholderDrawable();
}
target.onLoadFailed(e, error);
}
private Drawable getErrorDrawable() {
if (errorDrawable == null && errorResourceId > 0) {
errorDrawable = context.getResources().getDrawable(errorResourceId);
}
return errorDrawable;
}
private Drawable getPlaceholderDrawable() {
if (placeholderDrawable == null && placeholderResourceId > 0) {
placeholderDrawable = context.getResources().getDrawable(placeholderResourceId);
}
return placeholderDrawable;
}
/**
* A callback method that should never be invoked directly.
*/
@Override
public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
if (dataFetcher == null) {
onException(new Exception("Got null fetcher from model loader"));
return;
}
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadedFromMemoryCache = true;
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
loadedFromMemoryCache = resource != null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
private boolean canSetResource() {
return requestCoordinator == null || requestCoordinator.canSetImage(this);
}
private boolean canNotifyStatusChanged() {
return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this);
}
private boolean isFirstReadyResource() {
return requestCoordinator == null || !requestCoordinator.isAnyResourceSet();
}
/**
* A callback method that should never be invoked directly.
*/
@SuppressWarnings("unchecked")
@Override
public void onResourceReady(Resource<?> resource) {
if (resource == null) {
onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
+ " inside, but instead got null."));
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
releaseResource(resource);
onException(new Exception("Expected to receive an object of " + transcodeClass
+ " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
+ " inside Resource{" + resource + "}."
+ (received != null ? "" : " "
+ "To indicate failure return a null Resource object, "
+ "rather than a Resource object containing null data.")
));
return;
}
if (!canSetResource()) {
releaseResource(resource);
// We can't set the status to complete before asking canSetResource().
status = Status.COMPLETE;
return;
}
onResourceReady(resource, (R) received);
}
/**
* Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
*
* @param resource original {@link Resource}, never <code>null</code>
* @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
*/
private void onResourceReady(Resource<?> resource, R result) {
if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
isFirstReadyResource())) {
GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstReadyResource());
target.onResourceReady(result, animation);
}
status = Status.COMPLETE;
this.resource = resource;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
+ (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
}
}
/**
* A callback method that should never be invoked directly.
*/
@Override
public void onException(Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "load failed", e);
}
status = Status.FAILED;
//TODO: what if this is a thumbnail request?
if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
setErrorPlaceholder(e);
}
}
private void logV(String message) {
Log.v(TAG, message + " this: " + tag);
}
}