Android:ListView性能还是不够怎么办?
嗯,这个话题过去和现在仍然存在很多争论,我已经阅读了很多教程、提示并看到了有关它的讨论。但是,每当我的行达到一定的复杂性时,我仍然会遇到 ListView 的自定义 BaseAdapter 的实现问题。 所以我基本上拥有的是通过解析来自网络的 xml 获得的一些实体。此外,我还获取一些图像等,所有这些都是在 AsyncTask 中完成的。 我在 getView() 方法中使用性能优化 ViewHandler 方法,并按照大家的建议重用 ConvertView。也就是说,我希望我按照预期使用 ListView,当我只显示一个 ImageView 和两个 TextView 时,它确实工作得很好,它们使用 SpannableStringBuilder 进行样式设置(我不使用任何 HTML.fromHTML ) 。
现在它来了。每当我用多个小 ImageView、一个 Button 和更多一些使用 SpannableStringBuilder 设计不同样式的 TextView 来扩展行布局时,我就会得到停止滚动的性能。该行由作为父级的相对布局组成,所有其他元素都使用布局参数进行排列,因此我无法使该行的布局变得更简单。我必须承认,我从未见过任何 ListView 实现的示例,其中的行包含那么多 UI 元素。
然而,当我在 ScrollView 中使用 TableLayout 并用 AsyncTask 手动填充它(通过 onProgressUpdate() 稳定添加新行)时,即使其中有数百行,它也表现得非常平滑。如果滚动到列表末尾,则在添加新行时,它只会稍微出错。否则它比 ListView 平滑得多,在 ListView 中滚动时总是绊倒。
当 ListView 不想表现良好时,有什么建议可以做什么?我应该继续使用 TableLayout 方法还是建议摆弄 ListView 来稍微优化性能?
这是我的适配器的实现:
protected class BlogsSeparatorAdapter extends BaseAdapter {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return blogs.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UIBlog blog = getItem(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_cmmts_amount);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(blog.titleTxt);
holder.date.setText(blog.dateTxt);
holder.amount.setText(blog.amountTxt);
holder.author.setText(blog.authorTxt);
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
这是该行的 xml(没有按钮,仍然很麻烦):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:background="@drawable/listview_selector">
<ImageView
android:id="@+id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingTop="@dimen/blogs_row_icon_padding_top"
android:paddingLeft="@dimen/blogs_row_icon_padding_left"/>
<TextView
android:id="@+id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/blogs_row_title_padding"
android:textColor="@color/blogs_table_text_title"/>
<TextView
android:id="@+id/blogs_row_date"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/blogs_row_date_padding_left"
android:textColor="@color/blogs_table_text_date"/>
<ImageView
android:id="@+id/blogs_row_cmmts_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_date"
android:layout_margin="@dimen/blogs_row_cmmts_icon_margin"
android:src="@drawable/comments"/>
<TextView
android:id="@+id/blogs_row_cmmts_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_icon"
android:layout_margin="@dimen/blogs_row_author_margin"/>
<TextView
android:id="@+id/blogs_row_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_amount"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_margin="@dimen/blogs_row_author_margin"/>
</RelativeLayout>
*** ******* 更新************* >
事实证明,通过使用 ArrayAdapter 而不是 BaseAdapter 就可以简单地解决问题。我在 ArrayAdapter 中使用了完全相同的代码,性能差异巨大!它的运行与 TableLayout 一样流畅。
因此,每当我使用 ListView 时,我肯定会避免使用 BaseAdapter,因为它的速度明显慢,并且对于复杂布局的优化较差。这是一个相当有趣的结论,因为我在示例和教程中没有读到任何有关它的内容。或者也许我没有准确地阅读它。 ;-)
然而,这是运行顺利的代码(正如您所看到的,我的解决方案是使用分隔符对列表进行分组):
protected class BlogsSeparatorAdapter extends ArrayAdapter<UIBlog> {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context, List<UIBlog> rows) {
super(context, R.layout.blogs_row_layout, rows);
inflater = LayoutInflater.from(context);
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final UIBlog blog = uiblogs.get(position);
int type = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
if (type == SEPERATOR) {
convertView = inflater.inflate(R.layout.blogs_row_day_separator_item_layout, null);
View separator = convertView.findViewById(R.id.blogs_separator);
separator.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// do nothing
}
});
holder.separator = (TextView) separator.findViewById(R.id.blogs_row_day_separator_text);
} else {
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
}
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_author);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder.separator != null) {
holder.separator
.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "EEEE, dd. MMMMM yyyy"));
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(createTitle(blog.blog.getTitle()));
holder.date.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "'um' HH:mm'Uhr'"));
holder.amount.setText(createCommentsAmount(blog.blog.getComments()));
holder.author.setText(createAuthor(blog.blog.getAuthor()));
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
+++++++++++++++++ 第二编辑痕迹 +++++++++++++++++++++++ 只是为了表明 BaseAdapter 所做的事情与 ArrayAdapter 不同。这只是来自 getView() 方法的整个跟踪,两个适配器中的代码完全相同。
首先是调用量 http://img845.imageshack.us/img845/5463/tracearrayadaptercalls.png
<一href="http://img847.imageshack.us/img847/7955/tracebaseadaptercalls.png">http://img847.imageshack.us/img847/7955/tracebaseadaptercalls.png
独占时间消耗 http://img823.imageshack.us/img823/6541/tracearrayadapterexclus.png
http://img695.imageshack.us/img695/3613/tracebaseadapterexclusi.png
包含时间消耗 http://img13.imageshack.us/img13/4403/tracearrayadapterinclus.png
http://img831.imageshack.us/img831/1383/tracebaseadapterinclusi.png
如您所见,有一个这两个适配器之间存在巨大差异(ArrayAdapter 在 getView() 方法中速度快四倍)。我真的不知道为什么事情会如此戏剧化。我只能假设 ArrayAdapter 有某种更好的缓存或进一步的优化。
++++++++++++++++++++++++++++只是另一个更新+++++++++++++++++++ 为了向您展示我当前的 UIBlog 类是如何构建的:
private class UIBlog {
Blog blog;
CharSequence seperatorTxt;
Bitmap icon;
CharSequence titleTxt;
CharSequence dateTxt;
CharSequence amountTxt;
CharSequence authorTxt;
}
为了清楚起见,我将其用于两个适配器。
Well this topic was and still is debated really a lot and I already read many tutorials, hints and saw talks about it. But I still have problems with my implementation of a custom BaseAdapter for a ListView whenever I reach a certain complexity of my rows.
So what I basically have are some entities I'm getting by parsing xml coming from the network. In addition I fetch some Images, etc. and all this is done in an AsyncTask.
I use the performance optimizing ViewHandler approach within my getView() method and reuse convertView as suggested by everyone. I.e. I hope that I'm using ListView as it's supposed to be and it really works fine when I'm just displaying a single ImageView and two TextViews, which are styled with a SpannableStringBuilder (I don't use any HTML.fromHTML whatsoever).
And now here it comes. Whenever I extend my row layout with multiple small ImageViews, a Button and some more TextViews all differently styled with SpannableStringBuilder, I get a ceasing scroll performance. The row consists of a RelativeLayout as a parent and all other elements are arranged with layout parameters, so I can't get the row to be more simple in its layout. I must admit that I never saw any example of a ListView implementation with rows containing that many UI elements.
However, when I'm using a TableLayout within a ScrollView and filling it by hand with an AsyncTask (new rows added steadily by onProgressUpdate() ), it behaves perfectly smooth even with hundreds of rows in it. It just stumbles a little bit when new rows are added if scrolled to the end of the list. Otherwise it's much smoother than with the ListView, where it's always stumbling when scrolled.
Are there any suggestions what to do when a ListView just doesn't want to perform well? Should I stay with the TableLayout approach or is it advised to fiddle with a ListView to optimize the performance a bit?
Here is the implementation of my adapter:
protected class BlogsSeparatorAdapter extends BaseAdapter {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return blogs.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
UIBlog blog = getItem(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_cmmts_amount);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(blog.titleTxt);
holder.date.setText(blog.dateTxt);
holder.amount.setText(blog.amountTxt);
holder.author.setText(blog.authorTxt);
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
This is the xml for the row (no button, still stumbling):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:background="@drawable/listview_selector">
<ImageView
android:id="@+id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingTop="@dimen/blogs_row_icon_padding_top"
android:paddingLeft="@dimen/blogs_row_icon_padding_left"/>
<TextView
android:id="@+id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/blogs_row_title_padding"
android:textColor="@color/blogs_table_text_title"/>
<TextView
android:id="@+id/blogs_row_date"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/blogs_row_date_padding_left"
android:textColor="@color/blogs_table_text_date"/>
<ImageView
android:id="@+id/blogs_row_cmmts_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_date"
android:layout_margin="@dimen/blogs_row_cmmts_icon_margin"
android:src="@drawable/comments"/>
<TextView
android:id="@+id/blogs_row_cmmts_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_icon"
android:layout_margin="@dimen/blogs_row_author_margin"/>
<TextView
android:id="@+id/blogs_row_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/blogs_row_title"
android:layout_toRightOf="@id/blogs_row_cmmts_amount"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_margin="@dimen/blogs_row_author_margin"/>
</RelativeLayout>
********** UPDATE *************
As it turned out the problem was simply solved by using ArrayAdapter instead of a BaseAdapter. I used the exact same code with an ArrayAdapter and the performance difference is GIGANTIC! It runs just as smooth as with a TableLayout.
So whenever I'm using ListView, I will definitely avoid using BaseAdapter as it is significantly slower and less optimized for complex layouts. This is a rather interesting conclusion because I hadn't read a word about it in examples and tutorials. Or perhaps I wasn't reading it accurately. ;-)
Well however this is the code that is working smoothly (as you can see my solution is using seperators to group the list):
protected class BlogsSeparatorAdapter extends ArrayAdapter<UIBlog> {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context, List<UIBlog> rows) {
super(context, R.layout.blogs_row_layout, rows);
inflater = LayoutInflater.from(context);
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final UIBlog blog = uiblogs.get(position);
int type = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
if (type == SEPERATOR) {
convertView = inflater.inflate(R.layout.blogs_row_day_separator_item_layout, null);
View separator = convertView.findViewById(R.id.blogs_separator);
separator.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// do nothing
}
});
holder.separator = (TextView) separator.findViewById(R.id.blogs_row_day_separator_text);
} else {
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
}
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_author);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder.separator != null) {
holder.separator
.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "EEEE, dd. MMMMM yyyy"));
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(createTitle(blog.blog.getTitle()));
holder.date.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "'um' HH:mm'Uhr'"));
holder.amount.setText(createCommentsAmount(blog.blog.getComments()));
holder.author.setText(createAuthor(blog.blog.getAuthor()));
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* @param position
* @return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
+++++++++++++++++ SECOND EDIT WITH TRACES +++++++++++++++++++++
Just to show that BaseAdapter DOES something different than the ArrayAdapter. This is just the whole trace coming from the getView() method with the EXACT same code in both adapters.
First the amount of calls http://img845.imageshack.us/img845/5463/tracearrayadaptercalls.png
http://img847.imageshack.us/img847/7955/tracebaseadaptercalls.png
Exclusive time consumption
http://img823.imageshack.us/img823/6541/tracearrayadapterexclus.png
http://img695.imageshack.us/img695/3613/tracebaseadapterexclusi.png
Inclusive time consumption
http://img13.imageshack.us/img13/4403/tracearrayadapterinclus.png
http://img831.imageshack.us/img831/1383/tracebaseadapterinclusi.png
As you can see there is a HUGE difference (ArrayAdapter is four times faster in the getView() method) between those two adapters. And I really don't have any idea why this is so dramatic. I can only assume that ArrayAdapter has some sort of better caching or further optimizations.
++++++++++++++++++++++++++JUST ANOTHER UPDATE+++++++++++++++++
To show you how my current UIBlog class is built:
private class UIBlog {
Blog blog;
CharSequence seperatorTxt;
Bitmap icon;
CharSequence titleTxt;
CharSequence dateTxt;
CharSequence amountTxt;
CharSequence authorTxt;
}
Just to make it clear, I'm using this for BOTH adapters.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您应该使用 DDMS 的分析器来准确查看时间花费在哪里。我怀疑你在 getView() 中所做的事情很昂贵。例如, viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30);每次创建一个新图标?一直解码图像会产生问题。
You should use DDMS' profiler to see exactly where time is spent. I suspect that what you are doing inside getView() is expensive. For instance, does viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30); create a new icon each time? Decoding images all the time would create hiccups.
有很多东西要读;)
我看不出你的布局有什么问题。
你可以优化
- 你的第一个 if 带有 ||
- 将 blogs.get(position) 缓存在变量中
- 声明你恒定的静态。
- 你为什么使用日历并将其装饰回女士?你好像已经有你的女士了?
但我担心这还不够。
问候,
史蒂芬
Quite a lot to read ;)
I couldn't see anything wrong in your layout.
You could optimize
- your first if with a ||
- cache blogs.get( position ) in a variable
- déclare you constant static.
- why do you use a Calendar and finery it back to ms ? You seem to already have your ms ?
But I fear it won't be enough.
Regards,
Stéphane