A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

1黑马币
在面试的过程中,listview的优化考察是面试官屡试不爽的一个点

listview可以说是Android开发中最常见的UI控件了,listview能够以列表的方式显示大量同类的数据,这样问题就产生了,既然是大量数据,就会使用到很多布局,给布局绑定数据,listview将占用大量资源还可能会产生卡顿现象。
  listview现在最常用也拥有很好的性能的优化方式是在Adapter中使用静态的ViewHolder,具体代码如下:
Activity
  1. private TestAdapter mAdapter;

  2.      private String[] mArrData;
  3.      private TextView mTV;

  4.      @Override
  5.      protected void onCreate(Bundle savedInstanceState) {
  6.          super.onCreate(savedInstanceState);
  7.          setContentView(R.layout.main);
  8.          mTV = (TextView) findViewById(R.id.tvShow);

  9.          mArrData = new String[1000];
  10.          for (int i = 0; i $amp;             mArrData[i] = "Google IO Adapter" + i;
  11.          }
  12.          mAdapter = new TestAdapter(this, mArrData);
  13.          ((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
  14.      }
  15.   Adapter


  16.   private int count = 0;

  17.   private long sum = 0L;

  18.          @Override
  19.          public View getView(int position, View convertView, ViewGroup parent) {
  20.              // 开始计时
  21.             long startTime = System.nanoTime();

  22.              ViewHolder holder;
  23.              if (convertView == null) {
  24.                  convertView = mInflater.inflate(R.layout.list_item_icon_text,
  25.                          null);
  26.                  holder = new ViewHolder();
  27.                  holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1);
  28.                  holder.text1 = (TextView) convertView.findViewById(R.id.text1);
  29.                  holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2);
  30.                  holder.text2 = (TextView) convertView.findViewById(R.id.text2);
  31.                  convertView.setTag(holder);
  32.              }
  33.              else{
  34.                  holder = (ViewHolder)convertView.getTag();
  35.              }
  36.              holder.icon1.setImageResource(R.drawable.icon);
  37.              holder.text1.setText(mData[position]);
  38.              holder.icon2 .setImageResource(R.drawable.icon);
  39.              holder.text2.setText(mData[position]);

  40.              // 停止计时
  41.             long endTime = System.nanoTime();
  42.              // 计算耗时
  43.             long val = (endTime - startTime) / 1000L;
  44.              Log.e("Test", "Position:" + position + ":" + val);
  45.              if (count $amp;                 if (val $amp;                     sum += val;
  46.                      count++;
  47.                  }
  48.              } else
  49.                  mTV.setText(String.valueOf(sum / 100L));// 显示统计结果
  50.             return convertView;
  51.          }
  52.      }

  53.      static class ViewHolder {
  54.          TextView text1;
  55.          ImageView icon1;
  56.          TextView text2;
  57.          ImageView icon2;
  58.      }
复制代码
在Adapter的代码中,在getView方法里首先判断convertView是否为空,若为空则加载相应布局,若不为空则直接使用该布局,这能够很有效的使用Android为listview提供的缓存机制:只加载一屏的布局,之后滑动出来的item使用的是之前已经加载的布局的缓存;
  而使用静态的ViewHoulder的目的则是节省了findViewById的时间。如果不使用ViewHolder,每次getView的时候都需要得到一次子布局,而这也是很耗时并且耗资源的;如果使用了ViewHolder作为子布局的缓存,使用View的setTag方法将缓存与每个item绑定,则也可以省去了findViewById的事件;而将ViewHolder设置为static的目的是指在初始化Adapter时初始化一次这个内部类,否则将会在每次创建Adapter时都要初始化一次,而这是没有必要的。
  上述方法能够解决大部分listview消耗资源以及卡顿的问题,但对于不同的需求的listview来说还会存在其他让listview卡顿的原因,比如listview的item每次加载时都需要获得图片并设置到imageview中,item加载时需要进行大量的计算,item里的TextView需要设置指定字体;这些耗时的操作都会让listview滑动起来很卡,带来不好的体验;
  这几天我一直在研究Android4.0+系统联系人的源码,因为我发现,系统联系人采用的也是listview布局,每一个item都有图片文字,而且使用了fastscroller模式,对联系人以首字母进行了分来,同时Adapter还实现了SectionIndexer接口,能够实现这种效果:

  但是却一点都不卡,而我自己做的一个类似的界面,却十分卡顿,很影响用户体验;于是我找来了Android体统联系人的源码,自己建立起来了一个可以运行的程序,然后去研究它是如何实现这么流畅的listview的;
  经过研究我发现系统联系人的listview使用的是自定义的listview:PinnedHeaderListView 它的定义是这样的:
  1. /**

  2.   * A ListView that maintains a header pinned at the top of the list. The

  3.   * pinned header can be pushed up and dissolved as needed.

  4.   */
复制代码

它给listview加了一个header即显示在联系人界面最上方贴着屏幕顶部的那部分,同时它继承自AutoScrollListView 而这个AutoScrollListView继承自listview,对listview进行了一些优化,让listview在互动到指定的item时更流畅;
  看到这里,于是我使用了这个AutoScrollListView ,但效果不是很明显,listview还是很卡;在系统联系人使用的listview中没有找到解决方法,我开始研究它的Adapter;研究Adapter发现,系统的Adapter进行了很多层的封装,完全淡化了geiView方法;使用了很多AsyncTask来加载不同的数据,然后使用了CursorLoader来将加载好的数据添加到Adapter里,同时还将图片的加载与数据的加载进行了分离,代码逻辑十分复杂;但通过这个我得出了一个结论:不要将任何的耗时操作放在listview的getView方法里。
  在系统联系人的这些Adapter里我发现getView方法中没有任何的耗时操作,在设置图片时图片已经得到,对列表按照字母进行的分类也已经分类好了,存放在一个内部类里;在得出这个结论之前我尝试过很多系统联系人中的代码,但都没有得到明显的效果,经过大量的测试我得出了这个结论,并且在测试中得到了验证。
  在我的项目中,listview的每一个item都有一个图片,和很多TextView,而且所有的TextView都要设置非系统的字体;Adapter使用的是ViewHolder优化,在getView中的代码已经很少了,但是还是卡;我的listview中的数据是一个对象的List,在对象里只存放了item需要展示的图片的资源ID,或者是图片的路径,需要通过一些操作才能获得图片,而这些操作其实是很耗时的;于是我将原来的对象进行了更改,将图片对象直接存放在item对应的对象中,然后再Adapter初始化的时候将这些对象初始化,虽然listview展示所需的时间稍微长了一点,但是结果是listview滑动流畅了很多;接着我又将从assets中获得字体TypeFace的操作放在了Adapter初始化的方法中,并且将字体通过静态的变量都存起来,然后再getView中只需为TextView设置一下taptface即可,不需要在从asset中获取字体所花费的时间;通过上面两步操作之后,我的listview一点都不卡了,十分流畅。
  综上,listview的优化其实就是去找getView中的耗时操作,然后提取出来,要么使用异步的方式为item的布局设置数据,要是实在需要同步,就只能在Adapter初始化时将数据准备好,然后再getView中只需绑定一下就行。

3 个回复

倒序浏览
受教了  到时候实际项目试试
回复 使用道具 举报
yuchifeifan 来自手机 中级黑马 2016-2-22 00:48:39
藤椅
66666666666666666
回复 使用道具 举报
受教了  到时候实际项目试试
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马