RecyclerView回收复用机制浅析
RecyclerView 基本上已经成为了开发中常用的一个组件,通过其提供的强大能力,能实现各种需要的列表类效果。灵活的同时,要用好却也不容易,为了高效实现需求,避免掉到各种不明所以的坑里面,这里有必要对其回收复用机制做一个探究。
本文将带着下面这几个方面的问题来探究。tips:结合源码食用更佳。
回收复用机制到底回收复用的是啥
回收复用的单元是Viewholder,我们记得,在使用 ListView 的时候,也用到了 ViewHolder,这两者其实是有区别的。
ListView 中使用的 ViewHolder, 仅仅是对 view 的引用,只记录了 view 的实例。
RecylerView 中使用的 ViewHolder, 记录的东西,就非常多了。源码注释中是这么写的:A ViewHolder describes an item view and metadata about its place within the RecyclerView。 查看其实现,发现记录了 itemview,view 的当前 position,以前的 position,itemId, itemViewType, 当前的状态 flag 等等,itemview 的状态有(绑定|移除|更新|有效|不可回收|报废返回|暂时分离|移动|隐藏|等等),
这些组合信息,可以把每一个 itemview 布局状态区分开来,这正是 RecyclerView 具有局部刷新能力而 ListView 只能全量更新的原因。
谁负责回收复用机制
这里也首先从源码入手,结合源码的注释进行学习。
通过源码发现,Recycler 是负责管理废弃的(scrapped)分离的(detached) item进行的复用的。这里解释一下, 所谓废弃的 view,指的是 view 仍然
附加在父 RecyclerView 上, 但是被标记为要删除或者重新绑定或复用的。
Recycler回收复用的能力,可以通过观察其成员变量来理解:
-
ArrayList<ViewHolder> mAttachedScrap 和 mChangedScrap
还绑定在父 RecyclerView 上的 view 。 当视图滑出屏幕外了,需要在滑入屏幕的地方绑定 view 的时候,就会触发报废 view 的机制;调用数据更新接口也能触发 RecyclerView 重新排列,会对绑定的数据先报废分离,重新绑定。这个时候,也触发了报废 view 的机制,mChangedScrap 中的change指的是item被标记为更新、有动画且动画支持变化(即实现了animateChange方法), 这种情况下, ViewHolders 被添加到 mChangedScrap 中, 其他时候报废的 ViewHolders 会添加到 mAttachedScrap。
-
ArrayList<ViewHolder> mCachedViews
缓存滑出屏幕的 ViewHolder。
ViewHolder 被回收的时候,如果已经缓存的 ViewHolder 数量小于最大缓存值(默认为2,可以通过 setItemViewCacheSize() 来修改最大缓存数量),会优先放到 mCachedViews 中。 -
RecycledViewPool mRecyclerPool
RecyclerViewPool可以在不同的 RecyclerView 里面复用 ViewHolder;如果没有为 RecyclerView 设置 RecyclerViewPool(通过 setRecycledViewPool 方法), 默认会自己设置一个。这里简单分析一些RecyclerViewPool的实现:RecyclerViewPool内部持有一个二维的mScrap变量, mScrap 的 key 为 ViewHolder 的 type, value 为一个列表, 表示的是同一 type 的 ViewHolder 实例集合。内部的其他方法为外部提供回收和复用的接口。
ViewCacheExtension mViewCacheExtension
ViewCacheExtension 是一个抽象类, 其只包含
abstract public View getViewForPositionAndType(Recycler recycler, int position, int type)
这个抽象方法, 该方法为开发者自定义复用机制提供接口,由开发者自行决定 ViewHolder 缓存的实现,并根据 viewtype 和 adapter position 返回 view 给 RecyclerView。
回收复用的机制是怎样的
要了解复用的机制是什么, 着重学习 Recycler 的 getViewForPosition(int position, boolean dryRun) 的方法。下图是其流程图。
st=>start: Start
op1=>operation: 从 changedScrap 中寻找
op2=>operation: 从 attachedScrap 中寻找
op3=>operation: 从 mViewCacheExtension 中寻找
op4=>operation: 从 RecyclerViewPool 中寻找
op5=>operation: 调用 creatViewholder
cond1=>condition: 没找到
cond2=>condition: 没找到
cond3=>condition: 没找到
cond4=>condition: 没找到
o1=>inputoutput: 状态处理
e=>end: End
st->op1->cond1
cond1(yes)->op2->cond2
cond2(yes)->op3->cond3
cond3(yes)->op4->cond4
cond4(yes)->op5->o1->e
cond1(no)->o1
cond2(no)->o1
cond3(no)->o1
cond4(no)->o1
基于这样的回收复用机制, 使用的时候要注意哪些事情
- RecyclerView 的 item 只要视觉上看起来不一样, 都要重写 adapter 的 getItemViewType 方法。为啥呢?因为回收的 ViewHolder 都是放在 一个 以 viewtype 为 Key 的 List 里面, 如果不重写的话, 拿到的 ViewHolder 由于缓存的 存在, 可能 “残留” 其他视图的元素或属性。
- 可以用局部刷新的就用局部刷新。
- mCachedViews默认缓存2个,RecyclerPool默认缓存5个。在同一type可见item比较多且设备性能不错的时候(考虑到屏幕大小一定,交互美感等因素,也不可能无限多),可以适当增大默认缓存值。