/* * 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(); } }