Preventing application from hanging while image is downloading?

I am using a ListView to display some images and captions associated with those images. I am getting the images from the Internet. Is there a way to lazy load the images so while the text displays, the UI is not locked up and images are displayed as they are downloaded. The number of images is not fixed.

Update:

(Using below code, I have tried to download images and displaying in ListView)

Bitmap bmImg; Bitmap downloadFile(String fileUrl) 
{ 
 URL myFileUrl = null; 
  try { 
      myFileUrl = new URL(fileUrl); 
   } 
   catch (MalformedURLException e) 
   { 
      e.printStackTrace(); 
   } 

   try { 
     HttpURLConnection conn = (HttpURLConnection) myFileUrl .openConnection(); 
    conn.setDoInput(true); 
    conn.connect(); 
    InputStream is = conn.getInputStream(); 
    bmImg = BitmapFactory.decodeStream(is); 
   } catch (IOException e) 
    { 
        e.printStackTrace(); 
   } 
    return bmImg; 
  } 

import java.io.IOException;

public class DrawableManager {
    private final Map<String, Drawable> drawableMap;

    public DrawableManager() {
        drawableMap = new HashMap<String, Drawable>();
    }

    public Drawable fetchDrawable(String urlString) {
        if (drawableMap.containsKey(urlString)) {
            return drawableMap.get(urlString);
        }

        Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
        try {
            InputStream is = fetch(urlString);
            Drawable drawable = Drawable.createFromStream(is, "src");


            if (drawable != null) {
                drawableMap.put(urlString, drawable);
                Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                        + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                        + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
            } else {
              Log.w(this.getClass().getSimpleName(), "could not get thumbnail");
            }

            return drawable;
        } catch (MalformedURLException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        } catch (IOException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        }
    }

    public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
        if (drawableMap.containsKey(urlString)) {
            imageView.setImageDrawable(drawableMap.get(urlString));
        }

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        Thread thread = new Thread() {
            @Override
            public void run() {
                //TODO : set imageView to a "pending" image
                Drawable drawable = fetchDrawable(urlString);
                Message message = handler.obtainMessage(1, drawable);
                handler.sendMessage(message);
            }
        };
        thread.start();
    }

    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpGet request = new HttpGet(urlString);
        HttpResponse response = httpClient.execute(request);
        return response.getEntity().getContent();
    }

}

I read your question here is code which try this...

public class ImageDownloader { private static final String LOG_TAG = "ImageDownloader";

public enum Mode {
    NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT
}

private Mode mode = Mode.CORRECT;

/**
 * Download the specified image from the Internet and binds it to the
 * provided ImageView. The binding is immediate if the image is found in the
 * cache and will be done asynchronously otherwise. A null bitmap will be
 * associated to the ImageView if an error occurs.
 * 
 * @param url
 *            The URL of the image to download.
 * @param imageView
 *            The ImageView to bind the downloaded image to.
 */
public void download(String url, ImageView imageView) {

    if (url != null && !url.isEmpty()) {
        url = url.replaceAll(" ", "%20");
        resetPurgeTimer();
        Bitmap bitmap = getBitmapFromCache(url);

        if (bitmap == null) {
            forceDownload(url, imageView);
        } else {
            cancelPotentialDownload(url, imageView);
            imageView.setImageBitmap(bitmap);
        }
    }
}

/*
 * Same as download but the image is always downloaded and the cache is not
 * used. Kept private at the moment as its interest is not clear. private
 * void forceDownload(String url, ImageView view) { forceDownload(url, view,
 * null); }
 */

/**
 * Same as download but the image is always downloaded and the cache is not
 * used. Kept private at the moment as its interest is not clear.
 */
private void forceDownload(String url, ImageView imageView) {
    // State sanity: url is guaranteed to never be null in
    // DownloadedDrawable and cache keys.
    if (url == null) {
        imageView.setImageDrawable(null);

        return;
    }

    if (cancelPotentialDownload(url, imageView)) {
        switch (mode) {
        case NO_ASYNC_TASK:
            Bitmap bitmap = downloadBitmap(url);
            addBitmapToCache(url, bitmap);
            imageView.setImageBitmap(bitmap);
            break;

        case NO_DOWNLOADED_DRAWABLE:
            imageView.setMinimumHeight(156);
            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
            task.execute(url);
            break;

        case CORRECT:
            task = new BitmapDownloaderTask(imageView);
            DownloadedDrawable downloadedDrawable = new DownloadedDrawable(
                    task);
            imageView.setImageDrawable(downloadedDrawable);
            imageView.setMinimumHeight(156);
            task.execute(url);
            break;
        }
    }
}

/**
 * Returns true if the current download has been canceled or if there was no
 * download in progress on this image view. Returns false if the download in
 * progress deals with the same url. The download is not stopped in that
 * case.
 */
private static boolean cancelPotentialDownload(String url,
        ImageView imageView) {
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if (bitmapDownloaderTask != null) {
        String bitmapUrl = bitmapDownloaderTask.url;

        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.cancel(true);
        } else {
            // The same URL is already being downloaded.
            return false;
        }
    }
    return true;
}

/**
 * @param imageView
 *            Any imageView
 * @return Retrieve the currently active download task (if any) associated
 *         with this imageView. null if there is no such task.
 */
private static BitmapDownloaderTask getBitmapDownloaderTask(
        ImageView imageView) {
    if (imageView != null) {
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof DownloadedDrawable) {
            DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;

            return downloadedDrawable.getBitmapDownloaderTask();
        }
    }

    return null;
}

Bitmap downloadBitmap(String url) {
    final int IO_BUFFER_SIZE = 4 * 1024;

    // AndroidHttpClient is not allowed to be used from the main thread
    final HttpClient client = (mode == Mode.NO_ASYNC_TASK) ? new DefaultHttpClient()
            : AndroidHttpClient.newInstance("Android");
    final HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode != HttpStatus.SC_OK) {
            Log.w("ImageDownloader", "Error " + statusCode
                    + " while retrieving bitmap from " + url);

            return null;
        }

        final HttpEntity entity = response.getEntity();

        if (entity != null) {
            InputStream inputStream = null;

            try {
                inputStream = entity.getContent();
                // return BitmapFactory.decodeStream(inputStream);
                // Bug on slow connections, fixed in future release.
                return BitmapFactory.decodeStream(new FlushedInputStream(
                        inputStream));
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                entity.consumeContent();
            }
        }
    } catch (IOException e) {
        getRequest.abort();
        Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
    } catch (IllegalStateException e) {
        getRequest.abort();
        Log.w(LOG_TAG, "Incorrect URL: " + url);
    } catch (Exception e) {
        getRequest.abort();
        Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
    } finally {
        if ((client instanceof AndroidHttpClient)) {
            ((AndroidHttpClient) client).close();
        }
    }

    return null;
}

/*
 * An InputStream that skips the exact number of bytes provided, unless it
 * reaches EOF.
 */
static class FlushedInputStream extends FilterInputStream {
    public FlushedInputStream(InputStream inputStream) {
        super(inputStream);
    }

    @Override
    public long skip(long n) throws IOException {
        long totalBytesSkipped = 0L;

        while (totalBytesSkipped < n) {
            long bytesSkipped = in.skip(n - totalBytesSkipped);

            if (bytesSkipped == 0L) {
                int b = read();

                if (b < 0) {
                    break; // we reached EOF
                } else {
                    bytesSkipped = 1; // we read one byte
                }
            }

            totalBytesSkipped += bytesSkipped;
        }

        return totalBytesSkipped;
    }
}

/**
 * The actual AsyncTask that will asynchronously download the image.
 */
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
    private String url;
    private final WeakReference<ImageView> imageViewReference;

    public BitmapDownloaderTask(ImageView imageView) {
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    /**
     * Actual download method.
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        url = params[0];

        return downloadBitmap(url);
    }

    /**
     * Once the image is downloaded, associates it to the imageView
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        addBitmapToCache(url, bitmap);

        if (imageViewReference != null) {
            ImageView imageView = imageViewReference.get();
            BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
            // Change bitmap only if this process is still associated with
            // it
            // Or if we don't use any bitmap to task association
            // (NO_DOWNLOADED_DRAWABLE mode)
            if ((this == bitmapDownloaderTask) || (mode != Mode.CORRECT)) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

/**
 * A fake Drawable that will be attached to the imageView while the download
 * is in progress.
 * 
 * <p>
 * Contains a reference to the actual download task, so that a download task
 * can be stopped if a new binding is required, and makes sure that only the
 * last started download process can bind its result, independently of the
 * download finish order.
 * </p>
 */
static class DownloadedDrawable extends ColorDrawable {
    private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

    public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
        super(Color.WHITE);
        bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(
                bitmapDownloaderTask);
    }

    public BitmapDownloaderTask getBitmapDownloaderTask() {
        return bitmapDownloaderTaskReference.get();
    }
}

public void setMode(Mode mode) {
    this.mode = mode;
    clearCache();
}

/*
 * Cache-related fields and methods.
 * 
 * We use a hard and a soft cache. A soft reference cache is too
 * aggressively cleared by the Garbage Collector.
 */

private static final int HARD_CACHE_CAPACITY = 1000;
private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds

// Hard cache, with a fixed maximum capacity and a life duration
private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(
        HARD_CACHE_CAPACITY / 2, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(
            LinkedHashMap.Entry<String, Bitmap> eldest) {
        if (size() > HARD_CACHE_CAPACITY) {
            // Entries push-out of hard reference cache are transferred to
            // soft reference cache
            sSoftBitmapCache.put(eldest.getKey(),
                    new SoftReference<Bitmap>(eldest.getValue()));

            return true;
        } else
            return false;
    }
};

// Soft cache for bitmaps kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
        HARD_CACHE_CAPACITY / 2);

private final Handler purgeHandler = new Handler();

private final Runnable purger = new Runnable() {
    public void run() {
        clearCache();
        System.gc();
    }
};

/**
 * Adds this bitmap to the cache.
 * 
 * @param bitmap
 *            The newly downloaded bitmap.
 */
private void addBitmapToCache(String url, Bitmap bitmap) {
    if (bitmap != null) {
        synchronized (sHardBitmapCache) {
            sHardBitmapCache.put(url, bitmap);
        }
    }
}

/**
 * @param url
 *            The URL of the image that will be retrieved from the cache.
 * @return The cached bitmap or null if it was not found.
 */
private Bitmap getBitmapFromCache(String url) {
    // First try the hard reference cache
    synchronized (sHardBitmapCache) {
        final Bitmap bitmap = sHardBitmapCache.get(url);

        if (bitmap != null) {
            // Bitmap found in hard cache
            // Move element to first position, so that it is removed last
            sHardBitmapCache.remove(url);
            sHardBitmapCache.put(url, bitmap);

            return bitmap;
        }
    }

    // Then try the soft reference cache
    SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);

    if (bitmapReference != null) {
        final Bitmap bitmap = bitmapReference.get();

        if (bitmap != null) {
            // Bitmap found in soft cache
            return bitmap;

        } else {
            // Soft reference has been Garbage Collected
            sSoftBitmapCache.remove(url);
        }
    }

    return null;
}

/**
 * Clears the image cache used internally to improve performance. Note that
 * for memory efficiency reasons, the cache will automatically be cleared
 * after a certain inactivity delay.
 */
public void clearCache() {
    sHardBitmapCache.clear();
    sSoftBitmapCache.clear();
}

/**
 * Allow a new delay before the automatic cache clear is done.
 */
private void resetPurgeTimer() {
    purgeHandler.removeCallbacks(purger);
    // purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
}

}


While searching on net i found some interesting links for your question

  • https://github.com/thest1/LazyList
  • http://codehenge.net/blog/2011/06/android-development-tutorial-asynchronous-lazy-loading-and-caching-of-listview-images/
  • Hope these will be helpful to you

    链接地址: http://www.djcxy.com/p/93124.html

    上一篇: 从图像Android缓存和延迟加载

    下一篇: 防止图像下载时挂起应用程序?