【问题标题】:Android: What to do if performance of ListView is still not enough?Android:ListView的性能还是不够怎么办?
【发布时间】:2011-11-07 00:27:23
【问题描述】:

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

现在它来了。每当我使用多个小的 ImageView、一个 Button 和更多的 TextView 扩展我的行布局时,所有这些都使用 SpannableStringBuilder 进行了不同的样式设置,我得到了停止的滚动性能。该行由作为父级的 RelativeLayout 组成,所有其他元素都使用布局参数进行排列,因此我无法让该行的布局更简单。我必须承认,我从未见过任何 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

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

为了清楚起见,我将它用于两个适配器。

【问题讨论】:

  • 你对表格有什么问题?您找到了一个表现良好的解决方案,但为什么更喜欢列表?
  • 我当然对表格没有任何问题,这真的只是一个问题“使用 ListView 有任何限制吗?或者你可以总是使用 ListView(具有所有优点)作为列表?”在优化其性能时,也许我以某种方式错误地使用了 ListView。 :-)
  • @user535762:使用 Traceview 找出您的时间都花在了哪里。
  • 您的两种方法之间的一个重要区别是您在 ArrayAdapter 版本中只调用一次 get(position)。如果您的 List 随机访问(LinkedList)的成本很高,那可能是一个重要的区别。此外,发布 traceview 的屏幕截图对我们来说也不是很有用。您的屏幕截图未显示 getView() 中发生的情况,因此难以验证您的声明。
  • 这晚了,但基于BaseAdapter的原始适配器显然是错误的。 getItem(position) 返回位置?它与 ArrayAdapter 一起工作得更好的原因可能是因为他正在将他的对象从他正在使用的任何东西(可能每次都解析它们)转换为适当的支持数组。

标签: android performance android-listview tablelayout baseadapter


【解决方案1】:

您应该使用 DDMS 的分析器来准确查看时间花费的位置。我怀疑您在 getView() 中所做的事情很昂贵。例如,是否 viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30);每次都创建一个新图标?一直解码图像会造成麻烦。

【讨论】:

  • 好的,我会用分析器检查它。对于你的问题:我只是在做 view.setImageBitmap(myBitmap);位图已经被提取并存储在一个 ArrayList 中,所以我无法想象这应该是原因。当我在分析器中看到可疑内容时,我会在此处发布我的结果。 :)
  • BaseAdapter 不做任何事情,它不会受到“性能不佳”的影响。当您使用分析器时,您在哪里看到所花费的时间?
  • 去看看BaseAdapter的源代码,你就会明白它必须来自你在getView()中所做的一些事情。它不做任何事情。请不要建议其他开发人员不要使用 BaseAdapter,因为您的结论完全错误。
  • 您的个人资料图片无法证明任何事情,因为缺少重要数据。我在 Android 团队中维护 BaseAdapter 和 ArrayAdapter,我对它们的工作方式有一个很好的了解。看看 BaseAdapter 做了什么:android.git.kernel.org/?p=platform/frameworks/… 你的代码肯定有区别,例如你使用的 uiBlogs 列表是什么?
  • 所以.. 很抱歉破坏了这个,但是你有没有发现是什么导致你的性能在 BaseAdapter 上下降?只是出于兴趣。
【解决方案2】:

阅读量很大;)

我看不出您的布局有什么问题。 你可以优化 - 你的第一个 if 带有 || - 在变量中缓存 blogs.get( position ) - 声明你恒定不变。 - 为什么你使用日历并将其装饰回 ms ?你好像已经有你的女士了?

但我担心这还不够。

问候, 斯蒂芬

【讨论】:

  • 感谢美人 Stéphane,解决问题。 ;) 我会告诉你为什么我使用日历:用于将博客的给定 unixtime 设置为某个日期时间。 DateUtility.getDay(cal, 0) 方法将给定日历设置为 cal 给定日期的 00:00:00 点钟。因此,如果 cal 是 11 月 2 日 3:40 点,它将设置为 11 月 2 日 00:00:00 点。这样我就可以检查博客是在这个确切日期之前还是之后出现的。但正如你所说,这不可能是问题的原因。也许我以某种方式创建了太多对象而没有释放它们。我去看看....
  • 我仍然认为你可以优化更多的东西。例如,使用我所说的变量来缓存 blogs.get(position) 的结果。要优化的第二件事是存储 isSeparator 的结果。您可以在 UIBlog 列表(或特殊的 UIBlog 常量对象)列表中使用 null 来标记分隔符并避免每次都重新计算它们。此外,您认为您的位图在每次显示时都会调整大小还是已经适合?
  • 此外,您应该考虑拥有一个数据结构,一个模型,它不那么简洁,更接近逻辑,但更接近 UI,以避免重新计算标题、作者和日期,但已经将所有内容转换为迅速消失。好机会
  • 我已经将 blogs.get(position) 保存在一个临时变量中,并且我没有对图像进行任何调整大小或任何与图像相关的处理,在添加之前我已经在 AsyncTask 中完成了它们连接到适配器。我可以根据您的建议优化 isSeperator() 方法,但即使没有(仅显示博客,没有任何 isSeperator() 检查)任何分隔符,BaseAdapter 仍然会滞后。是的,我可以更改我的模型,但是我还必须更改我从中获取模型的第三方 API。正如我已经说过的,它与 ArrayAdapter 的代码相同,而且效果很好!
猜你喜欢
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 2015-09-07
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 2014-06-13
相关资源
最近更新 更多