Lots of garbage collection in a listview

I have a ListView that uses a custom adapter. The custom adapter's getView uses all the recommended practices:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    SuscriptionsViewsHolder holder;
    ItemInRootList item = mItemsInList.get(position);

    if (convertView == null) {
         convertView = mInflater.inflate(R.layout.label, null);

         holder = new SuscriptionsViewsHolder();
         holder.label = (TextView) convertView.findViewById(R.id.label_label);
         holder.icon = (ImageView) convertView.findViewById(R.id.label_icon);

        convertView.setTag(holder);
    } else {
        holder = (SuscriptionsViewsHolder) convertView.getTag();
    }

    String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);
    holder.label.setText(text);
    holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file );

    return convertView;
}

However when I scroll, it is sluggish because of heavy garbage collection:

GC_EXTERNAL_ALLOC freed 87K, 48% free 2873K/5447K, external 516K/519K, paused 30ms
GC_EXTERNAL_ALLOC freed 7K, 48% free 2866K/5447K, external 1056K/1208K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2866K/5447K, external 1416K/1568K, paused 28ms
GC_EXTERNAL_ALLOC freed 5K, 48% free 2865K/5447K, external 1600K/1748K, paused 27ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2865K/5447K, external 1780K/1932K, paused 30ms
GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed 3K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 28ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 27ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 34ms

What seems to be wrong?

EDIT @12:47 GMT:

In fact it's slightly more complicated than this. My app UI is based on 2 parts. One is the brain of a screen, creating the views, handling user input, etc. The other is a Fragment if the device has android 3.0, otherwise it's an Activity.

The GC happened on my Nexus One 2.3.3 device, so using the Activity. I don't have my Xoom with me to test the behaviour with a Fragment.

I could post the source if required, but let me try to explain it :

  • RootList is the brain of the UI. It contains :
    • a List<> of items that will be placed in the ListView.
    • a method that builds this list from a SQLite db
    • a custom BaseAdapter that contains basically only the getView method pasted above
  • RootListActivity is a ListActivity, which:
    • uses an XML layout
    • the layout has of course a listview with id android.id.list
    • the Activity callbacks are forwarded to the RootList class using an instance of RootList created when the activity is created (constructor, not onCreate)
    • in the onCreate, I call RootList's methods that will create the list of items, and set the list data to a new instance of my custom class derived from BaseAdapter

EDIT on may 17th @ 9:36PM GMT:

Here's the code of the Activity and the class that does the things. http://pastebin.com/EgHKRr4r

Answers


I found the issue. My XML layout for the activity was:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <include android:id="@+id/rootlist_header" layout="@layout/pre_honeycomb_action_bar" />

    <ListView android:id="@android:id/list"
        android:layout_below="@id/rootlist_header"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:textColor="#444444"
        android:divider="@drawable/list_divider"
        android:dividerHeight="1px"
        android:cacheColorHint="#00000000" />

</RelativeLayout>

If I remove the android:cacheColorHint="#00000000", the heavy GC is out, and the scrolling is smooth! :)

I don't really know why this parameter was set, because I don't need it. Maybe I copypasted too much instead of actually build my XML layout.

THANK YOU for your support, I really appreciate your help.


I also had a problem with the android:cacheColorHint="#00000000". I needed to use it although because I have a fixed background image.

I found that setting the following properties disables android's list view caching and the list scrolls smooth (the GC is not called that often any more):

android:scrollingCache="false"
android:animationCache="false"

Also if u want to get rid of the default selection color use this:

android:listSelector="#00000000"

You should use DDMS and its Allocation Tracker to figure out exactly what's generating so many objects. Note however that String.format() is well known for generating large amounts of garbage.

http://developer.android.com/resources/articles/track-mem.html


I actually went through your code and hacked a bit to make it work on my devices. I can confirm your ListAdapter is just fine, and that the problem is elsewhere.

This is what you do in the createDefaultLabelsList() method.

mItemsInList.clear();
ItemInRootList item;

do {
    item = new ItemInRootList();
    item.isLabel = true;
    item.id = c.getString(c.getColumnIndex(Labels.KEY_ID));
    item.title = Labels.label(item.id);
    //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL));
    item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT));
    mItemsInList.add(item);
} while (c.moveToNext());

It seems that not using a local variable in your loop is the cause to your performance problem, believe it or not. Replace that with:

mItemsInList.clear();

do {
    ItemInRootList item = new ItemInRootList();
    item.isLabel = true;
    item.id = c.getString(c.getColumnIndex(Labels.KEY_ID));
    item.title = Labels.label(item.id);
    //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL));
    item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT));
    mItemsInList.add(item);
} while (c.moveToNext());

and enjoy your blazing fast list! This solved it for me on htc hero (2.1) and Xoom (3.1) using a ListActivity.

That's how I would have done it in the first place - but I'm not exactly sure how this explains the sluggish behaviour with your original implementation.

I found this out because in order to reproduce your issue I had to replace the way you built the list, just created a list of with 10k random labels and watched it scroll really fast. I then noticed we didn't use the exact same loop, you were adding the same reference over and over again while I was adding 10k different references. I switched to your implementation and reproduced the bug.


this

 String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);

will make the GC called many times .

String.format() allocates some temporary memory in addition to the string it finally produces.

another way to do this.

using the StringBuilder class, like this.

StringBuilder builder = new StringBuilder(128);
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    SuscriptionsViewsHolder holder;
    ItemInRootList item = mItemsInList.get(position);

    if (convertView == null) {
         convertView = mInflater.inflate(R.layout.label, null);

         holder = new SuscriptionsViewsHolder();
         holder.label = (TextView) convertView.findViewById(R.id.label_label);
         holder.icon = (ImageView) convertView.findViewById(R.id.label_icon);

        convertView.setTag(holder);
    } else {
        holder = (SuscriptionsViewsHolder) convertView.getTag();
    }    

String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);

    builder.setLength(0);
    builder.append(item.title).append(" (").append(item.unreadCount).append(")");
    holder.label.setText(builder.toString());
    holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file );

    return convertView;
}

String text = String.format("%1$s (%2$s)", item.title, item.unreadCount); May be String.format() creates a lot of intermediate strings that pollutes the GC. Try StringBuilder and try to cache string it builds. Also as @tmho said try holder.icon.setImageDrawable(); instead of holder.icon.setImageResource(); because setImageResource() creates Drawable object each time. And each Drawable is about 50-60 bytes. It seems like heave bitmap isn't duplicated in this case, just drawable container but it may be enough to cause periodical GC calls.


try holder.icon.setImageBitmap(); instead of holder.icon.setImageResource();


This begins with 4.4.4 KitKat update. Rerun code already normal code on it and this problem appeared. ListView is completely unusable now due to very slow scroll. (Nexus 4)

And I'm not using any formatters on it.

When View is being scrolled numerous GC message appears.


I got similar question, my listview will show images from internet, and save them on SD card. When app launch later, local image will be used, then when i scroll the listview, lots of GC appear on logcat, and the view will stuns slightly.

I noticed that when app was launched first time, with images all from internet, the listview shows normally.

By the way, I used the android-imagedownloader from http://code.google.com/p/android-imagedownloader/, and add some local file cache code.

So here is my solution, when it loads image from local file, i will add the bitmap object to memory cache(a static HashMap), after that, when the listview scroll up and down, it will get image from memory cache, and lots GC did not happen again.


Need Your Help

Appcelerator vs Android SDK

android titanium appcelerator

I have been looking at appcelerator it seems pretty fine! Without a doubt, one of the advantages of appcelerator is its support for multi-platform. I am interested in building an android app and ma...

Can't add a corner radius and a shadow

iphone objective-c core-animation core-graphics quartz-graphics

I'm trying to draw a shadow and a corner radius on an image. I can add them separately, but I've got no way to add both effects at the same time. I'm adding a shadow with: