RecyclerView回收机制分析
Recycler类是RecyclerView内部final类,它管理scrapped(废弃)或detached(独立)的Item视图,使它们可以重用。我们都知道,在ListView中,也有一个类似的RecycleBin类,管理Item的重用。 本文的重点是Recycler类,分析一下视图在消失与出现时,如何利用Recycler实现重用。
ViewHolder类RecyclerView的内部抽象类,我们自己定义的Adapter中实现,封装子视图的一些视图。
先看一下Recycler内部的几个引用。
mAttachedScrap列表: 用来存储Scrapped(废弃)的ViewHolder,它对应的视图是detached的,即ItemView调用了ViewGroup的detachViewFromParent方法,从容器的子视图数组中移除,它其实并没有被废弃。它正是存放从RecyclerView中detached的ItemView的ViewHolder列表。
当RecyclerView初始加载Item,第一次触发onLayoutChildren时,fill创建满足RecyclerView高度的子ItemView,ViewHolder绑定ItemView,并ViewGroup#addView加入RecyclerView视图。第二次onLayoutChildren时,通过detachAndScrapAttachedViews方法将全部ItemView从mChildren数组删除,触发的是ViewGroup#detachViewFromParent方法,ItemView变为detached,ViewHolder放入mAttachedScrap,fill继续触发从mAttachedScrap中获取ViewHolder,将ViewHolder加入子View数组,触发的是ViewGroup#attachViewToParent方法。
mCachedViews列表: 从RecyclerView区域移除,从ViewGroup中删除的ItemView,存储在列表中,最大值max,大于max时,删除最早进入的第0个元素,该元素放入RecycledViewPool中,如果还是放不下,直接放入RecycledViewPool。 永远存储最新从RecyclerView删除的视图ViewHolder。
ViewGroup已经执行过removeViewAt删除了View 。
RecycledViewPool: 视图缓存池,当mCachedViews存储不下时,将ViewHolder放入,根据类型存储。ViewGroup已经执行过removeViewAt删除了View。
ViewCacheExtension: 扩展使用,开发者自己控制缓存。
图中的数据源一共有17项,显示区域中,可容纳的子视图大约在12个左右。
RecyclerView视图显示出来以后,手指触屏,向上滑动。此时,position是0,1,2,3...的ItemView依次滚动出视图可见范围。
通过源码调试, 发现在LinearLayoutManager的recycleChildren方法处, 触发了下面的方法,定义在LayoutManager类。
首先,LayoutManager的removeViewAt方法, 从RecyclerView中删除索引index的子视图,它与position无关。调用辅助类ChildHelper的removeViewAt方法。
RecyclerView类的初始化initChildrenHelper方法,定义Callback对象,在辅助类的方法中, 调用内部Callback的对应方法。
dispatchChildDetached方法, 通知子视图detached,将调用Adapter的onViewDetachedFromWindow方法, 可以在自己的Adapter中重写。注意,这里并没有触发ViewGroup的detachViewFromParent方法。
RecyclerView的removeViewAt方法,调用父类ViewGroup的removeViewAt方法,删除该ItemView子视图。
手指上滑,每次最顶部Item视图滑出屏幕时,删除的都是index是0的子视图,手指下移,每次底部Item视图滑出可视范围,删除的都是index是12左右的子视图, 与position无关。
其次,调用Recycler的recycleView方法, 将ViewHolder加入缓存mCachedViews或RecycledViewPool池。
根据View获取它绑定的ViewHolder对象, 从View的LayoutParams中获取。 ViewHolder的内部mScrapContainer(即Recycler)是空,isScrap方法返回false。只有执行过Recycler的scrapView(View)方法,将ViewHolder加入到mAttachedScrap列表时,才会设置内部mScrapContainer值,当isScrap返回true时,调用unScrap方法, 调用内部Recycler的unscrapView方法。
从mAttachedScrap列表中删除,置空ViewHolder内部Recycler。
Recycler的recycleViewHolderInternal方法, 将ViewHolder加入缓存mCachedViews或RecycledViewPool池。
待加入的ViewHolder不能是Scrap,前面经过unScrap方法处理过。缓存mCachedViews最大值是mViewCacheMax,当达到最大时,删除第一个,被删除元素加入RecycledViewPool。如果数量已经小于最大值,将新ViewHolder放入mCachedViews缓存,如果仍然大于, 将其放入RecycledViewPool。
将ViewHolder所属的RecyclerView置空,执行dispatchViewRecycled回调, 该方法将调用Adapter的onViewRecycled方法,可重写。 ViewHolder放置到RecycledViewPool缓存池。
手指触屏,向上滑动,position是12,13,14,15...的ItemView依次从底部冒出,通过调试源码, 调用Recycler的getViewForPosition方法。 该方法根据position获取ItemView视图,position是RecyclerView的数据源索引,当视图完全展示后,子视图有12个,那么,最后一个的索引是11,position是12索引对应视图不可见,上滑时,12索引首先出现。
首先, 从mAttachedScrap与mCachedViews中查找ViewHolder,在视图滚动时,mAttachedScrap是空的,因此,一般情况从mCachedViews缓存查找。
validateViewHolderForOffsetPosition方法, 验证holder是否可用于对应position索引。如果验证通过,设置fromScrap标志,返回holder的itemView视图。如果验证失败,将增加无效标志,holder内部mScrapContainer(即Recycler)存在,说明holder是isScrap的 ,Scrap的holder无法被回收,unScrap方法提前去除其标志,最后会加入缓存,recycleViewHolderInternal方法。
其次, 从RecycledViewPool缓存池中查找。从这里获取的ViewHolder,设置mPosition是NO_POSITION(-1)。如果都未找到,通过Adapter的createViewHolder方法创建, 调用Adapter的onCreateViewHolder抽象方法,开发者重写此方法, 初始化ItemView,创建ViewHolder对象。最后,通过Adapter的bindViewHolder方法, 调用Adapter的onBindViewHolder抽象方法,开发者重写此方法。 初始化ViewHolder的View中数据。
Recycler的getScrapViewForPosition方法。
当视图滚动时,该方法从缓存mCachedViews查找ViewHolder,并且它的mPosition要和position一致。
举个例子说明一下。 假如position是12完全不可见,当向上滑动时,position是12的视图出现,此时,ViewHolder不是getScrapViewForPosition获取。因为mCachedViews还是空,或者position是0的视图已在mCachedViews缓存,但它的mPosition是0,与12不相等,也不会使用它。 因此,position是12的要新建ViewHolder。 当position是13和14...视图出现,对应position是1,2..的视图要进入mCachedViews缓存,如果mCachedViews缓存未达到最大值,将会一直新建ViewHolder,原因也一样,mPosition不符合。 如果到达最大值,缓存的最大值默认是2, 此时,已经存储position是0和1的值,继续上滑,position是2的视图要进缓存,删掉最早position是0的值,将它放入RecycledViewPool池。继续,position是14的出现,从缓存未找到符合的position,因为此刻缓存里还都是头部position较小的值,RecycledViewPool已经有值,就从RecycledViewPool获取。这里获取的与positon无关,ViewHoder的mPosition都是-1,只要type类型一样,在Adapter的bindViewHolder方法,会为mPosition赋值,这个ViewHolder内部mPosition就属于14啦。
改变方向手指下滑, position是2的视图出屏幕,对应的ViewHolder在缓存,直接使用。position是14的消失了,将position是14的ViewHolder加入缓存。
到这里,我们已经获取了屏幕下一个将要显示的ItemView,接下来就要将它加入到RecyclerView视图中, 调用LayoutManager#addViewInt方法。
如果发现ViewHolder的FLAG_RETURNED_FROM_SCRAP标志或isScrap,先unScrap处理,再调用ViewGroup的attachViewToParent方法。在滚动时,获取的isScrap是false。
借助ChildHelper的addView方法,调用CallBack的addView方法,最终,调用的是ViewGroup的addView,ItemView加入父容器,dispatchChildAttached方法,会触发Adapter的onViewAttachedToWindow方法。
ItemView帮助类,它通过内部Callback接口暴露出来,在RecyclerView类初始化ChildHelper时实现接口方法,调用RecyclerView的对应方法。处理子视图会借助父类ViewGroup。
任重而道远