ListView 的优化方式
之前探讨了下高清图片加载的优化,高清图片的加载确实有很大几率会给用户带来卡顿的感觉。然而我们最容易感觉卡顿的是什么呢,含有图片的listview,一方面图片本身容易造成卡顿,而没有优化好的listview更是一场噩梦,我们不仅感觉listview显示的时候卡顿,而且滑动listview的时候更是卡成翔,那么优化listview都有哪些手段呢?我想就这个来探讨一下。
要知道为啥listview会卡,我们得从listview的生成过程来了解一下哪些process比较耗时。先来看一段listview最依赖的adapter代码:
1 public View getView(int position, View convertView, ViewGroup parent) { 2 // TODO Auto-generated method stub 3 LayoutInflater inflater = LayoutInflater.from(this.context); 4 View inflaterView = inflater.inflate(R.layout.item, null); 5 TextView tv = (TextView) inflaterView.findViewById(R.id.tv); 6 ImageView img = (ImageView) inflaterView.findViewById(R.id.img); 7 tv.setText(FEATURE_NAMES[position]); 8 img.setImageResource(ICONS[position]); 9 return inflaterView; 10 }
Adapter 类作为listview显示不可缺少的适配器,listview的显示耗时几乎都在其中,而Adapter类的几大必须实现的方法中getView方法占有绝大部分的耗时操作。
我们来看看getView这个方法中要完成多少操作:
1、拿到LayoutInflater类的对象(inflater)并把一个布局(R.layout.item)扩展成一个View对象(inflaterView);
2、用生成的View对象(inflat而View)通过findViewById的方法取得子控件;
3、分别给子控件设置资源;
4、返回View对象完成getView的操作。
这些操作对于一个完整的getView流程是必须要实现的,耗时操作有:
1、对应的扩展方法inflate();
2、对应的findViewById();
3、对应的资源set方法;
4、所有的new对象方法。
一次完整getView流程必须要实现,如何优化呢?核心思想就是 减少重复、引入缓存。
1、我们可以尽可能的减少方法的重复调用。因为listview是一个列表的形式存在,对于列表里面的内存,每显示一行,都要调用一次getView方法,里面的方法都要重新执行一遍,我们应该尽可能的不重复调用一个重复的方法。我们可以很清楚的发现有的操作其实只需要一遍就行了,譬如:
LayoutInflater inflater = LayoutInflater.from(this.context);拿扩展对象,没有必要重复new一个LayoutInflater的对象。
View inflaterView = inflater.inflate(R.layout.item, null);每次都是从同一个布局中扩展成view,没有必要重复。
TextView tv = (TextView) inflaterView.findViewById(R.id.tv);
ImageView img = (ImageView) inflaterView.findViewById(R.id.img);
findViewById是一个非常耗时的操作,因为findViewById方法是一个蠢萌的树形遍历方法,当你应用中id多了的时候这个就非常耗时。而且每次findViewById同一个id时都是重复操作。
所以这些操作都可以拿出来避免重复,初步优化后的代码如下:
1 private LayoutInflater inflater; 2 private View inflaterView; 3 private TextView tv; 4 private ImageView img; 5 6 public View getView(int position, View convertView, ViewGroup parent) { 7 // TODO Auto-generated method stub 8 if (inflater == null) { 9 inflater = LayoutInflater.from(this.context); 10 } 11 if (inflaterView == null) { 12 inflaterView = inflater.inflate(R.layout.item, null); 13 } 14 if (tv == null) { 15 tv = (TextView) inflaterView.findViewById(R.id.tv); 16 } 17 if (img == null) { 18 img = (ImageView) inflaterView.findViewById(R.id.img); 19 } 20 21 tv.setText(FEATURE_NAMES[position]); 22 img.setImageResource(ICONS[position]); 23 24 return inflaterView; 25 }
这样优化后的getView能保证没有了那些重复调用的方法,减少了重复的耗时操作,如何你的listview没有超过一页,那么几乎是最快的方法。
2、引入缓存。上面的优化对于listview较短没有滑动的listview来说足够,但是如果listview超过一页显示,当我们上下滑动的时候,会发现按照上面的方法,即使我们之前已经getView过了的list,还是会重新调用set方法,然而我们完全可以不再重新刷新里面的数据的,所以这个地方就可以引入缓存,把我们已经get过的list给缓存起来,等要用的时候再直接返回就行了,进一步优化后的代码如下:
1 private LayoutInflater inflater; 2 private static View inflaterView; 3 private static TextView tv; 4 private static ImageView img; 5 private static View[] viewCache; 6 7 public View getView(int position, View convertView, ViewGroup parent) { 8 // TODO Auto-generated method stub 9 if (viewCache[position] == null) { 10 if (inflater == null) { 11 inflater = LayoutInflater.from(this.context); 12 } 13 if (inflaterView == null) { 14 inflaterView = inflater.inflate(R.layout.item, null); 15 } 16 if (tv == null) { 17 tv = (TextView) inflaterView.findViewById(R.id.tv); 18 } 19 if (img == null) { 20 img = (ImageView) inflaterView.findViewById(R.id.img); 21 } 22 23 tv.setText(FEATURE_NAMES[position]); 24 img.setImageResource(ICONS[position]); 25 26 viewCache[position] = inflaterView; 27 } 28 29 return viewCache[position]; 30 }
在这个例子中,static变量里面缓存了listview中已经get过的view,这样就可以在上滑操作时不必重复给view设置资源数据了,省了很多时间,这样所有的view都只需要get一遍,这几乎是最快的方法了,但是缓存也有缺点,listview缓存增加了内存消耗,不过在现在手机内存情况下一般是足够的。