/*
* Copyright (C) 2016 Amit Shekhar
* Copyright (C) 2011 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.androidnetworking.internal;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import com.androidnetworking.AndroidNetworking;
import com.androidnetworking.cache.LruBitmapCache;
import com.androidnetworking.common.ANRequest;
import com.androidnetworking.error.ANError;
import com.androidnetworking.interfaces.BitmapRequestListener;
import java.util.HashMap;
import java.util.LinkedList;
/**
* Created by amitshekhar on 23/03/16.
*/
public class ANImageLoader {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
private static final int cacheSize = maxMemory / 8;
private int mBatchResponseDelayMs = 100;
private final ImageCache mCache;
private final HashMap<String, BatchedImageRequest> mInFlightRequests =
new HashMap<String, BatchedImageRequest>();
private final HashMap<String, BatchedImageRequest> mBatchedResponses =
new HashMap<String, BatchedImageRequest>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private Runnable mRunnable;
private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static ANImageLoader sInstance;
public static void initialize() {
getInstance();
}
public static ANImageLoader getInstance() {
if (sInstance == null) {
synchronized (ANImageLoader.class) {
if (sInstance == null) {
sInstance = new ANImageLoader(new LruBitmapCache(cacheSize));
}
}
}
return sInstance;
}
public interface ImageCache {
Bitmap getBitmap(String key);
void putBitmap(String key, Bitmap bitmap);
void evictBitmap(String key);
void evictAllBitmap();
}
public ANImageLoader(ImageCache imageCache) {
mCache = imageCache;
}
public ImageCache getImageCache() {
return mCache;
}
public static ImageListener getImageListener(final ImageView view,
final int defaultImageResId,
final int errorImageResId) {
return new ImageListener() {
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
@Override
public void onError(ANError anError) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
};
}
public interface ImageListener {
void onResponse(ImageContainer response, boolean isImmediate);
void onError(ANError anError);
}
public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
return isCached(requestUrl, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE);
}
public boolean isCached(String requestUrl, int maxWidth, int maxHeight,
ImageView.ScaleType scaleType) {
throwIfNotOnMainThread();
String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
return mCache.getBitmap(cacheKey) != null;
}
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, 0, 0);
}
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
return get(requestUrl, imageListener, maxWidth, maxHeight,
ImageView.ScaleType.CENTER_INSIDE);
}
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ImageView.ScaleType scaleType) {
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
imageListener.onResponse(imageContainer, true);
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
request.addContainer(imageContainer);
return imageContainer;
}
ANRequest newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
protected ANRequest makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
ImageView.ScaleType scaleType, final String cacheKey) {
ANRequest ANRequest = AndroidNetworking.get(requestUrl)
.setTag("ImageRequestTag")
.setBitmapMaxHeight(maxHeight)
.setBitmapMaxWidth(maxWidth)
.setImageScaleType(scaleType)
.setBitmapConfig(Bitmap.Config.RGB_565)
.setBitmapOptions(mBitmapOptions)
.build();
ANRequest.getAsBitmap(new BitmapRequestListener() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
@Override
public void onError(ANError anError) {
onGetImageError(cacheKey, anError);
}
});
return ANRequest;
}
public void setBitmapDecodeOptions(BitmapFactory.Options bitmapOptions) {
mBitmapOptions = bitmapOptions;
}
public void setBatchedResponseDelay(int newBatchedResponseDelayMs) {
mBatchResponseDelayMs = newBatchedResponseDelayMs;
}
protected void onGetImageSuccess(String cacheKey, Bitmap response) {
mCache.putBitmap(cacheKey, response);
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
request.mResponseBitmap = response;
batchResponse(cacheKey, request);
}
}
protected void onGetImageError(String cacheKey, ANError anError) {
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
request.setError(anError);
batchResponse(cacheKey, request);
}
}
public class ImageContainer {
private Bitmap mBitmap;
private final ImageListener mListener;
private final String mCacheKey;
private final String mRequestUrl;
public ImageContainer(Bitmap bitmap, String requestUrl,
String cacheKey, ImageListener listener) {
mBitmap = bitmap;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
public void cancelRequest() {
if (mListener == null) {
return;
}
BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
public Bitmap getBitmap() {
return mBitmap;
}
public String getRequestUrl() {
return mRequestUrl;
}
}
private class BatchedImageRequest {
private final ANRequest mRequest;
private Bitmap mResponseBitmap;
private ANError mANError;
private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
public BatchedImageRequest(ANRequest request, ImageContainer container) {
mRequest = request;
mContainers.add(container);
}
public void setError(ANError anError) {
mANError = anError;
}
public ANError getError() {
return mANError;
}
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
mContainers.remove(container);
if (mContainers.size() == 0) {
mRequest.cancel(true);
if (mRequest.isCanceled()) {
mRequest.destroy();
ANRequestQueue.getInstance().finish(mRequest);
}
return true;
}
return false;
}
}
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onError(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
private void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
}
}
private static String getCacheKey(String url, int maxWidth, int maxHeight,
ImageView.ScaleType scaleType) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
.toString();
}
}