Lazy Loading Images in a ListView

by Samuh » Tue, 08 Dec 2009 22:34:13 GMT


Sponsored Links
  trying a hand at ListViews and my current experiment is aimed at
displaying some data in a ListView. The data to be displayed in each
row is simple: an image and some text. The images come from a remote
server and the textual data is hardcoded.

I have a class that downloads images using AsyncTask and caches the
list of images fetched as SoftReferences in a LinkedHashMap. I am also
passing a reference of the view to this class, so when the image
download/cache read is complete the class will set appropriate Bitmap
in the view.

Following is my code:

//class BasicAdapter extends ArrayAdapter<RowData>
...
static class ViewHolder{
TextView text;
ImageView icon;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
if(convertView == null){
convertView =
mInflater.inflate(R.layout.listview_row,null);

holder = new ViewHolder();
holder.text = (TextView)
convertView.findViewById(R.id.row_txt);
holder.icon = (ImageView)
convertView.findViewById(R.id.row_img);

convertView.setTag(holder);
}
else{
holder = (ViewHolder) convertView.getTag();
}

holder.text.setText(mDataSet.get(position).getMText());

//set default icon
holder.icon.setImageResource(R.drawable.icon);

// set the actual image from the cache
String imageUrl = mDataSet.get(position).getMUrl();
WriteThroughCache.getImage(holder.icon, imageUrl);

return convertView;

}

// My helper class that manages cache and image Download

public class WriteThroughCache{
static LinkedHashMap<String,SoftReference<Bitmap>> imageCache =
new LinkedHashMap<String,SoftReference<Bitmap>>();

public static void getImage(ImageView icon, String imageUrl){
if(imageCache.containsKey(imageUrl)){
Bitmap image = imageCache.get(imageUrl).get();
if(image != null){
icon.setImageBitmap(image);
return;
}
}

new LoadImageInBackground().execute(new
Object[]{icon,imageUrl});

}

static class LoadImageInBackground extends AsyncTask<Object, Void,
Bitmap>{
ImageView mIcon = null;
String mUrl = null;
@Override
protected Bitmap doInBackground(Object... params) {
mIcon = (ImageView)params[0];
mUrl = (String) params[1];
Bitmap image = BitmapFactory.decodeStream
(GetMethodExecutor.getResponseStream(mUrl));
return image;
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
mIcon.setImageBitmap(result);
imageCache.put(mUrl, new SoftReference<Bitmap>(result));
}


}
}

Problem: The applicatio



Lazy Loading Images in a ListView

by skyhigh » Wed, 09 Dec 2009 15:46:01 GMT


 I haven't tried to do a lazy background image load like you are doing,
but have done some code that uses a holder/wrapper to store the layout
findViewById which is a nice optimization so that it doesn't have to
call inflate and findViewById again when each row is displayed.  My
understanding is that when you do this it will allocate enough views
for each of the rows that are visible on the screen, and then as a row
scrolls off the screen, it will reuse the already initialized view for
a new row that is scrolling onto the screen.

In looking at your code I think you may have a problem because of the
way that the views for each row get reused when they disappear from
sight.  The same view that was used for a row that just disappeared
can be reused for a new row that is appearing as the list scrolls.
Your getView routine is handling this by detecting that the
convertView is non-null when it has already been filled in when this
view was used by a previous row.  However you don't have any mechanism
to detect when a view has been reused and is now displaying a
different row and the URL has changed to a different URL while the
background AsyncTask was retrieving the image.  By the time the
AsyncTask finishes retrieving the image, the view that it saved the
reference to, in order to set the image, could have been reused to
display a different row in the list.  If you can add code to check if
it is still the expected URL prior to setting the image I think it
might solve your problem.  If the URL has changed then skip setting
the lazy image on that view, and just add the image to your cache.

--


Sponsored Links


Lazy Loading Images in a ListView

by Romain Guy » Wed, 09 Dec 2009 16:08:00 GMT


 You can check out the example I wrote at code.google.com/p/shelves. It
does lazy loading of images from the sdcard.



>



Lazy Loading Images in a ListView

by Samuh » Thu, 10 Dec 2009 22:20:01 GMT


 skyhigh: thanks for your reply!


As you can see, I have used a view holder pattern too; as was
explained in the Google I/O conference by Romain.


Thanks for pointing this out; I was able to recreate the situation
that you've described by inserting a delay in the doInBackground()
method of AsyncTask. Result: When I scrolled a few dozen rows and
stopped, the icons in the new Frame displayed the older icons first
and then the list was refreshed.

I could probably work around this by:
1. Tag the view with some unique Id and when setting the image in the
AsyncTask, find the View by Tag, and then proceed with setImage(..).
2. I think, I could optimize the implementation further by not loading
the image when the list is scrolling; similar to what SlowAdapter does.

--



Lazy Loading Images in a ListView

by Samuh » Thu, 10 Dec 2009 22:21:28 GMT


 


Thanks Romain for your time; I will surely check the implementation
out.

Cheers!

--



Lazy Loading Images in a ListView

by Lee » Fri, 11 Dec 2009 02:48:03 GMT


 I have also recently been implementing adapter-based image loading
into a list.

I found that when I used SoftReferences, they started getting cleared
very quickly indeed, after my cache of thumbnails was up to maybe 20
or so.

When I use normal references, the cache grows happily to a couple of
hundred thumbnails without OOMs being thrown.

Being a java newbie, I don't pretend to understand this, but I went
with normal references and careful OOM catching.

BTW, tip maybe, I used onScroll and onScrollStateChanged from the list
to initiate cache fills (rather than filling as a result of calls from
getView()) because otherwise I found scrolling getting sluggish as the
image cache got busy
filling the cache while scrolling was still ongoing.

Lee






--



Lazy Loading Images in a ListView

by Lee » Fri, 11 Dec 2009 02:49:12 GMT


 Oh nevermind, I saw you realised the scroll problem already :-)

Lee

--



Lazy Loading Images in a ListView

by Samuh » Fri, 11 Dec 2009 11:40:34 GMT


 > I found that when I used SoftReferences, they started getting cleared

One of the possible reasons, I can think of, why Romain chose to
persist the fetched images on SDcard(in his Shelves app) is because
when caching is done on heap, the objects keep getting garbage
collected so you have to repeat the time consuming fetches.

--



Lazy Loading Images in a ListView

by Sam Dutton » Fri, 15 Jan 2010 06:23:51 GMT


  haven't read through the whole thread, but FWIW there's a very good
introduction to list view 'lazy loading' techniques in Beginning
Android: http://www.apress.com/book/view/1430224193.

Look in the sample code under fancylists -- well documented in the
book.

Sam Dutton

On Dec 8 2009, 2:34pm, Samuh <samuh.va...@gmail.com> wrote:
> Thanks.



Lazy Loading Images in a ListView

by Mark Murphy » Fri, 15 Jan 2010 06:38:11 GMT


 


Hmmmmm...

I wrote that book, and I'm pretty sure I didn't cover the OP's question
there.

It's related to the Fancy ListViews chapter, but adding asynchronous
thumbnail loading is a whole 'nuther kettle of fish.

Fortunately for the OP, I have already filleted, breaded, and seasoned
that kettle of fish. Here's a reusable component that offers
asynchronous thumbnail downloading and application to a ListView:

 http://github.com/commonsguy/cwac-thumbnail 

-- 
Mark Murphy (a Commons Guy)
 http://commonsware.com  |  http://twitter.com/commonsguy 

_The Busy Coder's Guide to *Advanced* Android Development_
Version 1.3 Available!



Other Threads

1. analog to iOS Core Audio / AudioFileStreamServices?

Hello, all ...

Is there an Android equivalent to the iOS Core Audio / Audio File Stream 
Services? I need to be able to read audio bytes from a network and feed them to 
the audio system under my control, so I can do my own timeouts / reconnects / 
range requests / etc. without interrupting the audio playback (since the system 
audio thread would be playing audio already enqueued).

It seems that MediaPlayer doesn't give me this level of control. Is there a 
lower-level framework that does, either in the SDK or NDK?

Regards,

John

-- 

2. Android Market the Google I/O 2010 features

Anyone know when will the features introduced in Google I/O about
installing apps from the market via the website and etc be released?

-- 

3. eclipse - building workspace lockup

4. How to remove layout element

5. Alsa build problem

6. errors while building opencore outside of android

7. Bluetooth scanning: Issue with HTC Desire -> not scanning