ListView实在是使用的太频繁了,记录下ListView怎么在项目中去优化卡顿现象。首先想到的就是,这些是最基础的。其他:下拉刷新,分页加载,加载不同类型的item。
1 复用convertView,为什么复用convertView
ListView中的每一个Item显示都需要Adapter调用一次getView()的方法,这个方法会传入一个convertView的参数,这个方法返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存空间,即创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环)的构件,就是当ListView的Item从滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的缓存Item的View,所以说如果能重用这个convertView,就会大大改善性能.
这个convertView不存在时,即第一次使用它,我们就创建一个item布局的View对象并赋给convertView,以后使用convertView时,只需从convertView中getTag取出来就可以,不需要再次创建item的布局对象了。
google的新控件recycleView就是使用的viewholder模式。强制使用
2 使用static ViewHolder()
ViewHolder为什么声明为静态类?ViewHolder为什么能起到性能优化的作用?
A:非静态内部类拥有外部类对象的强引用,因此为了避免对外部类的引用,将内部类声明为static 。(一般写内部类的意图都是为了说明该类和宿主类关系密切,而private , static , final 这些可以单独拿出来说,static这个区别比较大,没有static修饰,说明此类必须依赖宿主类的对象;而有static修饰,就不依赖与对象的存在了,类名. 即可,整体类似方法上添加static。private这玩意,完全取决于你是否希望将此类暴露出去;final则是为了标明该类不可继承修改)。
A:在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用ViewHolder,把每一个item的子View控件对象都放在Holder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
3使用异步线程加载图片(一般是用自己项目封装好的图片加载库ImageLoader,Picasso..)
除此之外 还有一些可以优化的地方:
Adapter核心的方法都是在getView方法中进行的。很多性能问题都是没有正确使用getView这个方法造成的。
Q1:在一次显示ListView界面时,getView会被执行几次?
Q2 :每次getVIew执行的时间该被控制在多少毫秒之内?
Q3: getView中设置listener要注意什么?
首先我们要知道ListView的ItemView有一个复用机制,简单看如下图所示,ListView中有一个RecycleBin类复负回收不可见且可能被再次使用的ItemView,由ScrapView存储。
所以我在们设置Listener进就要注意,使用convertView时需要重新设置一个Listener,包括一些数据也需要重设置,不然可能会显示之前那个ItemView在回收前的状态。
在绘制ListView前往往要计算它的高度,所以一个ListView界面上可以看到6个ItemView,但是getView的执行次数却有可能是12次,多出的次数用来计算高度(这个可以通过设置ListView的height为0来避免)。所以要避免在getView中进行逻辑运算,两次计算同一逻辑完全是浪费。
1秒之内屏幕可以完成30帧的绘制,人才能看到它比较流畅(苹果是接近60帧,高于60之后人眼也无法分辨)。
每帧可使用的时间: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的滑动就会有卡顿的现象。
4 在adapter中尽量减少逻辑判断,特别是耗时判断
5 避免GC(可以在LogCat中查看有没有GC的Log);
6 在快速滑动时不要加载图片(等列表慢下来或者停下来时再加载图片)给ListView实现
setOnScrollListener接口,并在OnScrollListener的onScrollStateChanged方法中判断列表是否处于快速滑动状态,如果是,停止加载图片。
7 将xml文件中ListVIew的ScrollingCache和animateCache这两个属性设置为false。默认是true
8 尽可能减少item的Layout层次,过多的嵌套消耗性能(View绘制相关)
9 在清单文件中对应的Activity设置硬件加速