Skip to content

Lazy Loading Images: From URLs to ListViews

June 13, 2012

Hey everyone!

Recently while working on a project I was asked to lazy load a list of images (bitmaps) into a ListView. What made the task even trickier was the fact that each image had to be streamed from a given URL. I thought about downloading all of the images at the application’s startup Activity, and then dumping them into some kind of external SD card as a cache, but I was told that the images were subject to frequent change and that true caching would be difficult. The result is how I chose to implement the feature, and I thought I’d share it with everyone.

Now there’s no guarantee that the way I’m doing this is the “correct” or optimal way – in fact if you DO know the best way to lazy load images from URLs then please share – but the solution below works and uses a handful of Android classes and concepts, all of which I’ll note as I walk you through the solution.

Before I move onto the code, let’s conceptually think about how we’re going to make this feature work. In this example, let’s say we’re making an HTTP request to some external server that returns a list of Students. Each Student has a name and an image URL which is to be streamed, converted into a Bitmap, and then displayed in a ListView. Getting the list of Student objects is the easy part – but what’s the next step? One option is to loop through each Student, grab each Student’s image URL, convert the URL into a Bitmap, and then allow each Student to hold a reference to their Bitmap; at which point we can load the list like we normally would.

A very feasible solution – but depending on the size of your Student list. Let’s assume that each image takes between 0.5 to 1 seconds to load. Now, if your database only has 5-10 Students in it, then maybe this iterative solution may work. However, consider a database with 100s of Students – clearly this iterative solution won’t hold up in that case. What’s the better solution? Wouldn’t it be nice if we could parallelize the loading process?

That’s step one – thinking of a way to parallelize the process. Step two is to do all this loading and processing on separate background threads. This way the user can interact with the application, even while the images are loading. The last step, step three, is to make sure that each background thread can somehow communicate with the original ListAdapter and make sure that the list gets updated each time an image is successfully streamed.

With that, here’s the code.

First, let’s start with a basic Student object. Each Student has a picture that needs to be loaded, and each student holds a reference to both their image’s URL as well as their Bitmap (to be downloaded):


public class Student {

	private String name;

	private String imgUrl;

	private Bitmap image;

	private StudentAdapter sta;

	public Student(String name, String imgUrl) {
                this.name = name;
                this.imgUrl = imgUrl;

                // TO BE LOADED LATER - OR CAN SET TO A DEFAULT IMAGE
                this.image = null;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getImgUrl() {
		return imgUrl;
	}

	public void setImgUrl(String imgUrl) {
		this.imgUrl = imgUrl;
	}

        public Bitmap getImage() {
		return image;
	}

	public StudentAdapter getAdapter() {
		return sta;
	}

	public void setAdapter(StudentAdapter sta) {
		this.sta = sta;
	}

	public void loadImage(StudentAdapter sta) {
		// HOLD A REFERENCE TO THE ADAPTER
		this.sta = sta;
		if (imgUrl != null && !imgUrl.equals("")) {
			new ImageLoadTask().execute(imgUrl);
		}
	}

	// ASYNC TASK TO AVOID CHOKING UP UI THREAD
	private class ImageLoadTask extends AsyncTask<String, String, Bitmap> {

		@Override
		protected void onPreExecute() {
			Log.i("ImageLoadTask", "Loading image...");
		}

		// param[0] is img url
		protected Bitmap doInBackground(String... param) {
			Log.i("ImageLoadTask", "Attempting to load image URL: " + param[0]);
			try {
				Bitmap b = ImageService.getBitmapFromURLWithScale(param[0]);
				return b;
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		protected void onProgressUpdate(String... progress) {
			// NO OP
		}

		protected void onPostExecute(Bitmap ret) {
			if (ret != null) {
				Log.i("ImageLoadTask", "Successfully loaded " + name + " image");
				image = ret;
				if (sta != null) {
					// WHEN IMAGE IS LOADED NOTIFY THE ADAPTER
					sta.notifyDataSetChanged();
				}
			} else {
				Log.e("ImageLoadTask", "Failed to load " + name + " image");
			}
		}
	}

}

Alright so what’s going on here? Again, the Student object we have here is very simple – it has only a name, image URL, and Bitmap image associated with it. For now, the image will be null (or a default image can be used) but we choose to hold a reference to it so that once it is loaded, we can “cache” it and only have to load it once.

Then, let’s note two things. First, we see that each Student has a loadImage() method which initiates an AsyncTask that we created called ImageLoadTask. By containing the loading process within the Student object, we’ve found a natural way to address step one and parallelize the process. Now, each Student can independently kick off their image loading processes and independently handle the resulting Bitmap. Why do we use an AsyncTask? Well that’s to address step two above and make sure that each process is loaded in a background thread. By doing this, we guarantee that each Student manages their own image Bitmap, and without disturbing the main UI thread!

Furthermore, we see that each Student object holds a reference to a StudentAdapter, whose code will be shown next. This is our solution to step three from above. By holding a reference to the Adapter, we can make sure that once the Student’s image is loaded (through the ImageLoadTask), the Student can then tell the adapter to update itself using the Adapter’s notifyDataSetChanged() method.

Let’s take a quick look at how the StudentAdapter is defined:


public class StudentAdapter extends BaseAdapter {

	private LayoutInflater mInflater;

	private List items = new ArrayList();

	public StudentAdapter(Context context, List items) {
		mInflater = LayoutInflater.from(context);
		this.items = items;
	}

	public int getCount() {
		return items.size();
	}

	public Student getItem(int position) {
		return items.get(position);
	}

	public long getItemId(int position) {
		return position;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		Student s = items.get(position);
		if (convertView == null) {
			convertView = mInflater.inflate(R.layout.row_layout, null);
			holder = new ViewHolder();
			holder.name = (TextView) convertView.findViewById(R.id.name);
			holder.image = (ImageView) convertView.findViewById(R.id.image);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		holder.name.setText(s.getName());
		if (s.getImage() != null) {
			holder.pic.setImageBitmap(s.getImage());
		} else {
                // MY DEFAULT IMAGE
			holder.pic.setImageResource(R.drawable.generic_profile_man);
		}
		return convertView;
	}

	static class ViewHolder {
		TextView name;

		ImageView pic;
	}

}

Pretty simple really and nothing much to say. Really you just have to note that in the getView() method, if the Student’s image is null (implying that the Student’s image hasn’t been loaded yet) then I set the image to be a default generic profile picture. And so let’s bring this all together by looking at how we’d set this up in an Activity class:


public class StudentListActivity extends ListActivity {

        private List students;

        private StudentAdapter sta;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.list);

                // GET YOUR STUDENTS
                students = //...

                // CREATE BASE ADAPTER
                sta = new StudentAdapter(StudentListActivity.this, students);

                // SET AS CURRENT LIST
                setListAdapter(sta);

                for (Student s : students) {
                        // START LOADING IMAGES FOR EACH STUDENT
                        s.loadImage(sta);
                }
        }

}

And voila! That’s it! We look through our list of Students, and for each Student we call their loadImage() method, making sure we pass in the instantiated ListAdapter. Then, each loadImage() method will kick off an AsyncTask that will stream the image, cache it as a Bitmap, and then notify the ListAdapter to update itself.

How this is going to look at the end will be a list that the user can scroll through, where each row is loading itself behind the scenes, and displaying its image as soon as the loading is done. Hopefully this all makes sense – and again happy to hear other solutions as well! Best of luck and as usual, happy coding.

- jwei

About these ads
33 Comments leave one →
  1. June 13, 2012 9:06 pm

    Hi,

    Really a good one. Thanks.

  2. Bijesh permalink
    June 13, 2012 9:22 pm

    jwei… it was really helpful to me as i was struggling with ListView displaying the instant chat message..

  3. pathfinder permalink
    October 8, 2012 9:38 am

    Hi,

    i’m an absolute beginner in android and java. I’m struggling to understand what “dja” is referring to in this example. can you please brief me what dja is?

    Thank you in advance.

    • October 8, 2012 10:25 am

      Hi Pathfinder,

      Sorry that was actually a typo on my part – I’ve corrected it now. It should have been variable “sta” i.e. StudentAdapter.

      – jwei

      • pathfinder permalink
        October 8, 2012 10:48 am

        Hi,
        thank you clarifying it. I think the same mistake has been repeated in the “onPostExecute” method. Just wanted to draw your attention. This is a very nice tutorial.

        Thank you.

      • October 8, 2012 1:41 pm

        Hey Pathfinder,

        Thanks for the catches – they’ve been fixed now. =)

        – jwei

  4. John permalink
    October 20, 2012 3:20 pm

    Hello, I am trying to use bits of your code in something I am working on, however one bit is confusing me…

    // GET YOUR STUDENTS
    students = //…

    What type of data are you returning to students, I have tried List, List ArrayList and a few other combos…nothing i try seems to work…

    Otherwise I am very, VERY close…

    • October 22, 2012 5:18 am

      Hey John,

      So I believe I define what the actual Student object looks like. If your question is what data structure the Students are stored in, then yes a simple List structure is all that is needed.

      This List is then passed into the constructor of the BaseAdapter which is then set as the Activity’s ListAdapter.

      – jwei

      • John permalink
        October 22, 2012 11:03 am

        Thank you for your reply, everything on this post has been so helpful to getting a prototype up and running! Great work!

  5. November 15, 2012 7:07 pm

    Hello,

    How can you prove that the downloads are in parallel?

    for (Student s : students) {
    // START LOADING IMAGES FOR EACH STUDENT
    s.loadImage(sta);
    }

    In the code above, aren’t you still iteratively calling the Students’ loadImage function to download the pictures?

  6. Arjun permalink
    November 27, 2012 3:52 am

    Good One :)

  7. abilashn permalink
    November 30, 2012 9:58 pm

    Thanks for the tutorial. It works very well.

    I have a small problem. I am trying to load upto 20 thumbnails. about 3-5 thumbnails randomly doest seem to load or show up in my grid view.
    How can I ensure all the thumbnails are loaded?

    Thanks,
    Abilash

    • December 1, 2012 3:02 pm

      Hi Abilashn,

      Why exactly do the 3-5 thumbnails don’t show up? Are they the same 3-5 thumbnails? Have you checked any logs?

      – jwei

  8. AmateurAndyDeveloper permalink
    December 25, 2012 1:01 pm

    Hello Jwei,
    thanks for your work.
    Can you please explain what is the “list” in R.layout.list in StudentListActivity?
    I tried setting it as a xml with only ListView, it throws RuntimeException. I put that ListView into a LinearLayout, still the same exception.

    • AmateurAndyDeveloper permalink
      December 25, 2012 3:17 pm

      Hello again. I solved the exception by instantiating the Student List and removing the setContentView(). Now I am wondering why the code you have suggested works for many but not me…
      Thank anyways.

  9. January 28, 2013 12:10 am

    Your way of implementing is good. But you are not considering memory management stratgies. Keeping all image, may cause your application to be crash. So we should use the concept of LRU cache or softerefernce. And Saving all image to secondry storage is recommend way. It avoid the work of downloading image again and again. I have post Image Downloader article with combination of memory management. I respect your way of sharing knowldege. And invitation to better solution is appreciable

    • February 1, 2013 1:52 pm

      Hey Sameer,

      Thanks for sharing this – I agree there are a lot of improvements to be made on my method. It was sort of a hack but it got the job done for me at the time!

      – jwei

  10. February 3, 2013 10:44 pm

    I believe the same mistake has been repeated in the on PostExecute method. Just desired to draw your interest. This is a very nice tutorial.

  11. Lynn Gobin permalink
    February 4, 2013 8:56 am

    Thanks!

  12. February 21, 2013 3:43 pm

    How i can cancel all Task ?

  13. andrewmolo permalink
    February 27, 2013 2:01 am

    Can you click an item on the listview and it brings a short description of the item?

    • March 10, 2013 8:26 pm

      Hi Andrewmolo,

      The short answer to your question is yes. The longer answer depends on how you want the short description to appear. If you’d like it to appear as a separate Activity/Fragment then you can launch an Intent for a new Activity upon clicking. Subsequently you can set any values into the Intent that you’d like to pass into the new Activity. You could also choose to have a static TextView as a footer or something and use the setText() method to change the description text upon clicking on a new item. Lastly you could use a Toast that fades in and out to show the description.

      Hope this helps.

      – jwei

      • andrewmolo permalink
        April 2, 2013 4:41 am

        Thanks

Trackbacks

  1. lazy loading – Android: “Cannot convert from object to Album” (Album is a class..) « Android ASK
  2. how to implement lazyloading in the adapter : Android Community - For Application Development
  3. Refresh gridview after download image : Android Community - For Application Development
  4. Async load Image in contact list : Android Community - For Application Development
  5. Downloading Images from Server and Displaying them on imageview of Listview [closed] : Android Community - For Application Development
  6. Progress dialog on only that particular view : Android Community - For Application Development
  7. (beginner) How do I load images off of a site via main url address with lazy list? : Android Community - For Application Development

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 881 other followers

%d bloggers like this: