cn.edu.szjm.support.images.remote.RemoteImageLoader.java Source code

Java tutorial

Introduction

Here is the source code for cn.edu.szjm.support.images.remote.RemoteImageLoader.java

Source

/* Copyright (c) 2009-2011 Matthias Kaeppler
 *
 * 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 cn.edu.szjm.support.images.remote;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;

import cn.edu.szjm.core.tasks.IgnitedAsyncTask;
import cn.edu.szjm.core.tasks.IgnitedAsyncTask.IgnitedAsyncTaskCallable;
import cn.edu.szjm.support.cache.ImageCache;
import cn.edu.szjm.support.http.IgnitedHttp;
import cn.edu.szjm.support.http.IgnitedHttpRequest;
import cn.edu.szjm.util.SharedImageCache;

/**
 * Realizes a background image loader that downloads an image from a URL, optionally 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 RemoteImageLoader {

    // the default thread pool size
    // expire images after a day
    // TODO: this currently only affects the in-memory cache, so it's quite pointless
    private static final int DEFAULT_NUM_RETRIES = 3;
    private static final int DEFAULT_BUFFER_SIZE = 65536;

    private static final String LOG_TAG = "Ignition/ImageLoader";
    private static final int DEFAULT_RETRY_HANDLER_SLEEP_TIME = 1000;
    private String imageUrl;
    private ImageCache imageCache;
    private RemoteImageLoaderHandler handler;

    //    private ThreadPoolExecutor executor;
    public IgnitedAsyncTask<Context, Void, Void, Bitmap> task;
    private boolean usingCache;
    private int numRetries = DEFAULT_NUM_RETRIES;
    private int defaultBufferSize = DEFAULT_BUFFER_SIZE;
    private boolean cancel = false;

    private Drawable defaultDummyDrawable, errorDrawable;

    public RemoteImageLoader(Context context) {
        this(context, true);
    }

    /**
     * Creates a new ImageLoader that is backed by an {@link ImageCache}. The cache will by default
     * cache to the device's external storage, and expire images after 1 day. You can set useCache
     * to false and then supply your own image cache instance via {@link #setImageCache(ImageCache)}
     * , or fine-tune the default one through {@link #getImageCache()}.
     * 
     * @param context
     *            the current context
     * @param createCache
     *            whether to create a default {@link ImageCache} used for caching
     */
    public RemoteImageLoader(final Context context, boolean usingCache) {
        //        executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
        this.usingCache = usingCache;
        if (usingCache) {
            imageCache = SharedImageCache.getSharedImageCache();
        } else {
            imageCache = null;
        }
        errorDrawable = context.getResources().getDrawable(android.R.drawable.ic_dialog_alert);
        task = new IgnitedAsyncTask<Context, Void, Void, Bitmap>() {
            @Override
            public boolean onTaskCompleted(Bitmap bitmap) {
                if (!cancel) {
                    handler.handleImageLoaded(bitmap, null);
                }
                return true;
            }

            @Override
            public boolean onTaskFailed(Exception error) {
                if (task != null) {
                    Log.d("test", "failed " + task.getStatus() + task);
                }
                return true;
            }

            //            @Override
            //            protected void onCancelled() {
            //               if(task != null) {
            //                  Log.d("test", "cancel "+task.getStatus() + task + isCancelled());
            //               }
            //            }
        };
        IgnitedAsyncTaskCallable<Context, Void, Void, Bitmap> callable = new IgnitedAsyncTaskCallable<Context, Void, Void, Bitmap>() {
            @Override
            public Bitmap call(IgnitedAsyncTask<Context, Void, Void, Bitmap> task) throws Exception {
                // TODO Auto-generated method stub
                if (cancel) {
                    return null;
                }
                Bitmap bitmap = null;
                if (imageCache != null) {
                    // at this point we know the image is not in memory, but it could be cached to SD card
                    bitmap = imageCache.getBitmap(imageUrl);
                }
                if (bitmap == null) {
                    bitmap = downloadImage(context);
                }
                return bitmap;
            }

            protected Bitmap downloadImage(Context context) {
                int timesTried = 1;
                while (timesTried <= numRetries && !cancel) {
                    try {
                        if (cancel) {
                            return null;
                        }
                        byte[] imageData = retrieveImageData(context);
                        if (imageData == null) {
                            break;
                        }
                        if (imageCache != null) {
                            imageCache.put(imageUrl, imageData);
                        }
                        return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
                    } catch (Throwable e) {
                        Log.w(LOG_TAG, "download for " + imageUrl + " failed (attempt " + timesTried + ")");
                        e.printStackTrace();
                        SystemClock.sleep(DEFAULT_RETRY_HANDLER_SLEEP_TIME);
                        timesTried++;
                    }
                }
                return null;
            }

            protected byte[] retrieveImageData(Context context) throws IOException {
                IgnitedHttp http = new IgnitedHttp(context);
                http.disableResponseCache(true);
                IgnitedHttpRequest request = http.get(imageUrl, true).retries(3).expecting(200);
                request.setWrapResponse(false);
                if (cancel) {
                    return null;
                }
                HttpResponse response = (HttpResponse) request.send();
                HttpEntity entity = response.getEntity();
                //                URL url = new URL(imageUrl);
                //                HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                // determine the image size and allocate a buffer
                int fileSize = (int) entity.getContentLength();
                Log.d(LOG_TAG, "fetching image " + imageUrl + " ("
                        + (fileSize <= 0 ? "size unknown" : Integer.toString(fileSize)) + ")");
                BufferedInputStream istream = new BufferedInputStream(entity.getContent());
                try {
                    if (fileSize <= 0) {
                        Log.w(LOG_TAG, "Server did not set a Content-Length header, will default to buffer size of "
                                + defaultBufferSize + " bytes");
                        ByteArrayOutputStream buf = new ByteArrayOutputStream(defaultBufferSize);
                        byte[] buffer = new byte[defaultBufferSize];
                        int bytesRead = 0;
                        while (bytesRead != -1) {
                            if (cancel) {
                                return null;
                            }
                            bytesRead = istream.read(buffer, 0, defaultBufferSize);
                            if (bytesRead > 0)
                                buf.write(buffer, 0, bytesRead);
                        }
                        return buf.toByteArray();
                    } else {
                        byte[] imageData = new byte[fileSize];

                        int bytesRead = 0;
                        int offset = 0;
                        while (bytesRead != -1 && offset < fileSize) {
                            if (cancel) {
                                return null;
                            }
                            bytesRead = istream.read(imageData, offset, fileSize - offset);
                            offset += bytesRead;
                        }
                        return imageData;
                    }
                } finally {
                    // clean up
                    try {
                        istream.close();
                        //connection.disconnect();
                    } catch (Exception ignore) {
                    }
                }
            }
        };
        task.setCallable(callable);
    }

    public void cancel() {
        cancel = true;
        task.cancel(true);
        task = null;
    }

    //    /**
    //     * @param numThreads
    //     *            the maximum number of threads that will be started to download images in parallel
    //     */
    //    public void setThreadPoolSize(int numThreads) {
    //        executor.setMaximumPoolSize(numThreads);
    //    }

    /**
     * @param numAttempts
     *            how often the image loader should retry the image download if network connection
     *            fails
     */
    public void setMaxDownloadAttempts(int numAttempts) {
        numRetries = numAttempts;
    }

    /**
     * If the server you're loading images from does not report file sizes via the Content-Length
     * header, then you can use this method to tell the downloader how much space it should allocate
     * by default when downloading an image into memory.
     * 
     * @param defaultBufferSize
     *            how big the buffer should be into which the image file is read. This should be big
     *            enough to hold the largest image you expect to download
     */
    public void setDefaultBufferSize(int defaultBufferSize) {
        this.defaultBufferSize = defaultBufferSize;
    }

    public void setDownloadInProgressDrawable(Drawable drawable) {
        this.defaultDummyDrawable = drawable;
    }

    public void setDownloadFailedDrawable(Drawable drawable) {
        this.errorDrawable = drawable;
    }

    /**
     * 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. This method will the default
     * {@link RemoteImageLoaderHandler} to process the bitmap after downloading it.
     * 
     * @param imageUrl
     *            the URL of the image to download
     * @param imageView
     *            the ImageView which should be updated with the new image
     */
    public void loadImage(String imageUrl, ImageView imageView) {
        loadImage(imageUrl, imageView, defaultDummyDrawable,
                new RemoteImageLoaderHandler(imageView, imageUrl, errorDrawable));
    }

    /**
     * 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. This method will the default
     * {@link RemoteImageLoaderHandler} to process the bitmap after downloading it.
     *
     * @param imageUrl
     *            the URL of the image to download
     * @param imageView
     *            the ImageView which should be updated with the new image
     * @param dummyDrawable
     *            the Drawable to be shown while the image is being downloaded.
     */
    public void loadImage(String imageUrl, ImageView imageView, Drawable dummyDrawable) {
        loadImage(imageUrl, imageView, dummyDrawable,
                new RemoteImageLoaderHandler(imageView, imageUrl, errorDrawable));
    }

    /**
     * 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
     * @param handler
     *            the handler that will process the bitmap after completion
     */
    public void loadImage(String imageUrl, ImageView imageView, RemoteImageLoaderHandler handler) {
        loadImage(imageUrl, imageView, defaultDummyDrawable, 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. While waiting, the dummyDrawable is
     * shown.
     * 
     * @param imageUrl
     *            the URL of the image to download
     * @param imageView
     *            the ImageView which should be updated with the new image
     * @param dummyDrawable
     *            the Drawable to be shown while the image is being downloaded.
     * @param handler
     *            the handler that will process the bitmap after completion
     */
    public void loadImage(String imageUrl, ImageView imageView, Drawable dummyDrawable,
            RemoteImageLoaderHandler handler) {
        this.imageUrl = imageUrl;
        this.handler = handler;
        if (imageView != null) {
            if (imageUrl == null) {
                // In a ListView views are reused, so we must be sure to remove the tag that could
                // have been set to the ImageView to prevent that the wrong image is set.
                imageView.setTag(null);
                if (dummyDrawable != null) {
                    imageView.setImageDrawable(dummyDrawable);
                }
                return;
            }
            String oldImageUrl = (String) imageView.getTag();
            if (imageUrl.equals(oldImageUrl)) {
                // nothing to do
                return;
            } else {
                if (dummyDrawable != null) {
                    // Set the dummy image while waiting for the actual image to be downloaded.
                    imageView.setImageDrawable(dummyDrawable);
                }
                imageView.setTag(imageUrl);
            }
        }

        ImageCache cache = SharedImageCache.getSharedImageCache();
        if (usingCache) {
            if (cache != null && cache.containsKeyInMemory(imageUrl)) {
                // do not go through message passing, handle directly instead
                handler.handleImageLoaded(cache.getBitmap(imageUrl), null);
                return;
            }
        }
        task.execute();
    }

}