7

This is a very common scenario: displaying images in a ListView which have to be downloaded from the internet.

Right now I have a custom subclass of ArrayAdapter which I use for the ListView. In my getView() implementation of the ArrayAdapter, I spawn a separate thread to load an image. After the loading is done, it looks up the appropriate ImageView and sets the image with ImageView.setImageDrawable(). So the solution I used is kind of similar to this one: Lazy load of images in ListView

The problem I'm having is that as soon as I make the call to setImageDrawable() on the ImageView, the ListView somehow refreshes all currently visible rows in the list! This results in kind of an infinite loop:

  1. getView() is called
  2. thread is spawned to load image
  3. image is loaded; setImageDrawable() is called on ImageView
  4. ListView picks it up for some reason and refreshes itself
  5. For the ListView to refresh, getView() is called for each visible row, so we go back to step 1 and the whole thing repeats itself

So as far as I can see, the solution proposed in "Android - How do I do a lazy load of images in ListView" (see link above) simply doesn't work. It might look like it does, but it will run very slow because in the background, it keeps reloading the currently visible rows.

Did anyone encounter this before and/or have a solution for this?

Community
  • 1
  • 1
Tom van Zummeren
  • 9,130
  • 12
  • 52
  • 63

4 Answers4

4

I used the code in following link : another stackoverflow question

i made small changes in order to solve recycling view problem.i set the url of image to Tag of imageview in the adapter. Following code contains my solution that solves recycling problem:

public void fetchDrawableOnThread(final String urlString, final ImageView imageView,Drawable drw) {

    imageView.setImageDrawable(drw);//drw is default image
    if (drawableMap.containsKey(urlString)) {
        if(imageView.getTag().toString().equals(urlString))
        {
            imageView.setImageBitmap(drawableMap.get(urlString));
            imageView.invalidate();
            return;
        }

    }

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            BitmapWrapper wrapper = (BitmapWrapper)message.obj;
            if(wrapper.imageurl.equals(imageView.getTag().toString()))
            {
                imageView.setImageBitmap((Bitmap)wrapper.bitmap);
                imageView.invalidate();
            }

        }
    };

    Thread thread = new Thread() {
        @Override
        public void run() {
            //TODO : set imageView to a "pending" image

            Bitmap drawable = fetchDrawable(urlString);
            BitmapWrapper wrapper = new BitmapWrapper();
            wrapper.bitmap = drawable;
            wrapper.imageurl = urlString;
            Message message = handler.obtainMessage(1, wrapper);
            handler.sendMessage(message);
        }
    };
    thread.start();
}


    public class BitmapWrapper
{
    public Bitmap bitmap;
    public String imageurl;
}
Community
  • 1
  • 1
Murat
  • 3,084
  • 37
  • 55
4

I had the same issue.

After almost 2 days of heavy debugging/optimizing and trying to figure out, why my getView() is called for all views over and over again when using setImageBitmap() in a Row, I came up with a dirty solution:

1) Extend a custom ImageView which you use for all the Images in your List

2) in this ImageView overwrite the method

@Override
public void requestLayout()
{ 
  return; 
}

3) Dirty, but for me it works

4) Profit ;)

saberrider
  • 585
  • 3
  • 16
  • 1
    When Images on list are the same size and your imageview is also fixed size (not wrap content) it can be used. This helped me, thanks :) – Mark Feb 15 '13 at 09:41
3

In the linked solution, fetchDrawableOnThread() should only be called if the view does not already have the correct drawable.

A view does not have a drawable if getDrawable() returns null.

If you are reusing slots, you views you need to go further and manage the state. If your views have a member variable storing the URL, and a boolean to say whether it is loaded, it'd be easy to know whether to call fetchDrawableOnThread() or not, for example.

I'd speculate that the drawable's toString() detailed the path from which the image was loaded. (If it doesn't, you could subclass the drawable returned to make it so). In this case, you could avoid the boolean outlined above and just do a comparison to determine if its the right drawable or whether to fetch a replacement.

Additionally, your getView() on a visible row should ensure that those that no longer visible get unloaded, to prevent memory exhaustion. A finesse would be to move the no longer visible images to soft references (so they are unloaded when memory is needed) as another poster on the original thread noted.

Will
  • 73,905
  • 40
  • 169
  • 246
  • 1
    Yes I am using a map to cache the images. But that does not matter, since I eventually still call setImageDrawable() which again triggers the refresh. If I could somehow disable the refesh it would solve my problem. I don't use SoftReferences yet (but I will), but that is just a memory optimization, this doesn't solve the infinite loop – Tom van Zummeren Sep 11 '09 at 09:00
  • good point I misunderstood what you meant by 'thread is spawned to *load* image'. I'll rewrite my answer – Will Sep 11 '09 at 09:03
  • Thanks for your quick replies :) What you're now describing does solve the problem when you're not reusing the views for displaying rows like I do. With reusing rows I mean using the "convertView" argument that is given to getView(). Do you know a solution for this when reusing row views? (because in that case you DO have to call setImageDrawable() every time) – Tom van Zummeren Sep 11 '09 at 09:25
2

I have a ThumbnailAdapter that wraps up this whole pattern that may help.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491