列表视图中有大量垃圾回收

时间:2011-05-12 12:07:34

标签: android listview garbage-collection

我有一个使用自定义适配器的ListView。自定义适配器的getView使用了所有推荐的做法:

@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;
}

然而,当我滚动时,由于垃圾收集量很大,它很迟钝:

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

什么似乎错了?

EDIT @ 12:47 GMT:

事实上它比这稍微复杂一点。我的应用UI基于2个部分。一个是屏幕的大脑,创建视图,处理用户输入等。如果设备具有android 3.0,则另一个是Fragment,否则它是Activity

GC发生在我的Nexus One 2.3.3设备上,所以使用Activity。我没有我的Xoom用Fragment测试行为。

如果需要,我可以发布消息来源,但让我试着解释一下:

  • RootList是UI的大脑。它包含 :
    • List<>ListView个项目。
    • 从SQLite数据库
    • 构建此列表的方法
    • 自定义BaseAdapter,基本上只包含上面粘贴的getView方法
  • RootListActivityListActivity,其中:
    • 使用XML布局
    • 布局当然是一个标识为android.id.list
    • 的列表视图
    • Activity回调使用在创建活动时创建的RootList实例(构造函数,而不是RootList)转发到onCreate
    • onCreate中,我调用RootList的方法来创建项目列表,并将列表数据设置为从BaseAdapter <派生的自定义类的新实例/ LI>

编辑于5月17日下午9:36 GMT:

这是Activity的代码和执行这些操作的类。 http://pastebin.com/EgHKRr4r

9 个答案:

答案 0 :(得分:21)

我发现了这个问题。我的活动XML布局是:

<?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>

如果我删除了android:cacheColorHint="#00000000",则重型GC已关闭,滚动 平滑 ! :)

我真的不知道为什么设置这个参数,因为我不需要它。也许我复制了太多而不是实际构建我的XML布局。

感谢您的支持,非常感谢您的帮助。

答案 1 :(得分:11)

我也遇到android:cacheColorHint="#00000000"的问题。 我需要使用它,因为我有一个固定的背景图像。

我发现设置以下属性会禁用android的列表视图缓存,列表会顺利滚动(GC不会再被调用):

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

此外,如果你想摆脱默认的选择颜色,请使用:

android:listSelector="#00000000"

答案 2 :(得分:8)

您应该使用DDMS及其分配跟踪器来确切地确定生成这么多对象的内容。但请注意,String.format()以生成大量垃圾而闻名。

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

答案 3 :(得分:1)

我实际上已经完成了您的代码并进行了一些攻击,以使其在我的设备上运行。 我可以确认您的ListAdapter很好,问题出在其他地方。

这是你在createDefaultLabelsList()方法中所做的。

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());

似乎不在循环中使用局部变量是导致性能问题的原因,不管你信不信。 将其替换为:

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());

享受你快速的快速列表吧!这解决了我在htc hero(2.1)和Xoom(3.1)上使用ListActivity。

我就是这样做的第一步 - 但我不确定这是如何解释原始实现的缓慢行为。

我发现了这一点,因为为了重现你的问题,我不得不更换你建立列表的方式,只是创建了一个包含10k随机标签的列表,并且看着它滚动得非常快。然后我注意到我们没有使用完全相同的循环,你在我添加10k个不同的引用时反复添加相同的引用。我切换到你的实现并重现了这个bug。

答案 4 :(得分:1)

这个

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

会多次调用GC。

String.format()除了最终生成的字符串外,还会分配一些临时内存。

另一种方法。

使用StringBuilder类,就像这样。

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;
}

答案 5 :(得分:0)

尝试holder.icon.setImageBitmap();而不是holder.icon.setImageResource();

答案 6 :(得分:0)

String text = String.format(“%1 $ s(%2 $ s)”,item.title,item.unreadCount); 可能是String.format()创建了许多污染GC的中间字符串。尝试StringBuilder并尝试缓存它构建的字符串。 也正如@tmho所说的尝试 holder.icon.setImageDrawable();而不是holder.icon.setImageResource(); 因为setImageResource()每次都创建Drawable对象。每个Drawable大约50-60个字节。在这种情况下,似乎没有重复的起伏位图,只是可绘制的容器,但它可能足以导致定期的GC调用。

答案 7 :(得分:-1)

这从4.4.4 KitKat更新开始。重新运行代码已经正常代码就出现了这个问题。由于滚动非常慢,ListView现在完全无法使用。 (Nexus 4)

我没有使用任何格式化程序。

滚动视图时会出现许多GC消息。

答案 8 :(得分:-2)

我有类似的问题,我的列表视图将显示来自互联网的图像,并将它们保存在SD卡上。当应用程序稍后启动时,将使用本地图像,然后当我滚动列表视图时,许多GC出现在logcat上,并且视图会稍微晕眩。

我注意到,当应用程序第一次启动时,所有来自互联网的图像都会正常显示。

顺便说一句,我使用了来自http://code.google.com/p/android-imagedownloader/的android-imagedownloader,并添加了一些本地文件缓存代码。

所以这是我的解决方案,当它从本地文件加载图像时,我会将位图对象添加到内存缓存(静态HashMap),之后,当listview向上和向下滚动时,它将从内存缓存中获取图像,许多GC没有再次发生。