我有一个使用自定义适配器的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
个项目。RootListActivity
是ListActivity
,其中:
android.id.list
Activity
回调使用在创建活动时创建的RootList实例(构造函数,而不是RootList
)转发到onCreate
类onCreate
中,我调用RootList
的方法来创建项目列表,并将列表数据设置为从BaseAdapter
<派生的自定义类的新实例/ LI>
编辑于5月17日下午9:36 GMT:
这是Activity的代码和执行这些操作的类。 http://pastebin.com/EgHKRr4r
答案 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没有再次发生。