【问题标题】:ListView performance in AndroidAndroid 中的 ListView 性能
【发布时间】:2014-07-24 07:27:36
【问题描述】:

我正在开发一个 Andorid 应用程序,该应用程序将通过 ListView 列出很多书籍,实际上我已经使用 ListView 有一段时间了,我知道它的基本用法。

我还阅读了这篇论文:http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/

但是我发现即使我按照文章中提到的技巧,例如使用“视图回收”和“异步加载”,滚动 ListView 看起来也不流畅。

这是适配器:

class BookAdapter extends ArrayAdapter<Book> {
    private List<Book> data;

    public BookAdapter(Context context, int resource, List<Book> data) {
        super(context, resource, data);
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = new DynamicBookView(getContext());

        }

        Book bk = getItem(position);
        DynamicBookView bookView = ((DynamicBookView) convertView); //so said view recycling
        bookView.setBook(bk);

        return convertView;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Book getItem(int position) {
        return data.get(position);
    }

    public void swap(List<Book> books) {
        data.clear();
        data.addAll(books);
        notifyDataSetChanged();
    }
}

还有观点:

public class DynamicBookView extends RelativeLayout {
    private ImageView bImage;
    private TextView bName;
    private TextView bAuthor;
    private TextView bDesc;


    AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
    RequestHandle lastRequset;

    public DynamicBookView(Context context) {
        this(context, null);
    }

    public DynamicBookView(Context context, AttributeSet attrs) {
        super(context, attrs);

        View v = LayoutInflater.from(context).inflate(R.layout.common_dynamic_book, null);
        addView(v, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        bImage = (ImageView) v.findViewById(R.id.book_image);
        bName = (TextView) v.findViewById(R.id.book_name);
        bAuthor = (TextView) v.findViewById(R.id.book_author);
        bDesc = (TextView) v.findViewById(R.id.book_description);
    }

    public void setBook(final Book book) {
        DynamicBookView.this.reset();
        bName.setText(book.name);
        bAuthor.setText(book.author);
        bDesc.setText(book.description);
        if (book.icon != null)
            fetchImageForBook(book);
        if (!book.loaded) {
            // loadBook is an async operation which use `AsyncHttpClient` too
            BookServer.getInstance().loadBook(book, new BookServer.BookServerLoadListener() {
                @Override
                protected void onComplete() {
                    setBook(book);
                }
            });
        }
    }

    private void fetchImageForBook(Book book) {
        //get from local:


        if (lastRequset != null) {
            lastRequset.cancel(true);
        }
        lastRequset = asyncHttpClient.get(book.icon, new AsyncHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                bImage.setImageBitmap(BitmapFactory.decodeByteArray(responseBody, 0, responseBody.length));
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {

            }
        });

    }


    public void reset() {
        if (lastRequset != null) {
            lastRequset.cancel(true);
        }
        bName.setText("");
        bAuthor.setText("");
        bDesc.setText("");
        bImage.setImageBitmap(null);
    }
}

所以我想知道是否有什么我可以做的让滚动更流畅?

更新:1

一旦我删除加载代码:

public void setBook(final Book book) {
    DynamicBookView.this.reset();
    bName.setText(book.name);
    bAuthor.setText(book.author);
    bDesc.setText(book.description);
}

那么性能可以接受。

顺便说一句,请求需要 2-3 秒才能得到响应。

更新:2

我不确定你是否注意到,列表项的每个视图都会触发两个异步任务:

public void setBook(final Book book) {
    bName.setText(book.name);
    bAuthor.setText(book.author);
    bDesc.setText(book.description);
    if (book.icon != null)
        fetchImageForBook(book);
    if (!book.loaded) {
        // loadBook is an async operation which use `AsyncHttpClient` too
        BookServer.getInstance().loadBook(book, new BookServer.BookServerLoadListener() {
            @Override
            protected void onComplete() {
                setBook(book);
            }
        });
    }
}

通常,Book 只包含nameid,然后我必须像author descriptionimage 字段那样检索书的详细信息。然后我将触发另一个任务来获取图像。现在我评论了fetchImageForBook(book);这行,也就是说我只是显示书籍而不加载图像,但滚动仍然不流畅。(我一次无法获取信息,因为我们不提供服务)

【问题讨论】:

  • 如果把http操作换成本地的,性能会不会提升?
  • 我还没有添加本地支持,因为数据会经常变化,所以需要在线。
  • 好的,如果删除 http 元素会提高性能吗?您的结构是根据视图的请求从网络获取元素,所以我会认为这是造成性能问题的原因?
  • 是的,一旦我删除了异步加载任务(评论loadBook方法),那么性能是可以接受的。
  • @hguser 我不知道为什么你必须创建一个RelativeLayout的子类并自己添加视图,为什么不将common_dynamic_book.xml作为你的适配器的项目视图,你可以使用ViewHolder图案。查看本教程:jmsliu.com/1431/…

标签: android listview


【解决方案1】:

您可以使用ViewHolder pattern 进行更多性能优化,因为它可以被缓存并避免多次调用findViewById()

滚动 ListView 看起来不流畅

请记住,如果您只是在渲染视图时不执行繁重的任务,您的列表视图应该是平滑的。
从您的代码中,我可以看到您的问题是您多次从字节数组创建位图并且不缓存它们。

 bImage.setImageBitmap(BitmapFactory.decodeByteArray(responseBody, 0, responseBody.length));
 bImage.setImageBitmap(null);

如果你只是从远程服务器获取图像,我建议你使用一些图像加载器库,如 UIL、毕加索、Volley……因为它们可以将位图缓存到磁盘或内存中以提高性能,或者你可以自己做(在后台下载,缓存并加载到视图中)

这是我的适配器中使用 android Universal Image Loader 的示例:

 public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder;
    if (convertView == null) {
        viewHolder = new ViewHolder();
        convertView = mInflater.inflate(R.layout.item_document, null);      
        viewHolder.imageView = (ImageView)convertView.findViewById(R.id.image);
        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

   ImageLoader.getInstance().displayImage(item.getImagePath()),viewHolder.imageView);
 }

非常简单,滚动列表视图时我什至没有发现任何性能问题。
更新
从您的BookServer 源代码中,我认为您的问题在这里:

    @Override
        public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
            //do the hard job here   // what is this ???
            listener.onComplete();
        }

我不知道你在这个方法中做了什么辛苦的工作,但问题是这个回调方法运行在UI线程上。它会导致性能问题,而 asynctask 不会因为它在后台线程上运行。

【讨论】:

  • 谢谢,但我不确定是不是图片加载导致问题,你能检查我的update 2吗?
  • @hguser 首先,正如我所说,您应该使用查看器模式(您仍然可以重用它)。第二,你也可以评论DynamicBookView.this.reset();吗?您不需要在更新它们之前重置所有视图(尤其是对于图像位图)。最后,我怀疑您的图像加载任务不会导致滚动滞后,因为您在滚动时没有重用任何位图。如果你仍然遇到这个问题,我能猜到的最后一个是你的加载书方法。
  • 好的,我将评论DynamicBookView.this.reset();,为什么我使用这一行是我担心在加载当前书籍时重用的视图可能会显示一本旧书。我同意你的观点,图片加载任务不会导致滚动延迟,我会改成viewholder模式再试一次。
  • 嗨,R4j,我尝试更改为viewholder 模式,并且性能得到了显着提升。但我不确定为什么?由于两者都使用async 加载。即使我像这样更改代码:paste.ubuntu.com/7852812,一旦我使用BookServer.getInstance().loadBook 而不是new DownloadAsyncTask().execute(viewHolder);,我仍然会得到滚动延迟。
  • 所以你的意思是你在loadbook而不是asynctask中遇到了性能问题?
猜你喜欢
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-22
  • 2012-05-11
  • 1970-01-01
相关资源
最近更新 更多