Android ListView updating of Image Thumbnails using AsyncTask Causes View recycling
Asked Answered
T

3

10

I have been trying to get thumbnails to work with an AsyncTask for image files in a ListView. I have been having the common problem of Row Recycling, and thus on scrolling thumbnails are being assigned to wrong rows. I have tried adding tags to the ImageView and then confirming the tags in the onPostExecute() in the AsyncTask, but however have been unsuccessful in my attempts. Someone please help!

The custom adapter is as follows :

public class MySimpleAdapter extends SimpleAdapter {

        public MySimpleAdapter(Context context,
                List<? extends Map<String, ?>> data, int resource,
                String[] from, int[] to) {
            super(context, data, resource, from, to);
            // TODO Auto-generated constructor stub
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            final View v = super.getView(position, convertView, parent);
            thumbnail = (ImageView) v.findViewById(R.id.thumbnail);
            checker = (CheckBox) v.findViewById(R.id.checkBox);
            filenametext = (TextView) v.findViewById(R.id.text1);
            String pathtoimage = startLocation.concat("/"
                    + filenametext.getText().toString());
            desctext = (TextView) v.findViewById(R.id.text2);
            String temp = desctext.getText().toString();
            if (temp.equals("Directory") == true) {
                checker.setEnabled(false);
                switch (theme) {
                case 0:
                    thumbnail.setImageResource(R.drawable.folder_light);
                    break;
                case 1:
                    thumbnail.setImageResource(R.drawable.folder_dark);
                    break;
                }

            } else {
                checker.setEnabled(true);
                if (filenametext.getText().toString().endsWith(".jpg")
                        || filenametext.getText().toString().endsWith(".png")
                        || filenametext.getText().toString().endsWith(".bmp")) {
                    Boolean hashmapfound = false;
                    for (HashMap.Entry<String, Bitmap> entry : thumbnaillist
                            .entrySet()) {
                        String key = entry.getKey();
                        if (key.equals(filenametext.getText().toString())) {
                            Log.d(TAG, "HashMapKey Found!");
                            thumbnail.setImageBitmap(entry.getValue());
                            hashmapfound = true;
                        }
                    }
                    if (!hashmapfound) {
                        Log.d(TAG, "NO HashMapKey Found! Adding to HashMap!");
                        switch (theme) {
                        case 0:
                            thumbnail
                                    .setImageResource(R.drawable.unknown_image_light);
                            break;
                        case 1:
                            thumbnail
                                    .setImageResource(R.drawable.unknown_image_dark);
                            break;
                        }
                        thumbnail.setTag((filenametext.getText().toString()));
                        new GetBitMaps(thumbnail).execute(pathtoimage,
                                filenametext.getText().toString());
                    }

                } else {
                    switch (theme) {
                    case 0:
                        thumbnail.setImageResource(R.drawable.file_light);
                        break;
                    case 1:
                        thumbnail.setImageResource(R.drawable.file_dark);
                        break;
                    }
                }
            }
            return v;
        }

    }

The AsyncTask is as follows :

class GetBitMaps extends AsyncTask<String, Void, Bitmap> {

    private ImageView thumbnail;
    private String imageName;

    public GetBitMaps(ImageView thumb) {
        // TODO Auto-generated constructor stub
        thumbnail = thumb;
    }

    @Override
    protected Bitmap doInBackground(String... pathtoimage) {
        Bitmap bmp = createThumbnail(pathtoimage[0]);
        thumbnaillist.put(pathtoimage[1], bmp);
        imageName = pathtoimage[1];
        return bmp;
    }

    @Override
    protected void onPostExecute(Bitmap bmp) {

        if (((String) thumbnail.getTag()).equals(imageName)) {
            thumbnail.setImageBitmap(bmp);
        }
    }

    private Bitmap createThumbnail(String filepath) {
        Bitmap bmp = BitmapFactory.decodeFile(filepath);
        int w = bmp.getWidth();
        int h = bmp.getHeight();
        if (w > h) {
            w = 80;
            h = 40;
        } else if (w < h) {
            w = 40;
            h = 80;
        }
        bmp = Bitmap.createScaledBitmap(bmp, w, h, true);

        return bmp;
    }
}

I still cannot figure out what else to do in order to get the Thumbnails to display at the correct rows WHILE SCROLLING.

Please Note: After scrolling away from the affected rows and scrolling back down, things work fine, but the scrolling problem is not going away!

Tuneberg answered 27/7, 2012 at 21:25 Comment(1)
T
20

From this blog post:

What you want to do is running all per-row IO or any heavy CPU-bound routine asynchronously in a separate thread. The trick here is to do that and still comply with ListView‘s recycling behaviour. For instance, if you run an AsyncTask to load a profile picture in the adapter’s getView(), the view you’re loading the image for might be recycled for another position before the AsyncTask finishes. So, you need a mechanism to know if the view hasn’t been recycled once you’re done with the async operation.

One simple way to achieve this is to attach some piece of information to the view that identifies which row is associated with it. Then you can check if the target row for the view is still the same when the async operation finishes. There are many ways of achieving this. Here is just a simplistic sketch of one way you could do it:

public View getView(int position, View convertView,
        ViewGroup parent) {
    ViewHolder holder;

    ...

    holder.position = position;

    new ThumbnailTask(position, holder)
            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

    return convertView;
}

private static class ThumbnailTask extends AsyncTask {
    private int mPosition;
    private ViewHolder mHolder;

    public ThumbnailTask(int position, ViewHolder holder) {
        mPosition = position;
        mHolder = holder;
    }

    @Override
    protected Cursor doInBackground(Void... arg0) {
        // Download bitmap here
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mHolder.position == mPosition) {
            mHolder.thumbnail.setImageBitmap(bitmap);
        }
    }
}

private static class ViewHolder {
    public ImageView thumbnail;
    public int position;
}
Trochal answered 28/7, 2012 at 19:53 Comment(5)
ok thanks a lot! the ViewHolder method worked perfectly now. I must have been doing something wrong before! but now the main concern is that when i scroll, and the bitmaps have been created, they do not get assigned to the corresponding row unless i scroll the row out of the screen and then again back in! is there any method that i could possibly poll the view and assign the bitmaps using the HashMap of the filename and its bitmap created in the AsyncTask? your help would be really appreciated by me! thanks again!Tuneberg
I'm not sure what could cause this new problem; I suggest you post a new question about it. But, I did find a better implementation that you might be able to use from the Android Developers Blog. Complete demo project is here.Trochal
Jeff, could you please post the whole getView() function? I'm trying to implement it and only the first element of the ListView gets updated, looks like I have problem reusing the viewsHeeled
Do you have to recycle/dispose the bitmap if it's not the correct mPosition in onPostExecute?Contaminant
what is cursor ? and what return ?Gastropod
F
1

Using the AsyncTask.THREAD_POOL_EXECUTOR wil end up running multiple threads to fetch the thumnails. There is no order in the way they run or finisch and set the image for the view. When scrolling back and forth you may end up with the wrong images in your views.

I tested the AsyncTask.THREAD_POOL_EXECUTOR and it does lead to the wrong image for some views.

Fontes answered 26/6, 2013 at 9:15 Comment(0)
S
0

you can try passing in ListView into your Adapter constructor from your activity (may be even into your async task depends how you want to implement it). and compare the position using getFirstVisiblePosition() and getLastVisiblePosition() against your mPosition

Slaughterhouse answered 18/1, 2013 at 9:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.