Lots of garbage collection in a listview

11,862

Solution 1

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.

Solution 2

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"

Solution 3

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

Solution 4

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.

Solution 5

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;
}
Share:
11,862

Related videos on Youtube

Benoit Duffez
Author by

Benoit Duffez

Updated on June 29, 2020

Comments

  • Benoit Duffez
    Benoit Duffez almost 4 years

    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

    • Dave
      Dave almost 13 years
      Are you calling the GC yourself? I'm using far more complex objects in my ListViews and it's always been pretty smooth (in release mode).
    • ahmet alp balkan
      ahmet alp balkan almost 13 years
      What is result of the getViewTypeCount()? developer.android.com/reference/android/widget/…
    • Benoit Duffez
      Benoit Duffez almost 13 years
      @Dave: I'm not calling the GC myself. I pasted the whole getView method. @Ahmet Alp Balkan: getViewTypeCount() returns 1.
    • Joseph Earl
      Joseph Earl almost 13 years
      How big are your icons (file-size wise)?
    • Benoit Duffez
      Benoit Duffez almost 13 years
      REALLY small. in hdpi it's 32x32 PNG, approx 1.1kByte
    • dmon
      dmon almost 13 years
      Your getView() code seems fine, you should post the rest of your code to see if we can spot something wrong there.
    • Benoit Duffez
      Benoit Duffez almost 13 years
      Here's the code of the Activity and the class that does the things. pastebin.com/EgHKRr4r
  • Joe Simpson
    Joe Simpson almost 13 years
    This shouldn't be the issue, as the image will still be loaded in a similar way
  • tmho
    tmho almost 13 years
    worth a try imo, the view recycling code is correct, the objects shouldnt be destroyed.... the fact that his image size is 1.1kB and the garbage collector seems to have a lot around that size it leads me to believe that it is somewhere in the setImage creating an image object thus these objects will still have to be destroyed and not recycled into the next view, i reserve the right to be completely wrong tho, and if i am i would bet that the problem is not coming from this part of the code
  • Benoit Duffez
    Benoit Duffez almost 13 years
    That's a really good point, I don't know why I didn't think of DDMS before. However, the String.format is not the issue. If I comment out the setText and setImageResource, I end up with my list view populated with views, with no text and default icon. It is still choppy. The layout inflated is a simple relative layout containing a TextView and an ImageView.
  • Benoit Duffez
    Benoit Duffez almost 13 years
    I didn't try. But if I comment out the setImageResource line, it is still choppy. So that's not the problem.
  • Benoit Duffez
    Benoit Duffez almost 13 years
    Thank you for your answer. I don't think that's the problem because the List<> is only created once. Moreover, the method you are referring to is not the one called usually. In fact the list can be built in two ways: default order, or using a String that gives the order of the items. The app uses the ordered one if possible, which is likely to be the case at all times. When I scroll, this code is not executed, so that is not the issue. Thank you very much tough, I appreciate the time you spent trying to help me.
  • Benoit Duffez
    Benoit Duffez almost 13 years
    Even if I comment out the whole getView method and make it return (using convertView) a dumb TextView with default text like: tv.setText("test");, it is still choppy. I don't get it.
  • Thomas Philipakis
    Thomas Philipakis almost 13 years
    it's worth a try, maybe? you're using basically the same idiom in your createOrderedLabelsList method. Try to create a new reference of Item inside the for loop and see how it goes? for (i = 0; i < nbItems; i++) { ItemInRootList item = new ItemInRootList(); ...
  • Thomas Philipakis
    Thomas Philipakis almost 13 years
    ...and sorry for the comment spam - I'm very new to Stackoverflow :) - the thing is I think the ListView goes much faster when its underlying Collection is storing Pointers (my version) rather than Instances (your version). Now that I think about it, it makes sense and might well explain all the garbarge collection that's happening on your side.
  • Benoit Duffez
    Benoit Duffez almost 13 years
    I really don't understand. I basically removed all the code from my app: mItemsInList is not filled. I created a String[] containing 100 times "test". I created a layout that contains only a TextView, and used this adapter (not mine): new ArrayAdapter<String>(mActivity, R.layout.list_item, list);. Well, it still lags, it still does a lot of GC. So my initial adapter code was good, and the memory leak is somewhere else. I'm lost!
  • Benoit Duffez
    Benoit Duffez almost 13 years
    I did not really reply to your answer: yes I did try your solution, it was definitely worth a try. But it didn't solve the issue so I tried to remove my adapter and use a complete dumb String[] adapter. So the instance/pointer stuff is not causing the GC.
  • Thomas Philipakis
    Thomas Philipakis almost 13 years
    woah - now I'm starting to doubt how I could reproduce your bug :) - anyway congrats on solving your problem, keep it up!
  • Dori
    Dori over 12 years
    excellent, i was having this issue with the cacheColorHint attr also. Strange how setting it to transparent causes more GC?!
  • Artem Russakovskii
    Artem Russakovskii over 12 years
    Holy crap!!! The choppiness is gone after removing this evil cacheColorHint. The amount of GC_EXTERNAL_ALLOC has decreased 5-10-fold after removing just this value from the ListView XML definition. Garbage collection was going insane after I added BitmapFactory.Options inPurgeable = true to my Bitmap creation, and scrolling was insanely slow due to GC. You just saved me (and I even skipped this answer at first because I thought cacheColorHint was a property of each XML row, not the ListView itself!). That'll teach me to read closer.
  • Romain Guy
    Romain Guy over 12 years
    Looking at DDMS' allocation tracker would tell us what exactly is being allocated and collected. Setting the cacheColorHint to 0 should not cause memory to be allocated. I'd be curious to know on what version of Android or custom ROM you see this issue.
  • Artem Russakovskii
    Artem Russakovskii over 12 years
    I'm seeing massive amounts of GC_EXTERNAL_ALLOC in scrolling my GridView by the way. It's very choppy. Sometimes this goes away, but if I click Home and back into the app, it starts doing it again while scrolling.
  • manmal
    manmal over 12 years
    @RomainGuy: Is there a problem with setting the cacheColorHint to transparent? I'm experiencing the very same problem. Comparing the allocations in Allocation Tracker reveals no differences (scrolling down and up again - by pure chance, the number of allocations was EXACTLY the same), but jerkiness and gc'ing is insane with a transparent cacheColorHint.
  • Romain Guy
    Romain Guy over 12 years
    GC should not change if the allocations are the same. However, with a transparent color hint, the system has to do a lot of blending and the fades become more expensive.
  • achie
    achie over 12 years
    I am having the same issue and its weird but removing this solved it. "android:cacheColorHint="#00000000"". I picked up the habit of using it to remove the weird dark background that appears when scrolling as explained by you [Romain Guy] at Google I/O. I will look into this more later after I finish my project. Oh by the way my items are just an object with an id and string as title. I am not doing any string manipulations.
  • Danail
    Danail about 12 years
    Wow, this is huge... just removed mine from a listview, and the performance is completely different!! And I thought it was me, because of excessive image downloading... but not! The device is HTC Desire HD, 2.3.5 android. Really important, since it is also battery drainer!
  • Michał Klimczak
    Michał Klimczak over 11 years
    Worked for me, but I think it's just a hack and the problem lies elsewhere. That being said, the accepted answer of removing cacheColorHint didn't work for me. Nor the String.format() thing
  • tszming
    tszming over 10 years
    Your answer Worked for me (the answer "android:cacheColorHint" does not help)
  • Benoit Duffez
    Benoit Duffez almost 10 years
    If you have a similar, but new problem, you have to ask your own question. This question already found an answer and your answer is another question.