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.Encoder; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.ResourceEncoder; import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.data.DataFetcher; 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.target.Target; import com.bumptech.glide.util.LogTime; import java.io.InputStream; import java.util.ArrayDeque; import java.util.Queue; /** * A {@link Request} that loads a {@link Resource} into a given {@link Target}. * * @param The type of the model that the resource will be loaded from. * @param The type of the data that the resource will be loaded from. * @param The type of the resource that will be loaded. */ public class GenericRequest implements Request, Target.SizeReadyCallback, ResourceCallback { private static final String TAG = "GenericRequest"; private int placeholderResourceId; private int errorResourceId; private Context context; private Transformation transformation; private LoadProvider loadProvider; private RequestCoordinator requestCoordinator; private A model; private Class transcodeClass; private boolean isMemoryCacheable; private Priority priority; private Target target; private RequestListener requestListener; private float sizeMultiplier; private Engine engine; private GlideAnimationFactory animationFactory; private int overrideWidth; private int overrideHeight; private String tag = String.valueOf(hashCode()); private boolean cacheSource; private Drawable placeholderDrawable; private Drawable errorDrawable; private boolean isCancelled; private boolean isError; private boolean loadedFromMemoryCache; private Resource resource; private Engine.LoadStatus loadStatus; private boolean isRunning; private long startTime; private static final Queue queue = new ArrayDeque(); @SuppressWarnings("unchecked") public static GenericRequest obtain( LoadProvider loadProvider, A model, Context context, Priority priority, Target target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, RequestListener requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation transformation, Class transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory animationFactory, int overrideWidth, int overrideHeight, boolean cacheSource) { GenericRequest request = queue.poll(); if (request == null) { request = new GenericRequest(); } request.init(loadProvider, model, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderResourceId, errorDrawable, errorResourceId, requestListener, requestCoordinator, engine, transformation, transcodeClass, isMemoryCacheable, animationFactory, overrideWidth, overrideHeight, cacheSource); return request; } private GenericRequest() { } @Override public void recycle() { loadProvider = null; model = null; context = null; target = null; placeholderDrawable = null; errorDrawable = null; requestListener = null; requestCoordinator = null; engine = null; transformation = null; animationFactory = null; isCancelled = false; isError = false; loadedFromMemoryCache = false; loadStatus = null; isRunning = false; cacheSource = false; queue.offer(this); } private void init( LoadProvider loadProvider, A model, Context context, Priority priority, Target target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, RequestListener requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation transformation, Class transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory animationFactory, int overrideWidth, int overrideHeight, boolean cacheSource) { this.loadProvider = loadProvider; this.model = model; this.context = context; 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.cacheSource = cacheSource; // 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) { if (loadProvider.getCacheDecoder() == null) { throw new NullPointerException("CacheDecoder must not be null, try .cacheDecoder(ResouceDecoder)"); } if (loadProvider.getSourceDecoder() == null) { throw new NullPointerException("SourceDecoder must not be null, try .imageDecoder(ResourceDecoder) " + "and/or .videoDecoder()"); } if (loadProvider.getEncoder() == null) { throw new NullPointerException("Encoder must not be null, try .encode(ResourceEncoder)"); } if (loadProvider.getTranscoder() == null) { throw new NullPointerException("Transcoder must not be null, try .as(Class, ResourceTranscoder)"); } if (loadProvider.getModelLoader() == null) { throw new NullPointerException("ModelLoader must not be null, try .using(ModelLoader)"); } if (loadProvider.getSourceEncoder() == null) { throw new NullPointerException("SourceEncoder must not be null, try .sourceEncoder(Encoder)"); } } } @Override public void run() { startTime = LogTime.getLogTime(); if (model == null) { onException(null); return; } if (overrideWidth > 0 && overrideHeight > 0) { onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); } if (!isComplete() && !isFailed()) { setPlaceHolder(); isRunning = true; } if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); } } public void cancel() { isRunning = false; isCancelled = true; if (loadStatus != null) { loadStatus.cancel(); loadStatus = null; } } @Override public void clear() { cancel(); setPlaceHolder(); if (resource != null) { resource.release(); resource = null; } } @Override public boolean isRunning() { return isRunning; } @Override public boolean isComplete() { return resource != null; } @Override public boolean isFailed() { return isError; } private void setPlaceHolder() { if (!canSetPlaceholder()) return; target.setPlaceholder(getPlaceholderDrawable()); } private void setErrorPlaceholder() { if (!canSetPlaceholder()) return; Drawable error = getErrorDrawable(); if (error != null) { target.setPlaceholder(error); } else { setPlaceHolder(); } } 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; } @Override public void onSizeReady(int width, int height) { if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime)); } if (isCancelled) { return; } width = Math.round(sizeMultiplier * width); height = Math.round(sizeMultiplier * height); ResourceDecoder cacheDecoder = loadProvider.getCacheDecoder(); Encoder sourceEncoder = loadProvider.getSourceEncoder(); ResourceDecoder decoder = loadProvider.getSourceDecoder(); ResourceEncoder encoder = loadProvider.getEncoder(); ResourceTranscoder transcoder = loadProvider.getTranscoder(); ModelLoader modelLoader = loadProvider.getModelLoader(); final DataFetcher dataFetcher = modelLoader.getResourceFetcher(model, width, height); if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime)); } loadedFromMemoryCache = true; loadStatus = engine.load(width, height, cacheDecoder, dataFetcher, cacheSource, sourceEncoder, decoder, transformation, encoder, transcoder, priority, isMemoryCacheable, this); loadedFromMemoryCache = resource != null; if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); } } private boolean canSetImage() { return requestCoordinator == null || requestCoordinator.canSetImage(this); } private boolean canSetPlaceholder() { return requestCoordinator == null || requestCoordinator.canSetPlaceholder(this); } private boolean isFirstImage() { return requestCoordinator == null || !requestCoordinator.isAnyRequestComplete(); } @SuppressWarnings("unchecked") @Override public void onResourceReady(Resource resource) { isRunning = false; if (!canSetImage()) { resource.release(); return; } if (resource == null || !transcodeClass.isAssignableFrom(resource.get().getClass())) { if (resource != null) { resource.release(); } onException(new Exception("Expected to receive an object of " + transcodeClass + " but instead got " + (resource != null ? resource.get() : null))); return; } R result = (R) resource.get(); if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache, isFirstImage())) { GlideAnimation animation = animationFactory.build(loadedFromMemoryCache, isFirstImage()); target.onResourceReady(result, animation); } this.resource = resource; if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: " + (resource.getSize() / (1024d * 1024d)) + " fromCache: " + loadedFromMemoryCache); } } @Override public void onException(Exception e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "load failed", e); } isRunning = false; isError = true; //TODO: what if this is a thumbnail request? if (requestListener == null || !requestListener.onException(e, model, target, isFirstImage())) { setErrorPlaceholder(); } } private void logV(String message) { Log.v(TAG, message + " this: " + tag); } }