listview 优化



(翻译) Android ListView 性能优化指南

本文翻译了Lucas RochaPerformance Tips for Android’s ListView。这是一篇关于介绍如何提升ListView性能的文章,非常的优秀。使得我拜读之后,忍不住将其翻译。本文采用了意译的翻译方式,尽可能的保持原文中要表达的内容。但是,任有几处翻译存在一些异议。请读者原谅。

几周之前,我正在为Native Firefox for Android 和 Pattrn 编程。那段时间,我被Android的一些基础代码彻底的弄晕了。我注意到许多关于优化ListView的小贴士被分散在各个地方。这篇blog的目的就是将这些我发现的,有用的小贴士整理起来。

在这里,我假设你对于LisView已经非常熟悉了。对于AdapterView框架也理解的比较深刻。当然,在文章中,我会加入一些Android的源码。为的是让一些同学能够理解的更加深入一些。

性能优化原理

ListView的设计目的就是可扩展和高性能。在实践中,这意味着:

  1. 尽可能的少去执行Layout的Inflate
  2. 只渲染和布置那些在可视范围内,或者即将出现在可视范围内的Itemcode

第一条的原因非常简单:Layout的Inflate是消耗资源巨大的代码code。即使,Layout文件已经被高效的解析程序转换为了二进制代码code。Infalte操作依旧需要彻底包含整个XML代码树,而且还要实例化相应的Viewcode。在Android 的源码中,ListView通过View回收机制解决了这个问题code。这就意味着,开发者可以非常简单的通过可回收的View设置每个Item的内容code。而不用,为每一个Item都Inflate Layout 。

通过ListView的View回收机制,第二条也被实现了。她会将那些在可视范围上面或者下面的View加入到回收池中。当在可视范围内的View被移出可视范围内时,其也会被添加到回收池中code。以这种方式,ListView只需占用非常少的内存几可以存储可视范围内的View和回收池中的View。即使,Adapter中有上百条Item,她也会运行的非常高效。ListView的View回收机制以两种不同的方式提供可回收的View——从上往下提供,和从下往上提供。采取何种方式,取决于滑动的方式code。下面这张图展示了,当你下滑时ListView的View回收机制所做的工作。

让我们把这个工作机制记在心中,现在开始让我们把注意力转移到优化ListView性能的小贴士上面去。如你上面所看到的那样,当滑动ListView时,ListView动态的提供可回收的View。因此,如何使你的Adapter的getView()变得尽可能轻巧成为了关键所在。所有的使ListView高效的小贴士的核心都是围绕着如何让getView()更加轻巧存在的。

贴士一:使用可回收的View

每当ListView需要显示一条新的Item的时候,她会回调你的Adapter的getView()方法。正如你你所知道的那样,getView()方法有三个参数:Item的位置,convertView,Item的上级容器。

参数convertView实际上就是一个之前我们提到的可回收的View。当ListView要回收这个View的时候,她的数据就会被清空。因此,当convertView不为null的时候,你只需要将数据填充到里面,而不用Inflate一个新的View。你的Adapter的getView()方法的代码应该像下面这样:

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);
    }

    TextView text = (TextView) convertView.findViewById(R.id.text);
    text.setText("Position " + position);

    return convertView;
}

贴士二:使用ViewHolder

在一个已经Inflate的Layout中寻找View是Android开发中非常普遍的操作。这通常通过View的findViewById方法来实现。这个方法会递归整个View树,以寻找那个与IDcode匹配的View。在静态的代码中使用findViewById()还是非常棒的,但是,如你看到的那样。当滑动ListView的时候,ListView会非常频繁的回调Adapter的getView()方法。这就可能在不知不觉中影响ListView的滑动性能。尤其发生在你的Item的Layout非常的复杂的时候。

ViewHolder就是用来存储那些在你的getView()方法中调用findViewById()方法得到的View。在实践中,ViewHolder可以是一个非常轻巧的内部类。她存储那些Item内部的View的直接引用。然后你可以在Inflate结束之后,将ViewHolder对象存储在Item的tag当中。以这种方式,你只需要在第一次创建Item的时候调用findViewById就可以了。下面就是使用ViewHolder提高ListView的代码:

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);

        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);

        convertView.setTag(holder);
    } else {
        holder = convertView.getTag();
    }

    holder.text.setText("Position " + position);

    return convertView;
}

private static class ViewHolder {
    public TextView text;
}

贴士三:异步加载

在Android的App中,ListView使用像图片这样耗费资源的Item是非常普遍的。在你的Adapter中使用drawable资源是非常棒的,因为Android内存的代码会缓存这些资源code。但是,你可能会想要使用更加灵活的内容。比如来自本地设备或者来自网络的缩略图或者图片之类的。在这种情况下,你可能不想要直接的在你的Adapter的getView()中加载这些资源。因为你永远不应该阻塞UI线程。同时这么做也使得你的ListView滑动的更加平滑。

你所要做的事情就是让那些需要IO操作或者耗费CPU资源的操作在一个额外的线程中运行。为了实现这个目的,你任然需要遵循ListView的View回收机制的规则。例如:当你的Adapter的getView()正在使用AsyncTask加载一张图片。需要这张图片的Item可能已经在图片加载完成之前就已经被ListView的View回收机制回收了。因此你必须要知道,当你完成异步加载的时候,对应的Item是否已经被回收了。

一个简单的方法是,为你的AsyncTask提供一个识别信息用以区别其对应的Item。这样,当你的AsyncTask完成加载工作的时候,就可以判断对应的Item还是不式最初的那个Item。事实上存在许多方式实现这个功能,下面的代码只是其中最为简单的一种:

public View getView(int position, View convertView,
        ViewGroup parent) {
    ViewHolder holder;

    ...

    holder.position = position;

    new ThumbnailTask(position, holder)
            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

    return convertView;
}

private static class ThumbnailTask extends AsyncTask {
    private int mPosition;
    private ViewHolder mHolder;

    public ThumbnailTask(int position, ViewHolder holder) {
        mPosition = position;
        mHolder = holder;
    }

    @Override
    protected Cursor doInBackground(Void... arg0) {
        // Download bitmap here
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mHolder.position == mPosition) {
            mHolder.thumbnail.setImageBitmap(bitmap);
        }
    }
}

private static class ViewHolder {
    public ImageView thumbnail;
    public int position;
}

贴士四:交互意识

为ListView的每一个Item异步加载大的资源是提升LitView的性能中很重要的一步。但是,如果当你滑动时,盲目的为每一个getView()开启一个异步任务是非常愚蠢的。你会浪费非常多的资源。因为大多数加载的资源都是无效的,因为滑动时Item的回收再利用是非常频繁的。

我们需要给你的ListView加入交互意识。这样她就不会在快速滑动时,为每一个Item都开启一个异步加载任务。要明白,当快速滑动时,你的异步加载任务还没有启动,对应的Item就已经被回收了。每次当滑动停止的时候,或者将要停止的时候,就是你为每一个Item加载大的资源的时机。

我不会给出实现交互意识的代码,因为这会是一大堆的代码。但是由Romain Guy提供的 Shelves 应用提供了一个非常好的演示。在这个应用中,基本上只有当 GridView停止滑动时,异步加载的任务才会被触发。同时,你也可以在数据缓存技术和意识交互技术之间做出一个平衡。当你使用数据缓存技术的时候,你就需要在滑动的时候缓存必要的数据。我想你是能理解我的意思的。

就是这些!

我强烈的建议你看一下Romain Guy 和 Adam Powell 的关于ListView的演讲。这其中涵盖了大多数,我在这里提到的小贴士的内容。你也可以通过 Pattrn 看到我上述提到的小贴士的效果。我在这篇blog中提到的小贴士并不是什么新的技术,只是我认为这些非常有用的文档应该被放在一个地方。希望这些内容对于你Android开发有用。

更新

我最近发布了 Smoothie 开源项目。这是一个非常小的项目,用于帮助你以最简单的方式实现ListViews或GridViews的异步加载。同时她也包含了我在这篇blog中提到的绝大多数的用于提高ListView性能的小贴士。并且她的API也非常的简单易用。所以,去瞥一眼吧!

该博客基于Jekyll。使用了brume作为模板。感谢这些作者的贡献。

HOME  ABOUT18 September 2014

(翻译) Android ListView 性能优化指南

本文翻译了Lucas RochaPerformance Tips for Android’s ListView。这是一篇关于介绍如何提升ListView性能的文章,非常的优秀。使得我拜读之后,忍不住将其翻译。本文采用了意译的翻译方式,尽可能的保持原文中要表达的内容。但是,任有几处翻译存在一些异议。请读者原谅。

几周之前,我正在为Native Firefox for Android 和 Pattrn 编程。那段时间,我被Android的一些基础代码彻底的弄晕了。我注意到许多关于优化ListView的小贴士被分散在各个地方。这篇blog的目的就是将这些我发现的,有用的小贴士整理起来。

在这里,我假设你对于LisView已经非常熟悉了。对于AdapterView框架也理解的比较深刻。当然,在文章中,我会加入一些Android的源码。为的是让一些同学能够理解的更加深入一些。

性能优化原理

ListView的设计目的就是可扩展和高性能。在实践中,这意味着:

  1. 尽可能的少去执行Layout的Inflate
  2. 只渲染和布置那些在可视范围内,或者即将出现在可视范围内的Itemcode

第一条的原因非常简单:Layout的Inflate是消耗资源巨大的代码code。即使,Layout文件已经被高效的解析程序转换为了二进制代码code。Infalte操作依旧需要彻底包含整个XML代码树,而且还要实例化相应的Viewcode。在Android 的源码中,ListView通过View回收机制解决了这个问题code。这就意味着,开发者可以非常简单的通过可回收的View设置每个Item的内容code。而不用,为每一个Item都Inflate Layout 。

通过ListView的View回收机制,第二条也被实现了。她会将那些在可视范围上面或者下面的View加入到回收池中。当在可视范围内的View被移出可视范围内时,其也会被添加到回收池中code。以这种方式,ListView只需占用非常少的内存几可以存储可视范围内的View和回收池中的View。即使,Adapter中有上百条Item,她也会运行的非常高效。ListView的View回收机制以两种不同的方式提供可回收的View——从上往下提供,和从下往上提供。采取何种方式,取决于滑动的方式code。下面这张图展示了,当你下滑时ListView的View回收机制所做的工作。

让我们把这个工作机制记在心中,现在开始让我们把注意力转移到优化ListView性能的小贴士上面去。如你上面所看到的那样,当滑动ListView时,ListView动态的提供可回收的View。因此,如何使你的Adapter的getView()变得尽可能轻巧成为了关键所在。所有的使ListView高效的小贴士的核心都是围绕着如何让getView()更加轻巧存在的。

贴士一:使用可回收的View

每当ListView需要显示一条新的Item的时候,她会回调你的Adapter的getView()方法。正如你你所知道的那样,getView()方法有三个参数:Item的位置,convertView,Item的上级容器。

参数convertView实际上就是一个之前我们提到的可回收的View。当ListView要回收这个View的时候,她的数据就会被清空。因此,当convertView不为null的时候,你只需要将数据填充到里面,而不用Inflate一个新的View。你的Adapter的getView()方法的代码应该像下面这样:

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);
    }

    TextView text = (TextView) convertView.findViewById(R.id.text);
    text.setText("Position " + position);

    return convertView;
}

贴士二:使用ViewHolder

在一个已经Inflate的Layout中寻找View是Android开发中非常普遍的操作。这通常通过View的findViewById方法来实现。这个方法会递归整个View树,以寻找那个与IDcode匹配的View。在静态的代码中使用findViewById()还是非常棒的,但是,如你看到的那样。当滑动ListView的时候,ListView会非常频繁的回调Adapter的getView()方法。这就可能在不知不觉中影响ListView的滑动性能。尤其发生在你的Item的Layout非常的复杂的时候。

ViewHolder就是用来存储那些在你的getView()方法中调用findViewById()方法得到的View。在实践中,ViewHolder可以是一个非常轻巧的内部类。她存储那些Item内部的View的直接引用。然后你可以在Inflate结束之后,将ViewHolder对象存储在Item的tag当中。以这种方式,你只需要在第一次创建Item的时候调用findViewById就可以了。下面就是使用ViewHolder提高ListView的代码:

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);

        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);

        convertView.setTag(holder);
    } else {
        holder = convertView.getTag();
    }

    holder.text.setText("Position " + position);

    return convertView;
}

private static class ViewHolder {
    public TextView text;
}

贴士三:异步加载

在Android的App中,ListView使用像图片这样耗费资源的Item是非常普遍的。在你的Adapter中使用drawable资源是非常棒的,因为Android内存的代码会缓存这些资源code。但是,你可能会想要使用更加灵活的内容。比如来自本地设备或者来自网络的缩略图或者图片之类的。在这种情况下,你可能不想要直接的在你的Adapter的getView()中加载这些资源。因为你永远不应该阻塞UI线程。同时这么做也使得你的ListView滑动的更加平滑。

你所要做的事情就是让那些需要IO操作或者耗费CPU资源的操作在一个额外的线程中运行。为了实现这个目的,你任然需要遵循ListView的View回收机制的规则。例如:当你的Adapter的getView()正在使用AsyncTask加载一张图片。需要这张图片的Item可能已经在图片加载完成之前就已经被ListView的View回收机制回收了。因此你必须要知道,当你完成异步加载的时候,对应的Item是否已经被回收了。

一个简单的方法是,为你的AsyncTask提供一个识别信息用以区别其对应的Item。这样,当你的AsyncTask完成加载工作的时候,就可以判断对应的Item还是不式最初的那个Item。事实上存在许多方式实现这个功能,下面的代码只是其中最为简单的一种:

public View getView(int position, View convertView,
        ViewGroup parent) {
    ViewHolder holder;

    ...

    holder.position = position;

    new ThumbnailTask(position, holder)
            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

    return convertView;
}

private static class ThumbnailTask extends AsyncTask {
    private int mPosition;
    private ViewHolder mHolder;

    public ThumbnailTask(int position, ViewHolder holder) {
        mPosition = position;
        mHolder = holder;
    }

    @Override
    protected Cursor doInBackground(Void... arg0) {
        // Download bitmap here
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mHolder.position == mPosition) {
            mHolder.thumbnail.setImageBitmap(bitmap);
        }
    }
}

private static class ViewHolder {
    public ImageView thumbnail;
    public int position;
}

贴士四:交互意识

为ListView的每一个Item异步加载大的资源是提升LitView的性能中很重要的一步。但是,如果当你滑动时,盲目的为每一个getView()开启一个异步任务是非常愚蠢的。你会浪费非常多的资源。因为大多数加载的资源都是无效的,因为滑动时Item的回收再利用是非常频繁的。

我们需要给你的ListView加入交互意识。这样她就不会在快速滑动时,为每一个Item都开启一个异步加载任务。要明白,当快速滑动时,你的异步加载任务还没有启动,对应的Item就已经被回收了。每次当滑动停止的时候,或者将要停止的时候,就是你为每一个Item加载大的资源的时机。

我不会给出实现交互意识的代码,因为这会是一大堆的代码。但是由Romain Guy提供的 Shelves 应用提供了一个非常好的演示。在这个应用中,基本上只有当 GridView停止滑动时,异步加载的任务才会被触发。同时,你也可以在数据缓存技术和意识交互技术之间做出一个平衡。当你使用数据缓存技术的时候,你就需要在滑动的时候缓存必要的数据。我想你是能理解我的意思的。

就是这些!

我强烈的建议你看一下Romain Guy 和 Adam Powell 的关于ListView的演讲。这其中涵盖了大多数,我在这里提到的小贴士的内容。你也可以通过 Pattrn 看到我上述提到的小贴士的效果。我在这篇blog中提到的小贴士并不是什么新的技术,只是我认为这些非常有用的文档应该被放在一个地方。希望这些内容对于你Android开发有用。

更新

我最近发布了 Smoothie 开源项目。这是一个非常小的项目,用于帮助你以最简单的方式实现ListViews或GridViews的异步加载。同时她也包含了我在这篇blog中提到的绝大多数的用于提高ListView性能的小贴士。并且她的API也非常的简单易用。所以,去瞥一眼吧!

该博客基于Jekyll。使用了brume作为模板。感谢这些作者的贡献。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值