RecyclerView性能优化及高级使用

最近研究应用流畅度专题时,发现RecyclerView里边的坑真多,有很多可以优化的点,在理解优化点之前,最好对RecyclerView的缓存机制有一些了解,比如得知道CacheView和RecycledViewPool的区别和联系,RecyclerView的绘制流程有一定了解,再来谈RecyclerView的性能提升。缓存机制可以看看这篇文章:基于滑动场景解析RecyclerView的回收复用机制原理

还有一篇外国人写的,ViewHolder的探究,这篇文章把RecyclerView的各级缓存作用剖析得很清晰,以前看过很多人写的文章,感觉都是一知半解,总结下:

1、RecyclerView缓存

1.1 RecyclerView主要有三级缓存:

(1)Attached scrap & Changed scrap

ArrayList mAttachedScrap

主要用在插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中,作用在LayoutManager中,它仅仅把需要从ViewGroup中移除的子view设置它的父view为null,从而实现了从RecyclerView中移除操作detachView()。需要新插入的view从cacheView/Pool中找,没找到则createViewHolder。而从ViewGroup中移除的子view会放到Pool缓存池中,如下图中的itemView b。

ArrayList mChangedScrap :

主要用到刷新屏幕上的itemView数据,它不需要重新layout,notifyItemChanged()或者notifyItemRangeChanged() (2) cache Views :保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。用一个数组保存ViewHolder,实现是:ArrayList mCachedViews

(3) RecyclerViewPool :缓存池,当cacheView满了后,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。实现是: SparseArray> mScrap;//按viewType来保存ViewHolder,每种类型最大缓存个数默认为5

1.2 RecyclerView缓存过程:

在滑动过程中,会先滑动的itemView保存到CacheView中,CacheView大小默认是2,如果超过了最大容量,则按FIFO,将队列头部的itemView出队,保存至缓存池RecyclerViewPool中,缓存池是按itemView的类型itemType来保存的,每种itemType默认缓存个数是5,超过了,则直接由GC回收。具体表现如下图:

可以看到CacheView缓存中蓝色的块一直最最近两个,而RecycledViewPool中,保存最大是5,超过5了后ViewHolder都被回收。

1.3 RecyclerView缓存寻找过程:

RecyclerView在找到可用ViewHodler的顺序是:如果在缓存CacheViews中找到,则直接复用;如果在缓存池RecycerViewPool找到,则需要bindView;如果没有找到可用的ViewHolder,则需要create新建一个ViewHolder,并bindView绑定view。

1.4 调用notifyDataSetChanged过程:

如果调用notifyDataSetChanged,每个itemView没有稳定的id的话,RecyclerView不知道接下来会发生什么,也不知道哪些改变,它假设所有都改变了,会将每一个ViewHolder设置成无效并且放到缓存池Pool中,如果我们仅是把屏幕上的第四条itemView移到第六条的位置,屏幕上所有itemView都会重新layout一遍,这样只能从缓存池RecycledViewPool池中取缓存的ViewHolder,如果不够时,需要重新create ViewHolder.具体实现如下:

如果设置了Stable Ids,即每一个itemView都有一个唯一的id来标识,通过getItemId()来获取这个唯一标识id,当然我们不能用position来标识,因为itemView会复用,位置会乱序。当调用notifyDataSetChanged()方法时,ViewHolder会进入上面的一级缓存mAttachedScrap中,而不是进入缓存池pool中,这样的好处:1)不会存在缓存池pool满的问题,不需要重新createViewHolder; 2) 不需要重新bindView了。

下面说说RecyclerView的一些优化方案和使用技巧:

1、recyclerView.setHasFixedSize(true);

当Item的高度如是固定的,设置这个属性为true可以提高性能,尤其是当RecyclerView有条目插入、删除时性能提升更明显。RecyclerView在条目数量改变,会重新测量、布局各个item,如果设置了setHasFixedSize(true),由于item的