本文主要是介绍RecyclerView源码分析(二):RecyclerView的缓存与复用机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、开篇
在上一篇文章说到,LineaLayoutManager在填充布局之前会先调用detachAndScrapAttachedViews方法先暂时回收子View,然后在layoutChunk中进行子View的获取(可能会创建View)、测量、布局以及回收流程。实际上三大LayoutManager的流程都是一样的,只是细节上有所差异,感兴趣的同学可以阅读一下源码看看。本篇中涉及LayoutManager的部分依旧会以LinearLayoutManager为例。
回顾一下本系列要解决的问题:
- 既然是个ViewGroup,那少不了要问上一句:它的measure、layout和draw是怎么样的?
- RecyclerView是怎么回收View的?什么时候回收?
- 怎么支持多类型Item的?怎么缓存和查找的呢?
- Adapter的onRecreateViewHolder和onBindViewHolder两大核心方法是什么时候调用的?
- Item动画过程中notifyXXXChange会不会导致动画的错位?
问题1上篇文章已经解决了,而本篇要解决其中的234。
2、LayoutManager#detachAndScrapAttachedViews
detachAndScrapAttachedViews是基类LayoutManager定义的方法,如下所示:
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {final int childCount = getChildCount();for (int i = childCount - 1; i >= 0; i--) {final View v = getChildAt(i);scrapOrRecycleView(recycler, i, v);}
}private void scrapOrRecycleView(Recycler recycler, int index, View view) {final ViewHolder viewHolder = getChildViewHolderInt(view);// shouldIgnore()为true表示ViewHolder不应该被回收if (viewHolder.shouldIgnore()) {if (DEBUG) {Log.d(TAG, "ignoring view " + viewHolder);}return;}// isInvalid()为true表示Item的数据发生变化了(notifyDataSetChanged),需要重新绑定// isRemoved()为true表示Item被移除了// hasStableIds()为true表示每个Item会拥有一个唯一的long类型的id// 我们可以通过Adapter.setHasStableIds(true)并重写Adapter.getItemId(int)方法来指定每个Item的idif (viewHolder.isInvalid() && !viewHolder.isRemoved()&& !mRecyclerView.mAdapter.hasStableIds()) {removeViewAt(index);recycler.recycleViewHolderInternal(viewHolder);} else {detachViewAt(index);recycler.scrapView(view);mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);}
}
detachAndScrapAttachedViews遍历了子View,挨个调用了scrapOrRecycleView方法进行回收。scrapOrRecycleView方法对不同情况的View进行了不同的回收处理:
- viewHolder.shouldIgnore()为true:不回收
- 数据发生变化,但是Item没有被移除且没有指定ItemId
- 其他情况
2.1 数据发生变化,但是Item没有被移除且没有指定ItemId
不回收的情况就不用多说了,来看一下第二种情况。先看removeViewAt(index)方法
LayoutManager#removeViewAt:
public void removeViewAt(int index) {final View child = getChildAt(index);if (child != null) {mChildHelper.removeViewAt(index);}
}ChildHelper#removeViewAt:
void removeViewAt(int index) {final int offset = getOffset(index);final View view = mCallback.getChildAt(offset);if (view == null) {return;}if (mBucket.remove(offset)) {unhideViewInternal(view);}mCallback.removeViewAt(offset);if (DEBUG) {Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);}
}
可以看到,最终是调用了ChildHelper的mCallback.removeViewAt方法。这个mCallback是ChildHelper的构造方法传入的,它的实例化在RecyclerView#initChildrenHelper中:
private void initChildrenHelper() {mChildHelper = new ChildHelper(new ChildHelper.Callback() {...@Overridepublic void removeViewAt(int index) {final View child = RecyclerView.this.getChildAt(index);if (child != null) {dispatchChildDetached(child);// Clear any android.view.animation.Animation that may prevent the item from// detaching when being removed. If a child is re-added before the// lazy detach occurs, it will receive invalid attach/detach sequencing.// 清除View的动画child.clearAnimation();}RecyclerView.this.removeViewAt(index);}...});
}
所以在这种情况下,子View真的从RecyclerView中移除了。那么再来看Recycler的recycleViewHolderInternal方法对这个View已经被移除的ViewHolder做了些什么事情
void recycleViewHolderInternal(ViewHolder holder) {// 对holder进行各种状态检查,这段代码省略...// 强制回收判断// transientStatePreventsRecycling如果是true代表View处于某种临时状态(执行动画等)不能被回收// 这时候可以通过重写Adapter.onFailedToRecycleView方法来解除这个临时状态并返回true以达到可以被回收的目的final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();@SuppressWarnings("unchecked")final boolean forceRecycle = mAdapter != null&& transientStatePreventsRecycling&& mAdapter.onFailedToRecycleView(holder);boolean cached = false;boolean recycled = false;if (DEBUG && mCachedViews.contains(holder)) {throw new IllegalArgumentException("cached view received recycle internal? "+ holder + exceptionLabel());}if (forceRecycle || holder.isRecyclable()) { // 强制回收或者holder是可回收的if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // 注意这里的flag,没有这些flag的才会走接下来的回收流程// 从这里可以看出mViewCacheMax是mCachedViews的存储数量上限// 如果达到了这个上限,先移除最老的int cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {recycleCachedViewAt(0);cachedViewSize--;}int targetCacheIndex = cachedViewSize;... // 省略targetCacheIndex计算逻辑mCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {addViewHolderToRecycledViewPool(holder, true);recycled = true;}} else {// 注意:滚动时,如果View在执行动画的过程中可能会回收失败// 这种情况下,ItemAnimatorRestoreListener#onAnimationFinished回调中回收此View// 请考虑取消滚动出去时取消View的动画,以使View更快地被回收到回收池if (DEBUG) {Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "+ "re-visit here. We are still removing it from animation lists"+ exceptionLabel());}}// even if the holder is not removed, we still call this method so that it is removed// from view holder lists.// 即使holder没有被移除/回收,也从list中先行移除mViewInfoStore.removeViewHolder(holder);if (!cached && !recycled && transientStatePreventsRecycling) {holder.mOwnerRecyclerView = null;}
}void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {...if (dispatchRecycled) {dispatchViewRecycled(holder);}holder.mOwnerRecyclerView = null;getRecycledViewPool().putRecycledView(holder);
}
这里可以看出,mViewCacheMax<=0或者有以下几个flag之一的情况是不会进行holder的回收的
ViewHolder.FLAG_INVALID
ViewHolder.FLAG_REMOVED
ViewHolder.FLAG_UPDATE
ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
回收的时候,会优先回收到mCachedViews中,mCachedViews是一个ViewHolder的ArrayList。而RecycledViewPool是一个比较简单的对象。注意,我们当前情况是有FLAG_INVALID这个flag的,所以就当前情况是不会回收到mCachedViews中的。
public static class RecycledViewPool {private static final int DEFAULT_MAX_SCRAP = 5;static class ScrapData {final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();int mMaxScrap = DEFAULT_MAX_SCRAP;long mCreateRunningAverageNs = 0;long mBindRunningAverageNs = 0;}SparseArray<ScrapData> mScrap = new SparseArray<>();public ViewHolder getRecycledView(int viewType) {final ScrapData scrapData = mScrap.get(viewType);if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;for (int i = scrapHeap.size() - 1; i >= 0; i--) {if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {return scrapHeap.remove(i);}}}return null;}...public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {return;}if (DEBUG && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");}// 重置了scrap.resetInternal();scrapHeap.add(scrap);}...
}
其中ScrapData简单封装了一个ArrayList,而RecycledViewPool封装了一个SparseArray,key是viewType,value是ScrapData。也就是说,在RecyclerViewPool里面,ViewHolder是按照viewType分门别类地存储的。
2.2 其他情况
其他情况调用如下:
LayoutManager#scrapOrRecycleView:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {...else {detachViewAt(index);recycler.scrapView(view);mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);}
}
先看LayoutManager的detachViewAt
public void detachViewAt(int index) {detachViewInternal(index, getChildAt(index));
}private void detachViewInternal(int index, @NonNull View view) {if (DISPATCH_TEMP_DETACH) {ViewCompat.dispatchStartTemporaryDetach(view);}mChildHelper.detachViewFromParent(index);
}
mChildHelper最终其实也是调用了mCallback的相关回调
private void initChildrenHelper() {mChildHelper = new ChildHelper(new ChildHelper.Callback() {...@Overridepublic void detachViewFromParent(int offset) {final View view = getChildAt(offset);if (view != null) {final ViewHolder vh = getChildViewHolderInt(view);if (vh != null) {...// 加一个flag: FLAG_TMP_DETACHEDvh.addFlags(ViewHolder.FLAG_TMP_DETACHED);}}RecyclerView.this.detachViewFromParent(offset);}...});
}
最终调用了RecyclerView的detachViewFromParent。
再看Recycler的scrapView
void scrapView(View view) {final ViewHolder holder = getChildViewHolderInt(view);if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {...holder.setScrapContainer(this, false);mAttachedScrap.add(holder);} else {if (mChangedScrap == null) {mChangedScrap = new ArrayList<ViewHolder>();}holder.setScrapContainer(this, true);mChangedScrap.add(holder);}
}boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,viewHolder.getUnmodifiedPayloads());
}
被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中。这两个变量都是存放ViewHolder的ArrayList。
2.4 总结
现在来总结一下LayoutManager#detachAndScrapAttachedViews中的主要工作:
- 如果ViewHolder.shouldIgnore()返回true,忽略
- 如果ViewHolder.isInvalid()返回true且没有被移除也没有有效的id的时候,优先考虑缓存到mCachedViews里,mCachedViews达到设定的上限后,老的ViewHolder缓存到RecycledViewPool
- 其余情况,被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中
3、LayoutState#next
看一下LinearLayoutManager.LayoutState的next方法
View next(RecyclerView.Recycler recycler) {if (mScrapList != null) {return nextViewFromScrapList();}final View view = recycler.getViewForPosition(mCurrentPosition);mCurrentPosition += mItemDirection;return view;
}
mScrapList是在layoutForPredictiveAnimations中被赋值的,由于layoutForPredictiveAnimations的执行在fill之后,这里我们姑且把mScrapList当作null,尽管它不一定总是null,主要看怎么从recycler中取出对应的View的。
Recycler中的相关方法:
public View getViewForPosition(int position) {return getViewForPosition(position, false);
}View getViewForPosition(int position, boolean dryRun) {return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {if (position < 0 || position >= mState.getItemCount()) {throw new IndexOutOfBoundsException("Invalid item position " + position+ "(" + position + "). Item count:" + mState.getItemCount()+ exceptionLabel());}boolean fromScrapOrHiddenOrCache = false;ViewHolder holder = null;// 0) If there is a changed scrap, try to find from there// 预布局的时候从mChangedScrap中查找ViewHolderif (mState.isPreLayout()) { // 预布局holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;}// 1) Find by position from scrap/hidden list/cache// 1)先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找if (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// 不能复用了,回收这个holderif (!dryRun) {// we would like to recycle this but need to make sure it is not used by// animation logic etc.holder.addFlags(ViewHolder.FLAG_INVALID);if (holder.isScrap()) {removeDetachedView(holder.itemView, false);holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}holder = null;} else {fromScrapOrHiddenOrCache = true;}}}if (holder == null) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "+ "position " + position + "(offset:" + offsetPosition + ")."+ "state:" + mState.getItemCount() + exceptionLabel());}final int type = mAdapter.getItemViewType(offsetPosition);// 2) 在通过id查找mAttachedScrap、mChildHeppler的hidden列表和mCachedViewsif (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because LayoutManager does not// know it.// 从mViewCacheExtension查找final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);if (holder == null) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view which does not have a ViewHolder"+ exceptionLabel());} else if (holder.shouldIgnore()) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view that is ignored. You must call stopIgnoring before"+ " returning this view." + exceptionLabel());}}}// 最后才从RecycledViewPool中查找if (holder == null) { // fallback to poolif (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline("+ position + ") fetching from shared pool");}holder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}// 经过n此查找依然没有找到合适的ViewHolder,才通过Adapter创建新的ViewHolderif (holder == null) {long start = getNanoTime();if (deadlineNs != FOREVER_NS&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {// abort - we have a deadline we can't meetreturn null;}holder = mAdapter.createViewHolder(RecyclerView.this, type);if (ALLOW_THREAD_GAP_WORK) {// only bother finding nested RV if prefetching// 这里对嵌套的RecyclerView做了保存RecyclerView innerView = findNestedRecyclerView(holder.itemView);if (innerView != null) {holder.mNestedRecyclerView = new WeakReference<>(innerView);}}long end = getNanoTime();mRecyclerPool.factorInCreateTime(type, end - start);if (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");}}}// This is very ugly but the only place we can grab this information// before the View is rebound and returned to the LayoutManager for post layout ops.// We don't need this in pre-layout since the VH is not updated by the LM.if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);if (mState.mRunSimpleAnimations) {int changeFlags = ItemAnimator.buildAdapterChangeFlagsForAnimations(holder);changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,holder, changeFlags, holder.getUnmodifiedPayloads());recordAnimationInfoIfBouncedHiddenView(holder, info);}}boolean bound = false;if (mState.isPreLayout() && holder.isBound()) {// do not update unless we absolutely have to.holder.mPreLayoutPosition = position;} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {if (DEBUG && holder.isRemoved()) {throw new IllegalStateException("Removed holder should be bound and it should"+ " come here only in pre-layout. Holder: " + holder+ exceptionLabel());}final int offsetPosition = mAdapterHelper.findPositionOffset(position);// 尝试绑定ViewHolderbound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);}final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();final LayoutParams rvLayoutParams;if (lp == null) {rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();holder.itemView.setLayoutParams(rvLayoutParams);} else if (!checkLayoutParams(lp)) {rvLayoutParams = (LayoutParams) generateLayoutParams(lp);holder.itemView.setLayoutParams(rvLayoutParams);} else {rvLayoutParams = (LayoutParams) lp;}// 这里itemView通过持有LayoutParams间接持有了ViewHolder// 这样我们可以通过itemView找到对应的ViewHolder,也可以通过ViewHolder找到itemViewrvLayoutParams.mViewHolder = holder;rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;return holder;
}ViewHolder getChangedScrapViewForPosition(int position) {// If pre-layout, check the changed scrap for an exact match.final int changedScrapSize;if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {return null;}// 先通过position查找for (int i = 0; i < changedScrapSize; i++) {final ViewHolder holder = mChangedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}// 通过id查找if (mAdapter.hasStableIds()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {final long id = mAdapter.getItemId(offsetPosition);for (int i = 0; i < changedScrapSize; i++) {final ViewHolder holder = mChangedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}}}return null;
}ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {final int scrapCount = mAttachedScrap.size();// Try first for an exact, non-invalid match from scrap.// 先查找mAttachedScrapfor (int i = 0; i < scrapCount; i++) {final ViewHolder holder = mAttachedScrap.get(i);if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {// 标记FLAG_RETURNED_FROM_SCRAPholder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);return holder;}}if (!dryRun) { // 根绝上面的代码,dryRun为false// 这些被标记为hidden的View存储在ChildHelper中的mHiddenViews里// 这些View所谓的hidden并不是指View的visibility,而是它们不能被某些常规方法访问// 这里我们不深究View view = mChildHelper.findHiddenNonRemovedView(position);if (view != null) {// This View is good to be used. We just need to unhide, detach and move to the// scrap list.final ViewHolder vh = getChildViewHolderInt(view);mChildHelper.unhide(view);int layoutIndex = mChildHelper.indexOfChild(view);if (layoutIndex == RecyclerView.NO_POSITION) {throw new IllegalStateException("layout index should not be -1 after "+ "unhiding a view:" + vh + exceptionLabel());}mChildHelper.detachViewFromParent(layoutIndex);scrapView(view);// 标记FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LISTvh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);return vh;}}// 从mCachedViews里查找final int cacheSize = mCachedViews.size();for (int i = 0; i < cacheSize; i++) {final ViewHolder holder = mCachedViews.get(i);// invalid view holders may be in cache if adapter has stable ids as they can be// retrieved via getScrapOrCachedViewForIdif (!holder.isInvalid() && holder.getLayoutPosition() == position&& !holder.isAttachedToTransitionOverlay()) {if (!dryRun) {mCachedViews.remove(i);}if (DEBUG) {Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position+ ") found match in cache: " + holder);}return holder;}}return null;
}ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {// 依然是先查找mAttachedScrapfinal int count = mAttachedScrap.size();for (int i = count - 1; i >= 0; i--) {final ViewHolder holder = mAttachedScrap.get(i);if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {if (type == holder.getItemViewType()) {holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);if (holder.isRemoved()) {// this might be valid in two cases:// > item is removed but we are in pre-layout pass// >> do nothing. return as is. make sure we don't rebind// > item is removed then added to another position and we are in// post layout.// >> remove removed and invalid flags, add update flag to rebind// because item was invisible to us and we don't know what happened in// between.// 这里可能会是以下两种情况之一:// item被移除了,但是当前是预布局// item被移除后又被添加到了其他的位置if (!mState.isPreLayout()) {holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);}}return holder;} else if (!dryRun) {// if we are running animations, it is actually better to keep it in scrap// but this would force layout manager to lay it out which would be bad.// Recycle this scrap. Type mismatch.// 如果当前正在执行动画,保留在mAttachedScrap更好,但是这会导致LayoutManager会对它进行布局,这又不太好// 回收掉,因为viewType变了mAttachedScrap.remove(i);removeDetachedView(holder.itemView, false);quickRecycleScrapView(holder.itemView);}}}// 从mCachedViews查找final int cacheSize = mCachedViews.size();for (int i = cacheSize - 1; i >= 0; i--) {final ViewHolder holder = mCachedViews.get(i);if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {if (type == holder.getItemViewType()) {if (!dryRun) {mCachedViews.remove(i);}return holder;} else if (!dryRun) {recycleCachedViewAt(i);return null;}}}return null;
}private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,int position, long deadlineNs) {holder.mOwnerRecyclerView = RecyclerView.this;final int viewType = holder.getItemViewType();long startBindNs = getNanoTime();if (deadlineNs != FOREVER_NS&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {// abort - we have a deadline we can't meetreturn false;}// 绑定ViewHoldermAdapter.bindViewHolder(holder, offsetPosition);long endBindNs = getNanoTime();mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);attachAccessibilityDelegateOnBind(holder);if (mState.isPreLayout()) {holder.mPreLayoutPosition = position;}return true;
}
这段代码比较长,希望看到这里的同学耐心看完。总结一下要点:
- 预布局的时候,优先查找mChangedScrap,先position后id
- 先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找ViewHolder
- 通过id从mAttachedScrap和mCachedViews里查找ViewHolder
- 从mViewCacheExtension查找ViewHolder
- 通过type从RecycledViewPool中查找ViewHolder
- 如果上述步骤都没有找到合适的ViewHolder,同过Adapter创建一个ViewHolder
- 根据需要绑定ViewHolder,只有needsUpdate()或者isInvalid()为true的时候才会执行ViewHolder的绑定,出现的情况有:Adapter的notifyItemChanged/notifyRangeChanged、notifyDatasetChanged相关方法以及新建ViewHolder或者从RecycledViewPool中取出ViewHolder
4、ViewHolder的回收
ViewHolder的回收(或者说缓存)有好几个地方,第一个就是上面分析的填充之前的LayoutManager#detachAndScrapAttachedViews,第二第三次分别在LinearLayoutManager#fill和RecyclerView#dispatchLayoutStep3中
先来看LinearLayoutManager#fill:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + availablefinal int start = layoutState.mAvailable;if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {// TODO ugly bug fix. should not happenif (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;LayoutChunkResult layoutChunkResult = mLayoutChunkResult;while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {...layoutChunk(recycler, state, layoutState, layoutChunkResult);...if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}if (DEBUG) {validateChildOrder();}return start - layoutState.mAvailable;
}
这里有两段相似的代码,一段位于开头,一段位于while循环里,根据注释,开头这一段是“丑陋的bug修复代码”,所以主要发挥作用的应该是后者。它们都是调用了recycleByLayoutState(recycler, layoutState)来回收超出限制范围的View
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {if (!layoutState.mRecycle || layoutState.mInfinite) {return;}int scrollingOffset = layoutState.mScrollingOffset;int noRecycleSpace = layoutState.mNoRecycleSpace;if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);} else {recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);}
}
从布局方向的反方向回收,这里最终经过层层嵌套还是调用了Recycler#recycleViewHolderInternal,这个方法前面已经分析过了就不再赘述了。
再来看RecyclerView#dispatchLayoutStep3
private void dispatchLayoutStep3() {mState.assertLayoutStep(State.STEP_ANIMATIONS);startInterceptRequestLayout();onEnterLayoutOrScroll();mState.mLayoutStep = State.STEP_START;...mLayout.removeAndRecycleScrapInt(mRecycler);...
}
这里调用了LayoutManager的removeAndRecycleScrapInt方法
void removeAndRecycleScrapInt(Recycler recycler) {final int scrapCount = recycler.getScrapCount();// Loop backward, recycler might be changed by removeDetachedView()for (int i = scrapCount - 1; i >= 0; i--) {final View scrap = recycler.getScrapViewAt(i);final ViewHolder vh = getChildViewHolderInt(scrap);if (vh.shouldIgnore()) {continue;}// If the scrap view is animating, we need to cancel them first. If we cancel it// here, ItemAnimator callback may recycle it which will cause double recycling.// To avoid this, we mark it as not recycleable before calling the item animator.// Since removeDetachedView calls a user API, a common mistake (ending animations on// the view) may recycle it too, so we guard it before we call user APIs.vh.setIsRecyclable(false);if (vh.isTmpDetached()) {mRecyclerView.removeDetachedView(scrap, false);}if (mRecyclerView.mItemAnimator != null) {mRecyclerView.mItemAnimator.endAnimation(vh);}vh.setIsRecyclable(true);// 最终也是调用recycleViewHolderInternal(holder)recycler.quickRecycleScrapView(scrap);}recycler.clearScrap();if (scrapCount > 0) {mRecyclerView.invalidate();}
}
这里遍历了Recycler的mAttachedScrap,挨个回收,随后清空了mAttachedScrap和mChangedScrap,确保RecyclerView中没有多余的View。
5、总结
RecyclerView的缓存复用类Recycler有四级缓存机制:
- mAttachedScrap和mChangedScrap,用于临时存储可能还会停留在RecyclerView中的ViewHolder,在dispatchLayoutStep3中多余的ViewHolder会被缓存到mCachedViews或者RecycledViewPool
- mCachedViews,缓存上限是mViewCacheMax,默认值为2,缓存最后被回收的ViewHolder
- mViewCacheExtension,开发者自定义的缓存,Recycler本身并不会往里面put数据,只检索,其他逻辑由开发者实现
- RecycledViewPool,按照分类存储ViewHolder,进入这里的ViewHolder的position等都被重置了
另外,开头的234问题,到此也有了答案~
这篇关于RecyclerView源码分析(二):RecyclerView的缓存与复用机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!