/* Copyright (c) 2009 Matthias Käppler
*
* 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.github.droidfu.imageloader;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;
import com.github.droidfu.widgets.WebImageView;
/**
* Realizes an background image loader backed by a two-level FIFO cache. If the
* image to be loaded is present in the cache, it is set immediately on the
* given view. Otherwise, a thread from a thread pool will be used to download
* the image in the background and set the image on the view as soon as it
* completes.
*
* @author Matthias Kaeppler
*/
public class ImageLoader implements Runnable {
private static ThreadPoolExecutor executor;
private static ImageCache imageCache;
private static final int DEFAULT_POOL_SIZE = 2;
public static final int HANDLER_MESSAGE_ID = 0;
public static final String BITMAP_EXTRA = "droidfu:extra_bitmap";
private static int numAttempts = 3;
/**
* @param numThreads
* the maximum number of threads that will be started to download
* images in parallel
*/
public static void setThreadPoolSize(int numThreads) {
executor.setMaximumPoolSize(numThreads);
}
/**
* @param numAttempts
* how often the image loader should retry the image download if
* network connection fails
*/
public static void setMaxDownloadAttempts(int numAttempts) {
ImageLoader.numAttempts = numAttempts;
}
/**
* This method must be called before any other method is invoked on this
* class. Please note that when using ImageLoader as part of
* {@link WebImageView} or {@link WebGalleryAdapter}, then there is no need
* to call this method, since those classes will already do that for you.
* This method is idempotent. You may call it multiple times without any
* side effects.
*
* @param context
* the current context
*/
public static synchronized void initialize(Context context) {
if (executor == null) {
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
}
if (imageCache == null) {
imageCache = new ImageCache(context, 25, 5);
}
}
private String imageUrl;
private Handler handler;
private ImageLoader(String imageUrl, ImageView imageView) {
this.imageUrl = imageUrl;
this.handler = new ImageLoaderHandler(imageView);
}
private ImageLoader(String imageUrl, Handler handler) {
this.imageUrl = imageUrl;
this.handler = handler;
}
/**
* Triggers the image loader for the given image and view. The image loading
* will be performed concurrently to the UI main thread, using a fixed size
* thread pool. The loaded image will be posted back to the given ImageView
* upon completion.
*
* @param imageUrl
* the URL of the image to download
* @param imageView
* the ImageView which should be updated with the new image
*/
public static void start(String imageUrl, ImageView imageView) {
ImageLoader loader = new ImageLoader(imageUrl, imageView);
synchronized (imageCache) {
Bitmap image = imageCache.get(imageUrl);
if (image == null) {
// fetch the image in the background
executor.execute(loader);
} else {
imageView.setImageBitmap(image);
}
}
}
/**
* Triggers the image loader for the given image and handler. The image
* loading will be performed concurrently to the UI main thread, using a
* fixed size thread pool. The loaded image will not be automatically posted
* to an ImageView; instead, you can pass a custom
* {@link ImageLoaderHandler} and handle the loaded image yourself (e.g.
* cache it for later use).
*
* @param imageUrl
* the URL of the image to download
* @param handler
* the handler which is used to handle the downloaded image
*/
public static void start(String imageUrl, ImageLoaderHandlerIF handler) {
ImageLoader loader = new ImageLoader(imageUrl, handler.getHandler());
synchronized (imageCache) {
Bitmap image = imageCache.get(imageUrl);
if (image == null) {
// fetch the image in the background
executor.execute(loader);
} else {
loader.notifyImageLoaded(image);
}
}
}
/**
* Clears the 1st-level cache (in-memory cache). A good candidate for
* calling in {@link android.app.Application#onLowMemory()}.
*/
public static void clearCache() {
synchronized (imageCache) {
imageCache.clear();
}
}
public void run() {
Bitmap bitmap = null;
int timesTried = 1;
while (timesTried <= numAttempts) {
try {
URL url = new URL(imageUrl);
bitmap = BitmapFactory.decodeStream(url.openStream());
synchronized (imageCache) {
imageCache.put(imageUrl, bitmap);
}
break;
} catch (Throwable e) {
Log.w(ImageLoader.class.getSimpleName(), "download for " + imageUrl
+ " failed (attempt " + timesTried + ")");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
}
timesTried++;
}
}
if (bitmap != null) {
notifyImageLoaded(bitmap);
}
}
public void notifyImageLoaded(Bitmap bitmap) {
Message message = new Message();
message.what = HANDLER_MESSAGE_ID;
Bundle data = new Bundle();
data.putParcelable(BITMAP_EXTRA, bitmap);
message.setData(data);
handler.sendMessage(message);
}
}