【问题标题】:Lots of garbage collection in a listview列表视图中的大量垃圾收集
【发布时间】:2011-05-12 12:07:34
【问题描述】:

我有一个使用自定义适配器的 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

似乎出了什么问题?

编辑@12:47 GMT:

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

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

如果需要,我可以发布源代码,但让我尝试解释一下:

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

格林威治标准时间 5 月 17 日晚上 9:36 编辑:

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

【问题讨论】:

  • 你自己打电话给GC吗?我在我的 ListViews 中使用了更复杂的对象,而且它总是非常流畅(在发布模式下)。
  • getViewTypeCount() 的结果是什么? developer.android.com/reference/android/widget/…
  • @Dave:我自己没有打电话给 GC。我粘贴了整个 getView 方法。 @Ahmet Alp Balkan:getViewTypeCount() 返回 1。
  • 你的图标有多大(文件大小)?
  • 真的很小。在 hdpi 中它是 32x32 PNG,大约 1.1kByte

标签: android listview garbage-collection


【解决方案1】:

我发现了问题。我的活动 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 布局。

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

【讨论】:

  • 太棒了,我的 cacheColorHint attr 也有这个问题。奇怪将其设置为透明如何导致更多的 GC?!
  • 天哪!!!移除这个邪恶的 cacheColorHint 后,波涛汹涌的感觉就消失了。仅从 ListView XML 定义中删除此值后,GC_EXTERNAL_ALLOC 的数量减少了 5-10 倍。在我将 BitmapFactory.Options inPurgeable = true 添加到我的位图创建后,垃圾收集变得疯狂,并且由于 GC 滚动速度非常慢。你刚刚救了我(起初我什至跳过了这个答案,因为我认为 cacheColorHint 是每个 XML 行的属性,而不是 ListView 本身!)。这将教会我仔细阅读。
  • 查看 DDMS 的分配跟踪器会告诉我们究竟分配和收集了什么。将 cacheColorHint 设置为 0 应该不会导致分配内存。我很想知道您在哪个版本的 Android 或自定义 ROM 上看到此问题。
  • 顺便说一句,我在滚动我的 GridView 时看到了大量的 GC_EXTERNAL_ALLOC。这是非常波涛汹涌的。有时这会消失,但如果我单击主页并返回应用程序,它会在滚动时再次开始执行此操作。
  • 如果分配相同,GC 不应该改变。但是,使用透明颜色提示时,系统必须进行大量混合,并且淡入淡出变得更加昂贵。
【解决方案2】:

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

我发现设置以下属性会禁用 android 的列表视图缓存并且列表滚动平滑(不再经常调用 GC):

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

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

android:listSelector="#00000000"

【讨论】:

  • 为我工作,但我认为这只是一个 hack,问题出在其他地方。话虽如此,删除 cacheColorHint 的公认答案对我不起作用。也不是String.format() 的事情
  • 你的答案对我有用(答案“android:cacheColorHint”没有帮助)
【解决方案3】:

您应该使用 DDMS 及其分配跟踪器来准确找出产生这么多对象的原因。但请注意,String.format() 以产生大量垃圾而闻名。

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

【讨论】:

  • 这是一个非常好的观点,我不知道为什么我之前没有想到 DDMS。但是, String.format 不是问题。如果我注释掉 setText 和 setImageResource,我最终会得到我的列表视图,其中填充了视图,没有文本和默认图标。它仍然波涛汹涌。膨胀的布局是一个简单的相对布局,包含一个 TextView 和一个 ImageView。
  • 即使我注释掉整个 getView 方法并让它返回(使用 convertView)一个愚蠢的 TextView,默认文本如下:tv.setText("test");,它仍然不稳定。我不明白。
  • 我遇到了同样的问题,而且很奇怪,但删除它就解决了。 “机器人:cacheColorHint="#00000000"”。我养成了使用它来消除滚动时出现的奇怪深色背景的习惯,正如您 [Romain Guy] 在 Google I/O 上所解释的那样。在我完成我的项目后,我将在稍后进行更多研究。哦,顺便说一句,我的项目只是一个以 id 和字符串为标题的对象。我没有做任何字符串操作。
【解决方案4】:

我实际上浏览了您的代码并进行了一些修改以使其在我的设备上运行。 我可以确认您的 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());

并享受您的快速列表!这使用 ListActivity 在 htc hero (2.1) 和 Xoom (3.1) 上为我解决了这个问题。

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

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

【讨论】:

  • 感谢您的回答。我认为这不是问题,因为 List&lt;&gt; 只创建一次。而且,您所指的方法不是通常所说的方法。事实上,列表可以通过两种方式构建:默认顺序,或者使用给出项目顺序的String。如果可能,该应用程序会使用已订购的应用程序,这很可能始终如此。当我滚动时,这段代码没有执行,所以这不是问题。非常感谢你,感谢你花时间帮助我。
  • 值得一试,也许吧?您在 createOrderedLabelsList 方法中使用了基本相同的习语。尝试在 for 循环中创建 Item 的新引用,看看它是如何进行的? for (i = 0; i
  • ...对于垃圾评论感到抱歉 - 我对 Stackoverflow 很陌生 :) - 我认为当 ListView 的底层 Collection 存储指针(我的版本)而不是比实例(您的版本)。现在我想起来了,它是有道理的,并且可以很好地解释你身边发生的所有垃圾收集。
  • 我真的不明白。我基本上从我的应用程序中删除了所有代码:mItemsInList 未填充。我创建了一个包含 100 次“测试”的String[]。我创建了一个只包含TextView 的布局,并使用了这个适配器(不是我的):new ArrayAdapter&lt;String&gt;(mActivity, R.layout.list_item, list);。好吧,它仍然滞后,它仍然做了很多 GC。所以我最初的适配器代码很好,内存泄漏在其他地方。我迷路了!
  • 我并没有真正回复您的回答:是的,我确实尝试了您的解决方案,绝对值得一试。但这并没有解决问题,所以我尝试移除我的适配器并使用完全愚蠢的String[] 适配器。所以实例/指针的东西不会导致 GC。
【解决方案5】:

这个

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

【讨论】:

    【解决方案6】:

    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 字节。在这种情况下,似乎没有复制 heave 位图,只是可绘制容器,但它可能足以导致定期 GC 调用。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 这应该不是问题,因为图像仍然会以类似的方式加载
      • 值得一试imo,视图回收代码是正确的,对象不应该被破坏......事实上他的图像大小是1.1kB,而垃圾收集器似乎有很多这个大小它让我相信它在 setImage 中的某个地方创建了一个图像对象,因此这些对象仍然必须被销毁而不是回收到下一个视图中,我保留完全错误的权利,如果我敢打赌问题不是来自这部分代码
      • 我没试过。但是如果我注释掉 setImageResource 行,它仍然不稳定。所以这不是问题。
      【解决方案8】:

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

      而且我没有在上面使用任何格式化程序。

      当视图被滚动时,会出现大量 GC 消息。

      【讨论】:

      • 如果您有类似但新的问题,您必须提出自己的问题。此问题已找到答案,而您的答案是另一个问题。
      【解决方案9】:

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

      我注意到第一次启动应用程序时,所有图片均来自互联网,列表视图正常显示。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-10-26
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 2019-09-14
        • 1970-01-01
        • 2018-12-30
        相关资源
        最近更新 更多