9

このトピックは以前も今も非常に多く議論されており、私はすでに多くのチュートリアル、ヒントを読み、それについての話を見ました。しかし、行が特定の複雑さに達すると、ListView のカスタム BaseAdapter の実装にまだ問題があります。したがって、私が基本的に持っているのは、ネットワークからの xml を解析することによって得られるいくつかのエンティティです。さらに、いくつかの画像などを取得します。これはすべて AsyncTask で行われます。getView() メソッド内でパフォーマンスを最適化する ViewHandler アプローチを使用し、誰もが提案するように convertView を再利用します。つまり、ListView を想定どおりに使用していることを願っており、SpannableStringBuilder でスタイル設定された単一の ImageView と 2 つの TextView を表示しているだけで、実際に正常に動作します (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>

** * ** * ** * *更新* ** * ** * ** * ** *

結局のところ、問題は BaseAdapter の代わりに ArrayAdapter を使用することで簡単に解決されました。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;
    }
}

+++++++++++++++++ トレースを含む 2 番目の編集 +++++++++++++++++++++ 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

ご覧のとおり、これら 2 つのアダプターには大きな違いがあります (getView() メソッドでは ArrayAdapter の方が 4 倍高速です)。そして、なぜこれがそれほど劇的なのか、私にはまったくわかりません。ArrayAdapter には何らかの優れたキャッシングまたはさらなる最適化があるとしか思えません。

++++++++++++++++++++++++++もうひとつの更新++++++++++++++++++ 私の現在のUIBlogクラスがどのように構築されているかを示すには:

private class UIBlog {
    Blog blog;
    CharSequence seperatorTxt;
    Bitmap icon;
    CharSequence titleTxt;
    CharSequence dateTxt;
    CharSequence amountTxt;
    CharSequence authorTxt;
}

明確にするために、これを両方のアダプターに使用しています。

4

2 に答える 2

7

どこで時間が費やされているかを正確に確認するには、DDMS のプロファイラーを使用する必要があります。getView() 内で行っていることは高価であると思われます。たとえば、viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30); を実行します。毎回新しいアイコンを作成しますか? 常に画像をデコードすると、問題が発生します。

于 2011-08-30T18:40:24.170 に答える
2

読むのはかなり多いです;)

レイアウトに問題はありませんでした。|| を使用すると、最適化できます-最初の場合 - blogs.get( position ) を変数にキャッシュする - 定数 static を宣言します。- なぜ Calendar を使用し、それを ms に戻すのですか? あなたはすでにあなたのmsを持っているようですか?

しかし、それだけでは十分ではないのではないかと心配しています。

よろしく、ステファン

于 2011-08-30T23:10:23.270 に答える