basteez
basteez

Reputation: 477

Android AsyncTask weird behavior

I have a ListFragment that loads asynchronously multiple images but it behave in a strange way, as you can see here.

Here's the AsyncTask code

public class BitmapWorkerClass extends AsyncTask<Integer, Void, Bitmap>
{
    private Context context;
    private final WeakReference<ImageView> imageViewWeakReference;
    private int data = 0;
    public BitmapWorkerClass(ImageView imageView, Context context)
    {
        this.context = context.getApplicationContext();
        imageViewWeakReference = new WeakReference<ImageView>(imageView);
    }

    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return ImageResizer.decodeSampledBitmapFromResource(context.getResources(),     data, 100,100);
    }

    @Override
    public void onPostExecute(Bitmap bitmap)
    {
        if(imageViewWeakReference != null && bitmap != null)
        {
            final ImageView imageView = imageViewWeakReference.get();
            if(imageView != null)
            {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

And I call it from the ListFragment adapter's getView() method

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if(convertView == null)
    {
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_fragment_single_recipe_title, null);
        holder = new ViewHolder();

        holder.image = (ImageView) convertView.findViewById(R.id.recipeTitleImage);
        holder.title = (TextView) convertView.findViewById(R.id.recipeTitleText);
        convertView.setTag(holder);
    }
    else
    {
        holder = (ViewHolder) convertView.getTag();
    }
    Recipe recipe = getItem(position);
    loadBitmap(recipe.getImage(), holder.image);
    holder.title.setText(recipe.getTitle());
    return convertView;

public void loadBitmap(int resId, ImageView imageView)
{
    BitmapWorkerClass task = new BitmapWorkerClass(imageView, getContext());
    task.execute(resId);
}

Can you help me to understand what makes the AsyncTask behave as in the video?

Thanks in advance

Upvotes: 1

Views: 346

Answers (1)

Suau
Suau

Reputation: 4738

You are probably adding your data in a loop and calling notifyDataSetChanged() within the loop or you are using ArrayAdapters add() method. You possibly also add your data in a different order than displayed.

Therefore convertView gets recycled. This results in getView() for the same item to be called several times.

This causes multiple instances of BitmapWorkerClass to have a WeakReference to the same instance of ImageView. Each of them finish at some point and call OnPostExecute(), causing the shuffle effect.

Using a WeakReference (not recommended btw) helps with memory leaks, but it doesn't help you with View-recycling. The code below should fix the problem you have, however it might not be the most efficient way.

    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if(convertView == null)
        {
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_fragment_single_recipe_title, null);
            holder = new ViewHolder();

            holder.image = (ImageView) convertView.findViewById(R.id.recipeTitleImage);
            holder.title = (TextView) convertView.findViewById(R.id.recipeTitleText);
            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) convertView.getTag();
        }
        Recipe recipe = getItem(position);
        // cancel the previous asynctask if there was any
        if (holder.asynctask != null) {
            holder.asynctask.cancel(false);
            // you could pass true, but would have to handle the interruption then
        }
        // remove the previous image, you could set a default or loading image here instead 
        holder.image.setImageDrawable(null);
        holder.asynctask = loadBitmap(recipe.getImage(), holder.image);
        holder.title.setText(recipe.getTitle());
        return convertView;
    }

    public BitmapWorkerClass loadBitmap(int resId, ImageView imageView)
    {
        BitmapWorkerClass task = new BitmapWorkerClass(imageView, getContext());
        task.execute(resId);
        return task;
    }

    public static class ViewHolder {
        ImageView image;
        TextView title;
        BitmapWorkerClass asynctask; // save a reference to the asynctask
    }

Upvotes: 4

Related Questions