Android:如果ListView的性能还不够,该怎么办?

时间:2011-08-30 17:29:24

标签: android performance android-listview tablelayout baseadapter

这个主题已经并且仍然存在很多争论,我已经阅读了许多教程,提示并看到了有关它的讨论。但是当我达到某些行的复杂性时,我仍然遇到了为ListView实现自定义BaseAdapter的问题。 所以我基本上拥有的是通过解析来自网络的xml获得的一些实体。另外我获取一些图像等等,所有这些都是在AsyncTask中完成的。 我在getView()方法中使用性能优化的ViewHandler方法,并按照每个人的建议重用convertView。即我希望我正在使用ListView,因为它只是显示一个ImageView和两个TextViews,它们使用SpannableStringBuilder(我不使用任何HTML.fromHTML)。

现在它来了。每当我使用多个小型ImageView,一个Button以及一些使用SpannableStringBuilder进行不同样式设置的TextView扩展我的行布局时,我都会获得停止滚动的性能。该行包含一个RelativeLayout作为父级,所有其他元素都使用布局参数排列,因此我的布局不能使该行更简单。我必须承认,我从未见过任何包含许多UI元素的行的ListView实现示例。

但是,当我在ScrollView中使用TableLayout并手动填充AsyncTask(onProgressUpdate()稳定添加的新行)时,即使有数百行,它也表现得非常流畅。如果滚动到列表末尾,添加新行时会稍微绊倒一下。否则它比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>

** * ** * ** * 的* 更新 * ** * ** * ** * ** < EM> *

事实证明,问题只是通过使用ArrayAdapter而不是BaseAdapter来解决。我使用与ArrayAdapter完全相同的代码,性能差异是GIGANTIC!它的运行方式与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;
    }
}

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STRONG> 只是为了表明BaseAdapter与ArrayAdapter不同。这只是来自getView()方法的整个跟踪,在两个适配器中使用完全相同的代码。

首先是调用量http://img845.imageshack.us/img845/5463/tracearrayadaptercalls.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;
}

为了说清楚,我正在将它用于BOTH适配器。

2 个答案:

答案 0 :(得分:7)

您应该使用DDMS的探查器来确切地查看花费的时间。我怀疑你在getView()里面做的事情很昂贵。例如,viewUtility.setUserIcon(holder.usericon,blogs.get(position).getUid(),30);每次都创建一个新图标?一直解码图像会造成打嗝。

答案 1 :(得分:2)

阅读很多;)

我的布局中没有看到任何错误。 你可以优化   - 你的第一个如果有||   - 在一个变量中缓存blogs.get(position)   - déclare你一直静止。   - 你为什么要使用日历并将它装扮回ms?你好像已经有了你的ms?

但我担心这还不够。

此致  斯特凡