Trying to load images from the SD card into my listview within the UI thread (directly in bindView())made scrolling the listview very jerky and slow – it worked but was barely acceptable. My initial attempt at asynchronously loading the images from the SD card ran into a bit of a problem when I discovered that the Views for each row on a list are reused when they go off screen as initialising the Views is a costly process. This meant with the way I’d written my code that quickly scrolling the list would cause the ImageView to flicker between all the other images I’d loaded into it before settling on the final one. This is because all the AsyncTasks that’d been created were yet to finish and calling onPostExecute() binding the view before a new one was created!

So I looked into how to solve this problem. The code/tutorial in the post, Multithreading For Performance on the Android Developer blog, asynchronously downloads/binds an image from the Internet. I’ve adapted this to load the image from the SD card. Here’s my class, SDImageLoader:

package com.samcoles.specimenhunter.ui;

import java.lang.ref.WeakReference;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.widget.ImageView;

public class SDImageLoader {

	public void load(String filePath, ImageView v) {
		if(cancelPotentialSDLoad(filePath, v)) {
			SDLoadImageTask task = new SDLoadImageTask(v);
			SDLoadDrawable sdDrawable = new SDLoadDrawable(task);
			v.setImageDrawable(sdDrawable);
			task.execute(filePath);
		}
	}

	private Bitmap loadImageFromSDCard(String filePath) {

		BitmapFactory.Options bfo = new BitmapFactory.Options();
        bfo.inSampleSize = 4;
        bfo.outWidth = 150;
        bfo.outHeight = 150;
		Bitmap photo = BitmapFactory.decodeFile(filePath, bfo);
		return photo;
	} 

	private static boolean cancelPotentialSDLoad(String filePath, ImageView v) {
		SDLoadImageTask sdLoadTask = getAsyncSDLoadImageTask(v);

		if(sdLoadTask != null) {
			String path = sdLoadTask.getFilePath();
			if((path == null) || (!path.equals(filePath))) {
				sdLoadTask.cancel(true);
			} else {
				return false;
			}
		}
		return true;
	}

	private static SDLoadImageTask getAsyncSDLoadImageTask(ImageView v) {
		if(v != null) {
			Drawable drawable = v.getDrawable();
			if(drawable instanceof SDLoadDrawable) {
				SDLoadDrawable asyncLoadedDrawable = (SDLoadDrawable)drawable;
				return asyncLoadedDrawable.getAsyncSDLoadTask();
			}
		}
		return null;
	}

	private class SDLoadImageTask extends AsyncTask<String, Void, Bitmap> {

		private String mFilePath;
		private final WeakReference<ImageView> mImageViewReference;

		public String getFilePath() {
			return mFilePath;
		}

		public SDLoadImageTask(ImageView v) {
			mImageViewReference = new WeakReference<ImageView>(v);
		}

		@Override
		protected void onPostExecute(Bitmap bmp) {
			if(mImageViewReference != null) {
				ImageView v = mImageViewReference.get();
				SDLoadImageTask sdLoadTask = getAsyncSDLoadImageTask(v);
				// Change bitmap only if this process is still associated with it
				if(this == sdLoadTask) {
					v.setImageBitmap(bmp);
				}
			}
		}

		@Override
		protected Bitmap doInBackground(String... params) {
			mFilePath = params[0];
			return loadImageFromSDCard(mFilePath);
		}
	}

	private class SDLoadDrawable extends ColorDrawable {
		private final WeakReference<SDLoadImageTask> asyncSDLoadTaskReference;

		public SDLoadDrawable(SDLoadImageTask asyncSDLoadTask) {
			super(Color.BLACK);
			asyncSDLoadTaskReference = new WeakReference<SDLoadImageTask>(asyncSDLoadTask);
		}

		public SDLoadImageTask getAsyncSDLoadTask() {
			return asyncSDLoadTaskReference.get();
		}

	}
}

To use this, within your list adapter create an SDImageLoader as a member:

private final SDImageLoader mImageLoader = new SDImageLoader();

and within your overridden bindView() method simply call the method load, passing it a string representing the absolute filepath and the ImageView you want it to be loaded into. e.g.

mImageLoader.load(photoFilePath, photoView);

Here’s that in action:

Asynchronously Loaded Images in ListView

Asynchronously Loaded Images in ListView

A working example project based on this post can be found on github.