ListView如何运作的?

ListView是设计应用于对可扩展性和高性能要求的地方。实际上,这就意味着ListView有以下2个要求:

  1. 尽可能少的创建View;
  2. 只是绘制和布局在屏幕上可见的子View。

理解第一点很简单:通过布局xml文件在创建View并显示是很昂贵耗时耗资源的操作。尽管布局文件已经编译打包成了二进制形式以便于更高效的语法解析,但是创建View仍然需要通过一个特殊的XML树,并实例化所有需要响应的View。

ListView通过回收一些不可见的Views,通常在Android源码中称为“ScrapView(废弃的View)”来解决这个问题。这及意味着开发者只需要简单的更新每行的内容而不需要针对每个单独的行的布局来创建View。

为了实现第二点,在我们滑动屏幕时,ListView通过使用View回收器来增加低于或者高于当当前窗口的Views,并当前活动的Views移动到一个可回收池中。这样的话,ListView只需要在内存中保持足够多的Views去填充分配空间中的布局和一些额外的可回收Views,即使当你的Adapter有上百个items的适合。它会使用不同的方法去填充行之间的空间,从顶部或者底部等等,具体取决于窗口是如何变化的。

下面这个图很直观的展示了当你按下ListView的时候发生了什么:

通过上述介绍,相比我们已经熟悉了ListView的这种机制,让我们继续前往技巧部分。正如上述介绍的,在滑动时,ListView通过动态的创建和回收很多View,实现了尽可能地让Adapter的getView()轻量。所有的技巧都是通过多种方法让getView()更快。

View的回收

当ListView每次需要在屏幕上显示新的一行的时候,会从其Adapter中调用getView()的方法。众所周知,getView()方法有3个参数:行的位置, convertView以及父ViewGroup。

参数convertView说穿来就是之前讲述的ScrapView。当ListView要求更新一行的布局时,convertView是一个非空值。因此,当convertView值非空时,你仅仅需要更新内容即可,而不需要重新一个新行的布局。getView()在Adapter中一般是如下的形式:

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

View Holder如何写的模板

Android很常见的一个操作就是在布局文件中找到一个内部的View。通常是使用一个findViewById()的View方法来实现的。这个findViewById()方法在View树中,根据一个View ID,会递归的被调用来找到其子树。虽然在静态UI布局中使用findViewById()是完全正常的。但是,在滑动时,ListView调用其Adapter中的getView()是非常频繁的。findViewById()可能会影响ListView滑动时的性能,尤其是你的行布局是很复杂的时候。

寻找一个充气布局内的内部观点是在Android上最常用的操作之一。这通常是通过一个名为findViewById(查看方法完成)。此方法将递 归经过视图树寻找一个孩子用给定的ID码。静态的UI布局使用findViewById()是完全正常,但正如你所看到的,ListView中滚动时调用 适配器的getView()非常频繁。 findViewById()可能perceivably击中ListViews,尤其是滚动的性能,如果你行的布局是不平凡的。

View Holder的模式就是减少在Adapter中getView()方法中调用findViewById()次数。实际上,View Holder是一个轻量级的内部类,用于直接引用到所有内部views。在创建View之后,你可以在每行的View存储为一个标签。通过这种方法,只需要在初次创建布局的时候调用findViewById()。下面是一个使用上述方法的View Holder模板的代码示例:

 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应用在ListView每行中显示一些多媒体内容,比如图片等。在Adapter中的getView()使用应用内置的图片资源还是不会出什么问题的,因为可以存储在Android的高速缓存中。但当你想多态的显示来自本地磁盘或网络的内容时,例如缩略图,简历图片等。在这种情况下,你可能不希望直接在Adapter中的getView()加载它们,因为IO进程会阻塞UI线程。如果这样做的话,ListView就看起来非常卡顿。

在一个单独的线程,如果想要运行的所有行的IO操作或任何高负载CPU限制的异步操作。其中的技巧就是要做到符合ListView的回收行为。例如,如果在Adapter中的getView()中,使用AsyncTask的加载去加载资料图片,在AsyncTask完成之前,你正在加载的图片View就有可能被回收用于其他地方。所以,一旦异步操作完成的同时,需要一种机制来知道如果相应的View有没有被回收。

一个简单的方法来实现这一目标是通过附加一些标识该行与它相关的View的信息。然后,当异步操作完成的适合,检查目标行的View和标识的View是否一致。实现这一目标的方法很多。下面是实现这种方法的一个很简单的示例:

 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的必经之路。但是,在滑动屏幕时,如果你一味的在每一个getView()调用里面都去启动一个异步的操作,造成的结果就是你会浪费大量资源。因为行被频繁回收,造成大部分返回的结果会被丢弃。

考虑到实际的人机交互情况,在ListView适配器中,在每一行中都不应该去触发任何异步操作。也就是说,在ListView中有fling(快速滑动)操作时,启动任何异步操作都没有任何意义。一旦滚动停止或即将停止,才是开始真正显示每行的内容的时候。

我不会发布一个代码示例贴在这里,因为其中涉及到的代码太多。Romain Guy写了一个很经典的应用:Shelves app,其中有一个很好的的示例。当GridView停止滑动时不做其他事情时,它就开始触发从而去异步加载书的封面资源。即使在滑动时,你也可以展示缓存中的内容,通过使用memory cache来平衡交互。这真是个好主意!

以上

我强烈推荐你看下Romain Guy和Adam Powell的关于ListView的讨论,里面涵盖了很多这篇文章的东西。你可以看看Pattrn,可以看到这里面的几个技巧是如何在应用中运用的。

希望它是你在Android开发中一个很有用的参考:–)

Long Luo at PM17:30 Feb. 14th, 2014 @Shenzhen, China.

original link:http://longluo.github.io/blog/20140214/some_tips_about_android_listview_performence/

written by Frank Luo posted at http://longluo.github.io

提升Android ListView性能的几个技巧的更多相关文章

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

    本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住 ...

  2. 优化Android App性能?十大技巧必知!

    无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的.那么,如何能开发出更高性能的Android App?相信是软件 ...

  3. Android ListView性能优化实例讲解

    前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListVi ...

  4. 如何从软硬件层面提升 Android 动画性能?

    若是有人问如何解决动画性能不佳的问题,Dan Lew Codes 总会反问:你是否使用了硬件层? 动画放映过程中每帧画面可能都要重绘.如果使用视图层,,渲染过的视图可以存入离屏缓存以待将来重用,而无需 ...

  5. 提升 Web开发性能的 10 个技巧

    随着网络的高速发展,网络性能的持续提高成为能否在芸芸App中脱颖而出的关键.高度联结的世界意味着用户对网络体验提出了更严苛的要求.假如你的网站不能做到快速响应,又或你的App存在延迟,用户很快就会移情 ...

  6. Android Listview 性能优化

    首先我一般使用的适配器是BaseAdapter,其中有两个方法最主要,分别是: getCount,getView, 在对Listview 进行优化的时候,首先使用 convertview 和viewH ...

  7. Android开发——布局性能优化的一些技巧(一)

    0. 前言 上一篇我们分析了为什么LinearLayout会比RelativeLayout性能更高,意义在于分析了这两种布局的实现源码,算是对一个小结论的证明过程,但是对布局性能的优化效果,对这两种布 ...

  8. 十大技巧优化Android App性能

    无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的. 那么,如何能开发出更高性能的Android App?相信是软 ...

  9. Android ListView 常用技巧总结

    本文对 ListView 中的一些常用技巧做一个总结.附:虽然现在 RecyclerView 已逐渐取代 ListView,但实际情况是大部分项目中还在使用 ListView.当然,后续我会在我的博客 ...

随机推荐

  1. MVC与MVVM之间在IOS中的区别

    作为一个iOS开发者,我不止一次听到我的一些开发者朋友跟我说我写的iOS代码看起来好舒服,很整洁,为什么咱们iOS开发的相当一大部分应用软件都给人以美的享受,究竟是什么使得iOS上的应用可以如此漂亮? ...

  2. 初识ExtJS 6----自学笔记(一)

    一.使用环境 这一点写在前面,是为了方便大家在找资料的时候可以直接定位环境版本. ExtJS版本 6.2  中文官方网站提供版本,网站地址http://extjs.org.cn/node/793 开发 ...

  3. MySQL->复制表[20180509]

    MySQL复制表     通常复制表所采用CREATE TABLE .... SELECT 方式将资料复制,但无法将旧表中的索引,约束(除非空以外的)也复制.   完整复制MySQL数据表所需步骤: ...

  4. 每日Linux命令(2)-cal

    cal命令用来显示公历,公历是现在国际通用的历法. 一.格式 cal [选项] [参数] 二.功能 显示当前日历年月日,也可以指定显示某年全年日历及时间. 三.命令选项 -h 关闭今天显示的高亮 -j ...

  5. 基于OpenCV的微信跳一跳外挂

    摘要:微信跳一跳是时下热门的微信小游戏,基本原理是根据按压屏幕的时间控制棋子跳过的距离,使其跳到下一个方块上:现利用Android adb工具,PC端获取实时截图,使用OpenCV库分析图片计算距离, ...

  6. python3配置文件的增删改查,记录一下

    #!/usr/bin/env python3 import json #json模块,用于将像字典的字符串转换为字典 import re #re模块,查找替换 import shutil #copy文 ...

  7. 用gogs搭建git服务器

    Gogs 是一款极易搭建的自助 Git 服务. Gogs 的目标是打造一个最简单.最快速和最轻松的方式搭建自助 Git 服务.使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 G ...

  8. Go语言中多字节字符的处理

    1 概述 Go语言的字符串是使用 UTF-8 编码的.UTF-8 是 Unicode 的实现方式之一.本文内容包括:UTF-8 和 Unicode 的关系,Go语言提供的 unicode 包和 uni ...

  9. 【 C 】高级字符串查找之 strspn 和 strcspn 的思考

    我的CSDN博客 strspn 这个库函数是真的很难理解,看了很多中文描述,反正都是不知所云,给出一系列的例子,结果也是让我瞠目结舌,荒唐.荒谬.荒诞! 特此记录理解过程,最后竟然是百度百科让我明白了 ...

  10. .net core 无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了

    使用vs 发布.net CORE 项目,调试遇到了“无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了”这个问题,弄了半天才发现是发布的时候没有设置为debug,做个总 ...