Android中ListView常见优化方案

我们设置或者优化ListView的性能很多时候都是在getView中完成的,反过来说就是很多性能问题都是由于没有正确使用getView造成的。

public View getView(int position, View convertView, ViewGroup parent)

那么问题就来了。

在一次显示ListView的界面时,getView会被执行几次?

在绘制ListView前往往要计算它的高度,所以一个ListView界面上可以看到6个ItemView,但是getView的执行次数却有可能是12次,多出的次数用来计算高度(这个可以通过设置ListView的height为0来避免)。所以要避免在getView中进行逻辑运算,两次计算同一逻辑完全是浪费。

每次getView执行时间有多久?

1秒之内屏幕大概可以完成30帧的绘制,人才能看到它比较流畅,每帧可使用的时间:1000ms/30 = 33.33 ms,每个ListView一般要显示6个ListItem,加上1个重用convertView:33.33ms/7 = 4.76ms,即是说,每个getView要在4.76ms内完成工作才会较流畅,但是事实上,每个getView间的调用也会有一定的间隔(有可能是由于handler在处理别的消息),UI的handler处理不好的话,这个间隔也可难会很大(0ms-200ms)。结论就是,留给getView使用的时间应该在4ms之内,如果不能控制在这之内的话,ListView的滑动就会有卡顿的现象。
(转载:Android面试一天一题(11 Day) —— goeasyway)

有哪些常见的优化方案?

1. 使用ConvertView

也是最普通的优化,就在MyAdapter类中的getView方法中,我们注意到,上面的写法每次需要一个View对象时,都是去重新inflate一个View出来返回去,没有实现View对象的复用,而实际上对于ListView而言,只需要保留能够显示的最大个数的view即可,其他新的view可以通过复用的方式使用消失的条目的view,而getView方法里也提供了一个参数:convertView,这个就代表着可以复用的view对象,当然这个对象也可能为空,当它为空的时候,表示该条目view第一次创建,所以我们需要inflate一个view出来,所以在这里,我们使用下面这种方式来重写getView方法:

@Override  
public View getView(int position, View convertView, ViewGroup parent) {   
   View view;   
   // 判断convertView的状态,来达到复用效果   
   if (null == convertView) {    
       //如果convertView为空,则表示第一次显示该条目,需要创建一个view    
       view = View.inflate(MainActivity.this, R.layout.listview_item,null);   
    } else {    
       //否则表示可以复用convertView    
       view = convertView;   
    }   
    // listview_item里只有一个textview   
    TextView tv_item = (TextView) view.findViewById(R.id.tv_item);     
    tv_item.setText(list.get(position));   
    return view;  
}
2. 使用View Holder模式

经过上面的优化之后,我们不需要每一个view都重新生成了。下面我们来解决下一个每一次都需要做的工作,那就是view中组件的查找:

TextView tv_item = (TextView) view.findViewById(R.id.tv_item);

实际上,findViewById是到xml文件中去查找对应的id,可以想象如果组件多的话也是挺费事的,如果我们可以让view内的组件也随着view的复用而复用,就能对ListView进行很好的优化

private static class ViewHolder {  
   private TextView tvHolder; 
}

@Override  
public View getView(int position, View convertView, ViewGroup parent) {   
   View view;   
   ViewHolder holder;   
   // 判断convertView的状态,来达到复用效果   
   if (null == convertView) {    
       // 如果convertView为空,则表示第一次显示该条目,需要创建一个view    
       view = View.inflate(MainActivity.this, R.layout.listview_item, null);    
       //新建一个viewholder对象    
       holder = new ViewHolder();    
       //将findviewbyID的结果赋值给holder对应的成员变量    
       holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);    
       // 将holder与view进行绑定    
       view.setTag(holder);   
    } else {    
       // 否则表示可以复用convertView    
       view = convertView;    
       holder = (ViewHolder) view.getTag();  
    }   
    // 直接操作holder中的成员变量即可,不需要每次都findViewById   
    holder.tvHolder.setText(list.get(position));   
    return view;  
}

ps:
这里的ViewHolder类需要不需要定义成static,根据实际情况而定,如果item不是很多的话,可以使用,这样在初始化的时候,只加载一次,可以稍微得到一些优化。
不过,如果item过多的话,建议不要使用。因为static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(比如Context的情况最多),这时就要尽量避免使用了。

3. 分批加载与分页加载相结合

我们需要进行分批加载,比如说1000条新闻的List集合,我们一次加载20条,等到用户翻页到底部的时候,我们再添加下面的20条到List中,再使用Adapter刷新ListView,这样用户一次只需要等待20条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在ListView上显示。其次这样也可以缓解很多条新闻一次加载进行产生OOM应用崩溃的情况。

实际上,分批加载也不能完全解决问题,因为虽然我们在分批中一次只增加20条数据到List集合中,然后再刷新到ListView中去,假如有10万条数据,如果我们顺利读到最后这个List集合中还是会累积海量条数的数据,还是可能会造成OOM的情况,这时候我们就需要用到分页,比如说我们将这10万条数据分为1000页,每一页100条数据,每一页加载时都覆盖掉上一页中List集合中的内容,然后每一页内再使用分批加载,这样用户的体验就会相对好一些。

除此之前还有一些优化建议:

  1. 使用异步线程加载图片(一般都是直接使用图片库加载,如Glide, Picasso);
  1. 在adapter的getView方法中尽可能的减少逻辑判断,特别是耗时的判断;
  2. 避免GC(可以从LOGCAT查看有无GC的LOG);
  3. 在快速滑动时不要加载图片;
  4. 将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true);
  5. 尽可能减少List Item的Layout层次(如可以使用RelativeLayout替换LinearLayout,或使用自定的View代替组合嵌套使用的Layout);

(转载:Android面试一天一题(11 Day) —— goeasyway)

你可能感兴趣的:(Android中ListView常见优化方案)