译者注:1.由于这是一篇技术文章,为了表达更准确,所以使用了原文中的一些词句。2.由于有效水平,部分地方翻译可能不够准确。如有不当之处,请批评指正;3.inflation这个词一直没能找到特别好的中文翻译。列表视图是如何工作的?ListView旨在用于需要可伸缩性和高性能的地方。实际上,这意味着ListView有以下两个要求:创建尽可能少的View;只需绘制和布局屏幕上可见的子视图。第一点理解起来非常简单:通过layoutxml文件创建和展示View是一个非常昂贵、耗时、耗资源的操作。虽然布局文件已经被编译打包成二进制形式,以便更高效的语法解析,但是创建View仍然需要通过一个特殊的XML树,并实例化所有需要响应的View。ListView通过回收一些不可见的View来解决这个问题,在Android源码中通常称为“ScrapView(废弃View)”。这也意味着开发人员只需要简单地更新每行的内容,而无需为每个单独的行布局创建视图。为了实现第二点,当我们滑动屏幕时,ListView使用View回收器增加当前窗口下方或上方的Views,将当前活跃的Views移动到一个可回收池中。这样,ListView只需要在内存中保留足够的视图来填充分配空间中的布局和一些可以回收的额外视图,即使您的适配器适合数百个项目。它将使用不同的方法来填充行与行之间的空间,从顶部或底部等,具体取决于窗口的变化方式。下图直观的展示了当你按下ListView时会发生什么:通过上面的介绍,我们已经熟悉了ListView的机制,下面我们继续技巧部分。上面说了,ListView在滑动的时候会动态的创建和回收很多View,让Adapter的getView()尽可能的轻量级。所有的技巧都是通过多种方法使getView()更快。View的回收当ListView每次需要在屏幕上显示一个新行时,它会从它的Adapter中调用getView()方法。众所周知,getView()方法有3个参数:行的位置、convertView和父ViewGroup。参数convertView基本上就是前面说的ScrapView。当ListView要求更新行的布局时,convertView是一个非空值。因此,当convertView值为非null时,只需要更新内容即可,无需重新布局新行。Adapter中的getView()一般有如下形式:.findViewById(R.id.text);text.setText("Position"+position);returnconvertView;}ViewHolder的模板怎么写Android一个很常见的操作就是在layout文件中找一个内部View。这通常是使用findViewById()View方法完成的。View树中的findViewById()方法将被递归调用,以根据ViewID查找其子树。不过,在静态UI布局中使用findViewById()非常好。但是在滑动的时候,ListView会非常频繁的调用其Adapter中的getView()。findViewById()可能会影响ListView在滑动时的性能,尤其是当你的行布局非常复杂的时候。在膨胀的布局中查找内部视图是Android上最常用的操作之一。这通常是通过称为findViewById(View)的方法完成的。此方法将遍历视图树以查找具有给定ID代码的子项。使用findViewById()的静态UI布局非常好,但如您所见,ListView在滚动时非常频繁地调用适配器的getView()。如果您的行布局非常重要,findViewById()可能会明显影响ListView的性能,尤其是滚动。ViewHolder的模式是减少Adapter中getView()方法中findViewById()的调用次数。其实ViewHolder是一个轻量级的内部类,用来直接引用所有的内部视图。创建视图后,您可以将视图的每一行存储为标签。使用这种方法,您只需在首次创建布局时调用findViewById()。以下是使用上述方法的ViewHolder模板的代码示例:holder=newViewHolder();holder.text=(TextView)convertView.findViewById(R.id.text);convertView.setTag(holder);}else{holder=convertView.getTag();}holder.text.setText("Position"+position);returnconvertView;}privatestaticclassViewHolder{publicTextViewtext;}异步加载很多时候Android应用在ListView的每一行显示一些多媒体内容,比如图片。在Adapter中的getView()中使用应用内置的图片资源是没有问题的,因为它们可以存储在Android缓存中。但是当你想多态的显示来自本地磁盘或者网络的内容,比如缩略图,简历图片等。这种情况下,你可能不想直接在Adapter的getView()中加载它们,因为IO进程会阻塞用户界面线程。如果这样做,ListView会看起来很卡。如果要运行所有行IO操作或任何高负载CPU绑定异步操作,请在单独的线程中。诀窍是符合ListView的回收行为。比如你在Adapter中使用AsyncTask加载在getView()中加载头像,那么在AsyncTask完成之前你加载的图片View可能会被回收到其他地方。因此,一旦异步操作完成,就需要一种机制来知道对应的View是否已经被回收。实现此目的的一种简单方法是附加一些信息来标识与该行相关的视图。然后,当异步操作完成后,检查目标行的View是否与标识的View一致。有很多方法可以实现这个目标。这是实现此方法的一个非常简单的示例:publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ViewHolderholder;...holder.position=position;newThumbnailTask??(position,holder).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,null);returnconvertView;}privatestaticclassThumbnailTask??extendsAsyncTask{privateintmPosition;privateViewHoldermHolder;publicThumbnailTask??(intposition,ViewHolderholder){mPosition=position;mHolder=holder;}@OverrideprotectedCursordoInBackground(Void...arg0){//Downloadbitmaphere}@OverrideprotectedvoidonPostExecute(Bitmapbitmap=position).if(mHolder=position)){mHolder.thumbnail.setImageBitmap(bitmap);}}}privatestaticclassViewHolder{publicImageViewthumbnail;publicintposition;}人机交互知识每行异步加载大量资源是高性能ListView的必由之路。但是,在滑动屏幕的时候,如果一味的在每次调用getView()时都开启一个异步操作,结果是会浪费很多资源。因为频繁收集行,所以大部分返回的结果都被丢弃。考虑到实际的人机交互情况,在ListView适配器中,每一行都不应触发异步操作。也就是说,当ListView中存在滑动(滑动)操作时,启动任何异步操作是没有意义的。一旦滚动停止或即将停止,就该实际显示每一行的内容了。我不打算在这里发布代码示例帖子,因为涉及的代码太多。RomainGuy编写了一个经典应用程序:TheShelves应用程序,其中有一个很好的例子。当GridView停止滑动并且什么都不做时,它开始触发异步加载图书封面资源。即使在滑动时,也可以通过使用内存缓存来平衡交互来显示缓存的内容。真是个好主意!上面我强烈推荐你阅读RomainGuy和AdamPowell对ListView的讨论,其中涵盖了本文中的很多内容。您可以查看Pattrn以了解如何在应用程序中使用其中的几种技术。希望它对您在Android开发中有用的参考:-)LongLuoatPM17:30Feb.14th,2014@Shenzhen,China。原文链接:http://longluo.github.io/blog/20140214/some_tips_about_android_listview_performance/作者:FrankLuo
