RecyclerView基本结构
RecyclerView:列表容器
LayoutManager:布局管理器
Recycler:管理列表项的创建、回收、复用
Adapter:用于列表项的创建与数据绑定
ViewHodler:RecyclerView缓存的基本单元,持有ItemView
缓存结构
RecyclerView有四级缓存,相关字段都放在Recycler这个RecyclerView的内部类里,这里按照缓存的查找顺序从左到右排列
ScrapView
Scrap分为mAttachedScrap与mChangedScrap,是仅在布局阶段中使用到的缓存列表,无数量限制,每一次布局是都会将屏幕上的holder放入scrap中,布局完后将这个列表的ViewHolder清空,没有复用的holder将放入到RecyclerViewPool中,Scrap中的View出于detach状态,但是还没有被RecyclerView remove掉。
mAttachedScrap:数据不合法(调用notify)或者数据未变更的holder,后者可以直接复用,而无需绑定数据
mChangedScrap:数据发生变更的ViewHolder,例如调用了notifyItemChanged的holder。
CachedViews
在全生命周期流程中使用到,其中保存了刚刚滑出屏幕的ViewHolder,这些holder在滑回来时可以直接使用而不需要重新绑定数据,默认缓存数量是2个,在GapWorker机制下可以有更多个。
ViewCacheExtension
在全生命周期流程中使用到,无默认实现,可外部注入,只能用来提供ViewHolder,无存入逻辑,基本没有使用场景,可考虑用于提供预加载的View。
less
public abstract static class ViewCacheExtension {
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);
}
RecyclerViewPool
在全生命周期流程中使用到,按照类型去缓存ViewHoder,每种类型默认缓存5个,可动态设置,超出缓存数量后多余的ViewHolder会被丢弃。支持多个RecyclerView设置同一个pool,做到跨场景的复用。
相关机制
StableId
用于给每一个Item分配一个独立的id,它所带来的影响如下:
当代码中调用了notifyDataSetChange时,没有设置StableId的情况下,由于RecyclerView不知道哪些holder是有效的,所以所有的ViewHolder都会先被回收到pool中,再从pool里复用。 而当设置了StableId后,RecyclerView知道哪些item是还在的,所以它会先将所有的holder放入到scrap列表中,再从scrap里进行复用,剩余没复用的才会存放到pool里。这样虽能减少复用流程但是也有个坑,在设置stableId之前,item是可以复用到pool里的holder,而设置了stableId之后,由于这些holder不会回收到pool,而pool里又没有对应类型的holder时,将会通过adapter创建新的holder,导致性能下降。
GapWorker
RecyclerView的一个预加载机制,仅在滑动场景下生效(拖拽、惯性、嵌套滑动),它会在主线程空闲期间,根据滑动方向和距离去预先准备下一个item(包含创建与绑定数据),当产生滑动后,它首先去计算下一帧到来的时间,然后判断下一帧到来之前是否能将ViewHolder创建好,如果可以它就会去创建ViewHolder,否则不创建。
创建好的Holder会被缓存到mCachedView里,并且会扩大cacheView的size,不会挤出调正常滑动产生的cacheView。
离屏回收
typescript
#LinearLayoutManager.java
public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
mRecycleChildrenOnDetach = recycleChildrenOnDetach;
}
字面意思就是当recyclerView从父容器脱离后,回收所有的view到RecyclerViewPool中,如果此RecyclerView与其他RecyclerView共享一个pool,那就可以很好的提供holder给其他场景的RecyclerView了,但是这样也有概率产生bug,一个RecyclerView在detach的时候回收了所有的item,但是重新attach的时候却没有恢复item(原因未知)。导致界面空白。
完整复用流程
1、如果是预布局(动画相关)则从mChangedScrap中查找holder,只需要position对上即可。
2、从mAattchedScrap、mCachedViews中查找holder,需要position对上且数据有效才能返回(调用notifyxxx则会导致数据失效)。
3、如果有设置stableId,则从第2步的缓存列表中查找holdler,与第2步不同的是,不再用position去匹配holder了,而是通过id,同时也不再判断数据是否有效。
4、从ViewCacheExtension中获取holder(忽略)。
5、从RecyclerViewPool中获取holder。
6、通过adapter创建holder。
7、如果有需要,则绑定holder的数据。(如调了notifyxxx的holder,即便有设置stableId并且id一致也会调用。)