/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.android.volley.toolbox; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Handler; import android.os.Looper; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import androidx.annotation.MainThread; import androidx.annotation.Nullable; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.ResponseDelivery; import com.android.volley.VolleyError; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * Helper that handles loading and caching images from remote URLs. * *
The simple way to use this class is to call {@link ImageLoader#get(String, ImageListener)} and
* to pass in the default image listener provided by {@link ImageLoader#getImageListener(ImageView,
* int, int)}. Note that all function calls to this class must be made from the main thread, and all
* responses will be delivered to the main thread as well. Custom {@link ResponseDelivery}s which
* don't use the main thread are not supported.
*/
public class ImageLoader {
/** RequestQueue for dispatching ImageRequests onto. */
private final RequestQueue mRequestQueue;
/** Amount of time to wait after first response arrives before delivering all responses. */
private int mBatchResponseDelayMs = 100;
/** The cache implementation to be used as an L1 cache before calling into volley. */
private final ImageCache mCache;
/**
* HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so that we can
* coalesce multiple requests to the same URL into a single network request.
*/
private final HashMap The call flow is this: 1. Upon being attached to a request, onResponse(response, true)
* will be invoked to reflect any cached data that was already available. If the data was
* available, response.getBitmap() will be non-null.
*
* 2. After a network response returns, only one of the following cases will happen: -
* onResponse(response, false) will be called if the image was loaded. or - onErrorResponse will
* be called if there was an error loading the image.
*/
public interface ImageListener extends ErrorListener {
/**
* Listens for non-error changes to the loading of the image request.
*
* @param response Holds all information pertaining to the request, as well as the bitmap
* (if it is loaded).
* @param isImmediate True if this was called during ImageLoader.get() variants. This can be
* used to differentiate between a cached image loading and a network image loading in
* order to, for example, run an animation to fade in network loaded images.
*/
void onResponse(ImageContainer response, boolean isImmediate);
}
/**
* Checks if the item is available in the cache.
*
* @param requestUrl The url of the remote image
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @return True if the item exists in cache, false otherwise.
*/
public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
return isCached(requestUrl, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
/**
* Checks if the item is available in the cache.
*
* Must be called from the main thread.
*
* @param requestUrl The url of the remote image
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @param scaleType The scaleType of the imageView.
* @return True if the item exists in cache, false otherwise.
*/
@MainThread
public boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
Threads.throwIfNotOnMainThread();
String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
return mCache.getBitmap(cacheKey) != null;
}
/**
* Returns an ImageContainer for the requested URL.
*
* The ImageContainer will contain either the specified default bitmap or the loaded bitmap.
* If the default was returned, the {@link ImageLoader} will be invoked when the request is
* fulfilled.
*
* @param requestUrl The URL of the image to be loaded.
*/
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, /* maxWidth= */ 0, /* maxHeight= */ 0);
}
/**
* Equivalent to calling {@link #get(String, ImageListener, int, int, ScaleType)} with {@code
* Scaletype == ScaleType.CENTER_INSIDE}.
*/
public ImageContainer get(
String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) {
return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
/**
* Issues a bitmap request with the given URL if that image is not available in the cache, and
* returns a bitmap container that contains all of the data relating to the request (as well as
* the default image if the requested image is not available).
*
* Must be called from the main thread.
*
* @param requestUrl The url of the remote image
* @param imageListener The listener to call when the remote image is loaded
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @param scaleType The ImageViews ScaleType used to calculate the needed image size.
* @return A container object that contains all of the properties of the request, as well as the
* currently available image (default if remote is not loaded).
*/
@MainThread
public ImageContainer get(
String requestUrl,
ImageListener imageListener,
int maxWidth,
int maxHeight,
ScaleType scaleType) {
// only fulfill requests that were initiated from the main thread.
Threads.throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container =
new ImageContainer(
cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight or completed but pending batch delivery.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request == null) {
request = mBatchedResponses.get(cacheKey);
}
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request Must be called from the main thread.
*/
@MainThread
public void cancelRequest() {
Threads.throwIfNotOnMainThread();
if (mListener == null) {
return;
}
BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
// check to see if it is already batched for delivery.
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
/**
* Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
*/
public Bitmap getBitmap() {
return mBitmap;
}
/** Returns the requested URL for this container. */
public String getRequestUrl() {
return mRequestUrl;
}
}
/**
* Wrapper class used to map a Request to the set of active ImageContainer objects that are
* interested in its results.
*/
private static class BatchedImageRequest {
/** The request being tracked */
private final Request> mRequest;
/** The result of the request being tracked by this item */
private Bitmap mResponseBitmap;
/** Error if one occurred for this response */
private VolleyError mError;
/** List of all of the active ImageContainers that are interested in the request */
private final List