views:

152

answers:

2

I implemented the lazy-loading images in my ListView. I use a AsyncTask to download the image from the internet and bind it to the ImageView in the UIThread.

It's working except that when I scroll the ListView vary fast, the downloaded images sometimes are binded into the wrong items in the list.

I guess the problem is from the reuse of convertView in the BaseAdapter. Any ideas to solve it?

Many thanks.

EDIT: I post the answer as following:

public void setBitmap(int position, Bitmap image) {
    View itemView = mListView.getChildAt(position - mListView.getFirstVisiblePosition());
    if (itemView != null) {
        ImageView itemImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
        itemImageView.setImageBitmap(image);
    }
}
A: 

Create a function called void setBitmap(Bitmap bitmap, int position) or similar in your adapter. Let your AsyncTask call this method when a new bitmap is available. This method may then call notifyDataSetChanged() on the UI-Thread itself to ensure the views get refreshed. Holding references to views in an adapter (even by holding them in an AsyncTask) is dangerous!

mreichelt
This is not the problem that shiami has. He is reusing views that have an image set and because the correct image is set in an async task the old image is shown until the new image is loaded etc.
Janusz
Yes, it is. I once worked on a project where exactly this behaviour occurred, and using setTag() or getTag() was just an ugly workaround. The solution was to set the bitmap directly in the getView() method, which itself is triggered by notifyDataSetChanged(). Of course this will lead to a refresh of all views which are visible, which might cause a noticable stutter. To refresh just one view, this solution might be better: http://stackoverflow.com/questions/3724874/android-update-single-item-in-list
mreichelt
Thanks for the tip. But where can I get the correct instance of view to bind my downloaded image according to the given position?
shiami
I tried the solution http://stackoverflow.com/questions/3724874/android-update-single-item-in-list but it causes `NullPointerException` when scrolling vary fast. Seems the getFirstVisiblePosition() is changed and can not get the correct position.
shiami
Of course, the solution on http://stackoverflow.com/questions/3724874/android-update-single-item-in-list might return null if the view is not visible in the moment - so you have to check that. I added a comment about this there already.
mreichelt
Great! Sorry I didn't see your comment. Now It's working smooth now. Many thanks!
shiami
A: 

There are two problems that will arise during lazy loading of images in a ListView.

  1. The old images are still shown until the new ones are loaded. This is easy just set the ImageView to an image is loading view or set it to invisible before starting the image download.
  2. The second problem is harder to solve. Imagine you are scrolling very fast through your list. Now your views may be recycled before the old AsyncTask has finished loading the image. You now have two tasks running that in the onPostExecute method will set an image to the imageview. Now for a short time the wrong image will be shown until the second Task finishes, or even worse for some network related reason they don't finish in the order they started and you have the wrong image overwrite the correct image. To solve this you have to check what image should be displayed after the task finished. In the View class are two methods for things exact like this one:

    setTag and getTag You can bind any object to the imageview that comes into your mind. In most of the cases I use setTag to bind the URL of the image as a String to the imageview before I start a task. Now I can cast getTag to a String after the task finished and compare the URL that should be displayed with the URL that I downloaded and only set the image if necessary.

Janusz
Thanks! The first problem you described seems not a solution to me. Because the `ImageView` will be binded several times when scrolling vary fast. So setting the ImageView a loading image or invisible is not working.
shiami
The getTag method seems won't bind the `ImageView` immediately because the tag is changed after scrolling. It's only updated after slowly scrolling away and back. Is there any step I'm doing incorrectly?
shiami
I don't understand what you mean with the getTag won't bind the ImageView immediately so I can't tell if you are doing something wrong.
Janusz
After fast-scrolling and stop somewhere, the ImageView appears does not being binded. I need to slightly move it out of the screen and back to let it update. After I tried the solution from here http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html I think it is not a good idea to hold the strong reference of the ImageView since it is designed for being released by ListView. Anyway, Thanks for your help.
shiami
With the setTag solution the imageview still can be released. The reference from the background task will cease to exist together with the asyncTask and then the task and the imageview will be released. You are not holding the reference for ever and allow a huge amount of ImageViews to build up and leak memory. It is just a little bit more memory that is used during the run time of the task.
Janusz