Stuttering Listview despite ViewHolder, LazyLoader

2019-07-23 09:19发布

问题:

I have a listview that loads images from a server into an ImageView. The Listview implements the viewholder pattern and has a very simple relative-layout for the rows. For lazy image loading, I have tried universalimageloader, picasso and am currently using glide, which is based on google volley, which should be blazing fast. However: My listview still stutters while replacing the placeholder image with the servers image and I just cant figure out why, here is my adapter-code:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        News newsItem = getItem(position);

        if (rowView == null) {
            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(mResource, null);
            // Create a viewHolder enabled row
            NewsViewHolder viewHolder = new NewsViewHolder();
            viewHolder.newsTitle = (TextView) rowView.findViewById(R.id.news_title);
            viewHolder.newsSubTitle = (TextView) rowView.findViewById(R.id.news_sub_title);
            viewHolder.newsDescription = (TextView) rowView.findViewById(R.id.news_desc);
            viewHolder.newsDate = (TextView) rowView.findViewById(R.id.news_date);
            viewHolder.newsThumb = (ImageView) rowView.findViewById(R.id.news_thumb);
            rowView.setTag(viewHolder);
        }
        NewsViewHolder holder = (NewsViewHolder) rowView.getTag();

        // Fill in fields of the current row of the NewsListView
        holder.newsTitle.setText(newsItem.title);
        holder.newsSubTitle.setText(newsItem.subTitle);
        holder.newsDescription.setText(newsItem.desciption);
        holder.newsDate.setText(SimpleDateFormat.getDateInstance().format(new Date(Long.parseLong(newsItem.date) * 1000)));

        Glide.load(newsItem.imageThumb)
                .centerCrop()
                .placeholder(R.drawable.placeholder)
                .into(holder.newsThumb);

        return rowView;
    }
}

/*
 * ViewHolder class for NewsListView
 */
public static class NewsViewHolder {
    public TextView newsTitle;
    public TextView newsSubTitle;
    public TextView newsDescription;
    public TextView newsDate;
    public ImageView newsThumb;
}

The list only stutters while i am scrolling it for the first time, so when my images are retrieved and pasted into the list by glide. After the images are pasted into the listview scrolling is buttery smooth as it should be from the start.

What did I do wrong? How can I efficiently find out what is causing the hangup? I am not an expert in Traceview and it is producing a lot of data to sift through.

Thanks for any help!

回答1:

Stuttering can be caused by ImageView continuously requesting layout. See ImageView.setImageDrawable:

if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
    requestLayout();
}

I can see that a single list item is really complex (5 which takes a lot of time to lay out, especially if the whole list is laid out on every image load.

The key is the size of the R.drawable.placeholder. On the first load the .placeholder() is always shown, because the image must be read from the Glide disk cache (that is: background I/O). Scrolling back and forth fills the memory cache which results in immediate loads (no background thread) and also the .placeholder() is not shown for this very reason → no size change → no requestLayout().

It also helps that your ImageView has a fixed size (assuming from centerCrop()) which means that all Bitmap loaded by Glide into this view will be the same size and these Bitmaps will be recycled, so you'll probably have a 1:1 correspondence between Bitmaps in the pool and your recycled rows. Also when an existing row is recycled the ViewHolder holds an ImageView that has a same sized image already displayed in it so there will be no re-layout.

Note: Glide is not based on Volley, there's a possiblity of using Volley via volley-integration, but by default it has no dependency on it. Though this question is quite old, maybe what you stated was true before 3.0.



回答2:

Look at your logcat. Maybe, the stuttering you are experiencing is due to the garbage collector (GC) trying to reallocate the heap for new bitmaps (you should see GC_FOR_ALLOC logs). If that's the case, there is not much to do... ART improved the way the GC works a lot (try it on ART).

If your bitmaps are all of the same size, you can reuse the bitmaps so that the runtime doesn't have to allocate more space for new bitmaps, as stated here:

https://developer.android.com/training/displaying-bitmaps/manage-memory.html#inBitmap

If you are using Glide, you can prefill the bitmaps pool to achieve the same thing.



回答3:

Try changing your code to this:

if (rowView == null) {
    LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    rowView = inflater.inflate(mResource, null);
    // Create a viewHolder enabled row
    NewsViewHolder viewHolder = new NewsViewHolder();
    viewHolder.newsTitle = (TextView) rowView.findViewById(R.id.news_title);
    viewHolder.newsSubTitle = (TextView) rowView.findViewById(R.id.news_sub_title);
    viewHolder.newsDescription = (TextView) rowView.findViewById(R.id.news_desc);
    viewHolder.newsDate = (TextView) rowView.findViewById(R.id.news_date);
    viewHolder.newsThumb = (ImageView) rowView.findViewById(R.id.news_thumb);
    rowView.setTag(viewHolder);
} else { // NEW
    NewsViewHolder holder = (NewsViewHolder) rowView.getTag();
}