处理回收复用相关工作的
参考博客:https://www.jianshu.com/p/9306b365da57
几个重要的集合
mAttachedScrap | 缓存显示到屏幕的item的holder。临时存放onLayout过程中的childern。 应用场景:RecyclerView在onLayout时会先把childern移除掉,在重新添加进去 |
mChangedScrap | 看名字应该跟 ViewHolder 的数据发生变化时有关吧 |
mCachedViews | 滑动中的回收和复用都先处理这个List,可以直接添加到view中,不需要重新onBindViewHolder。注意仅仅缓存的原来位置的item,其他位置的item无法使用。好像是 ViewPager 之类的缓存一样 |
mRecyclerPool | 存在这里的holder数据信息会被重置,需要重新调用onBindViewHolder来绑定 |
RecyclerView的绘制显示
最终继承于View,那么在View绘制的过程分三步:onMeasure测量view的大小,onLayout确定view的布局,onDraw将view绘制到界面上。其中重点在onLayout中。
1)LayoutManager的next()
根据debug从onLayout过程中对应的各个方法的调用过程,最终调用到对应的LayoutManager的next()。如图所示
那么进入到next()看看里面有些什么逻辑。
2)next()
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
从上面的代码中可以看到又调用recycler的getViewForPosition()方法,来获取对应position的view。通过跟进recycler的getViewForPosition()方法,最终进入到tryGetViewHolderForPositionByDeadline()返回可用的holder。
RecyclerView的滑动过程
根据debug发现最终也是调用到到tryGetViewHolderForPositionByDeadline()返回可用的holder。
下面重点介绍下缓存读取的过程
RecyclerView的复用机制中从缓存中读取holder
主要在tryGetViewHolderForPositionByDeadline()方法中实现。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ...... 省略代码
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// ...... 省略代码
// 1)从mAttachedScrap或者mCachedViews找到可用的holder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
// ...... //省略代码
}
}
if (holder == null) {
// ...... //省略代码
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 查找设置过stableId的holder,这个需要开发者去设置才生效
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// ...... //省略代码
}
}
3)查找开发者设置的缓存方式,这个一般只有开发者设置过才有效
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
//...... //省略代码
}
if (holder == null) { // fallback to pool
4)查找ViewPool中的缓存的holder
holder = getRecycledViewPool().getRecycledView(type);
//...... //省略代码
}
5)如果上述4种方式下来都没有可用holder,则调用mAdapter的createViewHolder来创建holder
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//...... //省略代码
}
}
//...... //省略代码
//设置holder的LayoutParams
//...... //省略代码
return holder;
}
在该方法里面涉及了RecyclerView的所有缓存的处理逻辑。
1)首先从mAttachedScrap或者mCachedViews找到可用的holder。
可以从getScrapOrHiddenOrCachedHolderForPosition()中可以看到这个里面其实就是从mAttachedScrap或者mCachedViews两个集合中找到可用的holder。
mAttachedScrap这个里面主要就是onLayout过程中临时存放的item的holder。mCachedViews中对应的就是之前滑出屏幕的item的holder。默认到为2,如果超出则将第0个移除到ViewPool中。注意这个只能是对应position上的holder才可以用。
从中取出的holder都会去验证可行性
2)若第一步中没有找到可用的holder,则继续向下执行,到getScrapOrCachedViewForId()。
这个方法里面主要就是开发者设置过stableId的,会重复去mAttachedScrap和mCachedViews查找可用的holder,注意这个stableId并不是xml设置的id,是Adapter持有一个属性,除非重写Adapter 的 setHasStableIds(),才会进行这一步的查找。
3)若2中还是没有找到可用的holder,则继续向下执行,执行到mViewCacheExtension!=null 的情况。
mViewCacheExtension是提供的自定义实现的类,需要开发者重写 getViewForPositionAndType() 方法来实现自己的复用策略,忽略该步。
4)若3中还是没有找到可用的holder,则进入到getRecycledViewPool().getRecycledView(type)。
这个是RecyclerView中的第四级缓存,RecycledViewPool根据不同的item type创建不同的list。在getRecycledView,在复用的时候,只要ViewPool中相同的type有holder缓存的话,就从最后一个拿出来复用。
这个集合里面的元素,只有缓存的holder超过mCachedViews设置的个数的时候,才会将holder加入到这里面来。注意加入到这里的holder由于调用holder.resetInternal();重置holder,所以从这里拿的holder需要重新onBindViewHolder。
7)通过上面的四步之后,发现若holder还是null,则调用mAdapter.createViewHolder重新创建holder
最后将holder设置LayoutParam,返回。
RecyclerView的回收机制
1、概述
先往mCacheScrap放2个,当超出2个之后,从mCacheScrap中拿出一个给到ViewPool等待复用。
2、代码解读
1)
拿LinearLayoutManager举例说明,在向下滑动的过程中,根据下面debug的方法跟踪,进入到recycleByLayoutState()
2)recycleByLayoutState()
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
这个里面的代码主要就是根据滑动是向上或者向下滑动来确定怎么回收,我们拿向上滑动举例说明下,进入到recycleViewsFromStart()中。
3)recycleViewsFromStart()
根据debug的日志可以看到,最终进入到recycler的recycleViewHolderInternal()进行回收holder
进入到recycleViewHolderInternal()进行查看是怎么回收的
3)recycleViewHolderInternal()
void recycleViewHolderInternal(ViewHolder holder) {
//......省略代码,省略代码
//取判断是否可以将holder放入到mCachedViews中
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
//超出mCachedViews设置的数量之后,将集合中的第一个元素取出,放到ViewPool中
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
//.......省略代码,判断是否可以放入到mCachedViews中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果仍没有被缓存,则加入到ViewPool中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
//NOTE: A view can fail to be recycled when it is scrolled off while an
// animation runs.
//.......省略代码
}
// even if the holder is not removed, we still call this method so that it is
// removed from view holder lists.
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
从代码中可以看出,首先会先去判断mCachedViews中的集合元素是否超过设置的最大值,若超过了,则将第一个元素移到ViewPool中;若没有超过最大元素,则放入到mCachedViews集合中等待复用。
总结
1)RecyclerView在缓存和回收的过程中,会遵循先复用后回收的原则。所以就有以下这种情况:
第一屏显示完之后,在加载第二屏的view的时候,再将第一屏的view移除屏幕的时候,第二屏的view会重新从onCreateHolder到onBinderView的过程,因为这个时候没有view缓存,所以无法取得缓存的holder,所以第二屏无法复用第一屏的数据。加载完第二屏的view之后,才会将第一屏的view添加到mCachedViews或ViewPool中。
09-25 17:16:55.966 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:16:55.968 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 0
09-25 17:16:55.970 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:16:55.971 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 1
09-25 17:16:55.972 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:16:55.975 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 2
09-25 17:16:55.976 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:16:55.979 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 3
09-25 17:16:55.980 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:16:55.983 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 4
09-25 17:17:27.745 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:17:27.750 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 5
09-25 17:17:27.828 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:17:27.832 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 6
09-25 17:17:27.839 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:17:27.841 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 7
09-25 17:17:27.924 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder ---
09-25 17:17:27.926 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 8
该例子中一屏含有5个item,在显示第二屏的item的时候,仍然会从onCreateViewHolder到onBindViewHolder
2)紧接着1继续滑动,发现明明显示了5个item,发现却只有3个走了onBindViewHolder
原理:当第一行在回收的时候,会先放入的mCacheView,满了再将旧的移到ViewPool中,所以5个卡位其中2个放到mCacheView,3个缓存到viewpool中,在ViewPool中的需要重新bind数据,而mCacheView直接可以复用。
至于是哪2个缓存在 mCachedViews,这是由 LayoutManager 控制