All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.solovyev.android.http.CachingImageLoader Maven / Gradle / Ivy

/*
 * Copyright 2013 serso aka se.solovyev
 *
 * 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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Contact details
 *
 * Email: [email protected]
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.http;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.ImageView;
import org.solovyev.android.FileCache;
import org.solovyev.android.Threads;
import org.solovyev.common.Objects;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachingImageLoader implements ImageLoader {

    /*
	**********************************************************************
    *
    *                           CONSTANTS
    *
    **********************************************************************
    */

	private static final String TAG = CachingImageLoader.class.getSimpleName();

	private static final int REQUIRED_SIZE = 70;

    /*
    **********************************************************************
    *
    *                           FIELDS
    *
    **********************************************************************
    */

	@Nonnull
	private final MemoryCache memoryCache = new MemoryCache();

	@Nonnull
	private final FileCache fileCache;

	@Nonnull
	private final Map imageViews = Collections.synchronizedMap(new WeakHashMap());

	@Nonnull
	private final ExecutorService executorService;

	public CachingImageLoader(@Nonnull Context context, @Nonnull String cacheFileName) {
		fileCache = new FileCache(context, cacheFileName);
		executorService = Executors.newFixedThreadPool(5);
	}

	@Override
	public void loadImage(@Nonnull String url, @Nonnull ImageView imageView, @Nullable Integer defaultImageId) {
		loadImage(url, new ImageViewImageLoadedListener(imageView, defaultImageId));
	}

	@Override
	public void loadImage(@Nonnull String url, @Nonnull OnImageLoadedListener imageLoadedListener) {
		imageViews.put(imageLoadedListener, url);

		final Bitmap bitmapFromMemory = memoryCache.get(url);
		if (bitmapFromMemory != null) {
			// bitmap found in memory => set
			imageLoadedListener.onImageLoaded(bitmapFromMemory);
		} else {
			imageLoadedListener.setDefaultImage();

			// add to loading queue
			queuePhoto(url, imageLoadedListener);
		}
	}

	@Override
	public void loadImage(@Nonnull String url) {
		final Bitmap bitmapFromMemory = memoryCache.get(url);
		if (bitmapFromMemory != null) {
			// bitmap found in memory
		} else {
			// add to loading queue
			queuePhoto(url, EmptyImageLoadedListener.getInstance());
		}
	}

	private void queuePhoto(@Nonnull String url, @Nonnull OnImageLoadedListener imageLoadedListener) {
		executorService.submit(new PhotosLoader(new PhotoToLoad(url, imageLoadedListener)));
	}

	@Nullable
	private Bitmap getBitmap(@Nonnull String url) {
		final File cachedBitmapFile = fileCache.getFile(createFilename(url));

		// from SD cache
		Bitmap result = decodeFile(cachedBitmapFile);
		if (result == null) {
			//from web
			try {
				final URL imageUrl = new URL(url);

				final HttpURLConnection connection = (HttpURLConnection) imageUrl.openConnection();
				connection.setConnectTimeout(30000);
				connection.setReadTimeout(30000);
				connection.setInstanceFollowRedirects(true);


				final InputStream is = connection.getInputStream();
				OutputStream os = null;
				try {
					os = new FileOutputStream(cachedBitmapFile);

					Utils.copyStream(is, os);

				} finally {
					if (os != null) {
						os.close();
					}
				}

				result = decodeFile(cachedBitmapFile);
			} catch (MalformedURLException e) {
				Log.e(TAG, e.getMessage(), e);
			} catch (FileNotFoundException e) {
				Log.e(TAG, e.getMessage(), e);
			} catch (IOException e) {
				Log.e(TAG, e.getMessage(), e);
			}
		}

		return result;
	}

	@Nonnull
	private String createFilename(String url) {
		try {
			return URLEncoder.encode(url, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new AssertionError(e);
		}
	}

	// decodes image and scales it to reduce memory consumption
	@Nullable
	private static Bitmap decodeFile(@Nonnull File file) {
		try {
			//decode image size
			final BitmapFactory.Options o = new BitmapFactory.Options();

			// causes bitmap to be null for VK images
			//o.inJustDecodeBounds = true;

			final Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, o);

			//Find the correct scale value. It should be the power of 2.

			int tmpWidth = o.outWidth;
			int tmpHeight = o.outHeight;

			int scale = 1;
			while (true) {
				if (tmpWidth < REQUIRED_SIZE || tmpHeight < REQUIRED_SIZE) {
					break;
				}
				tmpWidth /= 2;
				tmpHeight /= 2;
				scale *= 2;
			}

			if (scale == 1) {
				return bitmap;
			} else {
				//decode with inSampleSize
				final BitmapFactory.Options o2 = new BitmapFactory.Options();
				o2.inSampleSize = scale;
				return BitmapFactory.decodeStream(new FileInputStream(file), null, o2);
			}
		} catch (FileNotFoundException e) {
			return null;
		}
	}

	//Task for the queue
	private static class PhotoToLoad {

		@Nonnull
		public final String url;

		@Nonnull
		public final OnImageLoadedListener imageLoadedListener;

		public PhotoToLoad(@Nonnull String url, @Nonnull OnImageLoadedListener imageLoadedListener) {
			this.url = url;
			this.imageLoadedListener = imageLoadedListener;
		}
	}

	private class PhotosLoader implements Runnable {

		@Nonnull
		private final PhotoToLoad photoToLoad;

		private PhotosLoader(@Nonnull PhotoToLoad photoToLoad) {
			this.photoToLoad = photoToLoad;
		}

		@Override
		public void run() {
			if (!isNeedToLoad(photoToLoad)) {

				final Bitmap bmp = getBitmap(photoToLoad.url);
				if (bmp != null) {
					memoryCache.put(photoToLoad.url, bmp);
				}

				if (!isNeedToLoad(photoToLoad)) {
					photoToLoad.imageLoadedListener.onImageLoaded(bmp);
				}
			}
		}
	}

	private boolean isNeedToLoad(@Nonnull PhotoToLoad photoToLoad) {
		final String url = imageViews.get(photoToLoad.imageLoadedListener);
		if (url == null || !url.equals(photoToLoad.url)) {
			return true;
		} else {
			return false;
		}
	}

	public void clearCache() {
		memoryCache.clear();
		fileCache.clear();
	}

    /*
    **********************************************************************
    *
    *                           STATIC CLASSES
    *
    **********************************************************************
    */


	private static final class ImageViewImageLoadedListener implements OnImageLoadedListener {

		@Nonnull
		private final WeakReference imageViewRef;

		@Nullable
		private final Integer defaultImageId;

		private ImageViewImageLoadedListener(@Nonnull ImageView imageView, @Nullable Integer defaultImageId) {
			this.imageViewRef = new WeakReference(imageView);
			this.defaultImageId = defaultImageId;
		}

		@Override
		public void onImageLoaded(@Nullable final Bitmap image) {
			final ImageView imageView = imageViewRef.get();
			if (imageView != null) {
				final Activity activity = (Activity) imageView.getContext();
				Threads.tryRunOnUiThread(activity, new Runnable() {
					@Override
					public void run() {
						if (image != null) {
							imageView.setImageBitmap(image);
						} else {
							if (defaultImageId != null) {
								imageView.setImageResource(defaultImageId);
							}
						}
					}
				});
			}
		}

		@Override
		public void setDefaultImage() {
			final ImageView imageView = imageViewRef.get();
			if (imageView != null) {
				final Activity activity = (Activity) imageView.getContext();
				Threads.tryRunOnUiThread(activity, new Runnable() {
					@Override
					public void run() {
						if (defaultImageId != null) {
							imageView.setImageResource(defaultImageId);
						}
					}
				});
			}
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (!(o instanceof ImageViewImageLoadedListener)) return false;

			ImageViewImageLoadedListener that = (ImageViewImageLoadedListener) o;

			final ImageView thisImageView = this.imageViewRef.get();
			final ImageView thatImageView = that.imageViewRef.get();
			if (!Objects.areEqual(thisImageView, thatImageView)) return false;

			return true;
		}

		@Override
		public int hashCode() {
			final ImageView imageView = imageViewRef.get();
			return imageView == null ? 0 : imageView.hashCode();
		}
	}


	private static final class EmptyImageLoadedListener implements OnImageLoadedListener {

		@Nonnull
		private static final OnImageLoadedListener instance = new EmptyImageLoadedListener();

		private EmptyImageLoadedListener() {
		}

		@Nonnull
		public static OnImageLoadedListener getInstance() {
			return instance;
		}

		@Override
		public void onImageLoaded(@Nullable Bitmap image) {
		}

		@Override
		public void setDefaultImage() {
		}
	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy