RecyclerView源码分析之二 滚动时候的ViewHolder的回收和复用

源码版本:androidx1.3.2

分析场景:

RecyclerView 使用线性布局,方向为竖直方向,布局从上到下。第一次设置 LayoutManager 和 Adapter ,正常显示数据以后,滚动 RecyclerView。

先说下结论:

  1. 在正常滚动过程中,RecyclerView ViewHolder 的回收和复用只涉及 Recycler.mCachedViews、Recycler.mRecyclerPool。
  2. RecyclerView会回收滑出屏幕的View,当回收一个ViewHolder A 的时候,如果如果mCachedView缓存已达上限(默认是2),会调用 addViewHolderToRecycledViewPool 从mCachedViews中移除最老的ViewHolder,添加到RecyclerViewPool中(每种ItemViewType的缓存池大小默认是5)。然后把A加入到Recycler.mCachedViews。
  3. 从 Recycler.mCachedViews 返回的 ViewHolder 是不会重新 onBindViewHolder 的。
  4. 从 RecyclerViewPool 返回的 ViewHolder 是会重新 onBindViewHolder 的。

其他注意的点: 在调试过程中发现,GapWorker 的 prefetchPositionWithDeadline 方法干扰到 RecyclerView 的回收和复用过程。有时候会导致 导致 Recycler.mCachedViews的 size变成了3(默认是2)。这个不是主要流程,我们先忽略,有兴趣可以仔细研究研究。

RecyclerView的缓存的 ViewHolder 的地方有 Recycler.mAttachedScrap、Recycler.mChangedScrap、Recycler.mCachedViews、Recycler.mRecyclerPool。
缓存 View 的地方有 Recycler.mViewCacheExtension、ChildHelper.mHiddenViews。 如下所示:

public final class Recycler {final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();ArrayList<ViewHolder> mChangedScrap = null;final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();private final List<ViewHolder>mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);RecycledViewPool mRecyclerPool;private ViewCacheExtension mViewCacheExtension;//...
}
class ChildHelper {private final List<View> mHiddenViews = new ArrayList<View>();//...
}

本篇文章只分析在滚动的时候的回收和复用。只涉及 Recycler.mCachedViews、Recycler.mRecyclerPool 。在后续的文章中,会进行一个总结说明每一个缓存的作用。

在滚动时候的回收

在RecyclerView滚动的时候,会调用 LinearLayoutManager 的fill方法。fill方法内部会发生View的回收和复用。

/*** @param recycler        当前关联到RecyclerView的recycler。* @param layoutState     该如何填充可用空间的配置信息。* @param state           Context passed by the RecyclerView to control scroll steps.* @param stopOnFocusable 如果为true的话,遇到第一个可获取焦点的View则停止填充。* @return 返回添加的像素。*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + availablefinal int start = layoutState.mAvailable;//...//注释0处,如果layoutState.mScrollingOffset不为SCROLLING_OFFSET_NaN的话,调用recycleByLayoutState方法,从这个方法名,我们可以看出来,这是一个回收View的方法。if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {//...recycleByLayoutState(recycler, layoutState);}int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;LayoutChunkResult layoutChunkResult = mLayoutChunkResult;while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();//注释1处,获取并添加子View,然后测量、布局子View并将分割线考虑在内。layoutChunk(recycler, state, layoutState, layoutChunkResult);//条件满足的话,跳出循环if (layoutChunkResult.mFinished) {break;}layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;//注释2处,这里也会判断是否要调用recycleByLayoutState方法。//在滚动的时候,会把滑出屏幕的View回收。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;}}return start - layoutState.mAvailable;
}

注释0处和注释2处,调用 recycleByLayoutState 方法,从这个方法名可以看出来,这是一个回收View的方法。接下来我们看看其中的细节。

LinearLayoutManager 的 recycleByLayoutState 方法。

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) {//注释1处recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);} else {//注释2处recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);}
}

注释1处,以默认竖直方向的LinearLayoutManager来说,就是手指从上向下滑动的时候,回收从下面滑出屏幕的View。

注释2处,以默认竖直方向的LinearLayoutManager来说,就是手指从下向上滑动的时候,回收从上面滑出屏幕的View。

LinearLayoutManager的recycleViewsFromEnd方法。

private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace) {final int childCount = getChildCount();if (scrollingOffset < 0) {return;}final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;if (mShouldReverseLayout) {//...} else {//从后向前遍历for (int i = childCount - 1; i >= 0; i--) {View child = getChildAt(i);if (mOrientationHelper.getDecoratedStart(child) < limit|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {//注释1处,遇到第一个View的top坐标小于limit就停止。然后调用recycleChildren方法,回收从childCount - 1到i之间的所有View。recycleChildren(recycler, childCount - 1, i);return;}}}
}

注释1处,遇到第一个View的top坐标小于limit就停止。然后调用recycleChildren方法,回收从 [childCount - 1,i) 左闭右开之间的所有View。

LinearLayoutManager的recycleViewsFromStart方法。

private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace) {if (scrollingOffset < 0) {return;}// ignore padding, ViewGroup may not clip children.final int limit = scrollingOffset - noRecycleSpace;final int childCount = getChildCount();if (mShouldReverseLayout) {//...} else {for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {//注释1处,遇到第一个View的bottom坐标大于limit就停止。然后调用recycleChildren方法,回收从0到i之间的所有View。recycleChildren(recycler, 0, i);return;}}}
}

注释1处,遇到第一个View的bottom坐标大于limit就停止。然后调用recycleChildren方法,回收从 [0,i) 左闭右开之间的所有View。

接下来我们看看recycleChildren方法。

LinearLayoutManager的recycleChildren方法。

/*** 回收指定索引之间的子View。** @param startIndex 包括* @param endIndex   不包括*/
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {//startIndex == endIndex的话,直接return。比如滚动距离很短,没有View需要回收。if (startIndex == endIndex) {return;}if (endIndex > startIndex) {for (int i = endIndex - 1; i >= startIndex; i--) {removeAndRecycleViewAt(i, recycler);}} else {for (int i = startIndex; i > endIndex; i--) {removeAndRecycleViewAt(i, recycler);}}
}

调用父类RecyclerView.LayoutManager的removeAndRecycleViewAt方法。

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {final View view = getChildAt(index);//注释1处,调用RecyclerView.LayoutManager的removeViewAt方法移除View。removeViewAt(index);//注释2处,回收View。recycler.recycleView(view);
}

注释1处,调用RecyclerView.LayoutManager的removeViewAt方法移除View。 最终会调用 RecyclerView.this.removeViewAt(index);

注释2处,回收View。

Recycler的recycleView方法。

public void recycleView(@NonNull View view) {// This public recycle method tries to make view recycle-able since layout manager// intended to recycle this view (e.g. even if it is in scrap or change cache)//获取ViewHolderViewHolder holder = getChildViewHolderInt(view);if (holder.isTmpDetached()) {removeDetachedView(view, false);}if (holder.isScrap()) {//注释1处,将ViewHolder从mChangedScrap或者mAttachedScrap中移除。holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}//注释2处,调用recycleViewHolderInternal方法。recycleViewHolderInternal(holder);if (mItemAnimator != null && !holder.isRecyclable()) {mItemAnimator.endAnimation(holder);}
}

注释1处,如果ViewHolder是从 mChangedScrap中或者mAttachedScrap 返回的,则将ViewHolder从mChangedScrap或者mAttachedScrap中移除。也就是说滑出屏幕的ViewHolder不会缓存在mChangedScrap中或者mAttachedScrap中。
对于我们分析的这个场景,mChangedScrap中或者mAttachedScrap 是没有 ViewHolder 的。条件不满足。

注释2处,调用 Recycler 的recycleViewHolderInternal方法。

void recycleViewHolderInternal(ViewHolder holder) {//这里判断了在mChangedScrap中或者mAttachedScrap中的ViewHolder不会被回收,没有被移除的子View对应的ViewHolder也不能被回收。if (holder.isScrap() || holder.itemView.getParent() != null) {throw new IllegalArgumentException("Scrapped or attached views may not be recycled. isScrap:"+ holder.isScrap() + " isAttached:"+ (holder.itemView.getParent() != null) + exceptionLabel());}//临时从屏幕上detach的ViewHolder也不能被回收。if (holder.isTmpDetached()) {throw new IllegalArgumentException("Tmp detached view should be removed "+ "from RecyclerView before it can be recycled: " + holder+ exceptionLabel());}if (holder.shouldIgnore()) {throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"+ " should first call stopIgnoringView(view) before calling recycle."+ exceptionLabel());}//是否阻止回收final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();//是否强制回收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()) {if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {int cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {//注释1处,如果mCachedView缓存已达上限,从mCachedViews中移除最老的ViewHolder到RecyclerViewPool中recycleCachedViewAt(0);cachedViewSize--;}int targetCacheIndex = cachedViewSize;if (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {// when adding the view, skip past most recently prefetched viewsint cacheIndex = cachedViewSize - 1;while (cacheIndex >= 0) {int cachedPos = mCachedViews.get(cacheIndex).mPosition;if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {break;}cacheIndex--;}targetCacheIndex = cacheIndex + 1;}//注释2处,将要回收的ViewHolder加入mCachedViewsmCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {//注释3处,没有成功缓存到mCachedViews,则加入到RecycledViewPool中。addViewHolderToRecycledViewPool(holder, true);recycled = true;}} else {//...}// even if the holder is not removed, we still call this method so that it is removed// from view holder lists.//跟动画相关的ViewHolder也从mViewInfoStore移除。mViewInfoStore.removeViewHolder(holder);//...
}

注释1处,如果mCachedView缓存已达上限,从mCachedViews中移除最老的ViewHolder到RecyclerViewPool中。

Recycler的recycleCachedViewAt方法。

void recycleCachedViewAt(int cachedViewIndex) {//从mCachedViews中获取ViewHolderViewHolder viewHolder = mCachedViews.get(cachedViewIndex);//加入到RecycledViewPooladdViewHolderToRecycledViewPool(viewHolder, true);//mCachedViews移除对应位置上的ViewHoldermCachedViews.remove(cachedViewIndex);
}

内部调用了Recycler的addViewHolderToRecycledViewPool方法。

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {clearNestedRecyclerViewIfNotNested(holder);View itemView = holder.itemView;//...if (dispatchRecycled) {dispatchViewRecycled(holder);}holder.mOwnerRecyclerView = null;//注释1处,调用RecycledViewPool的putRecycledView方法。getRecycledViewPool().putRecycledView(holder);
}

注释1处,调用RecycledViewPool的putRecycledView方法。

/*** 将废弃的ViewHolder加入到缓存池。* 如果ViewHolder对应的ViewType类型的缓存池已经满了,就直接将ViewHolder丢弃。** @param scrap ViewHolder to be added to the pool.*/
public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();final ArrayList <ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;if(mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {//注释1处,如果超过最大限制(默认是5),直接丢弃。PoolingContainer.callPoolingContainerOnRelease(scrap.itemView);return;}if(sDebugAssertionsEnabled && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");}scrap.resetInternal();//加入到缓存池scrapHeap.add(scrap);
}

Recycler的 recycleViewHolderInternal 方法注释2处,先将要回收的ViewHolder加入mCachedViews中。

注释3处,没有成功缓存到 mCachedViews,则加入到RecycledViewPool中。比如说我们设置 mCachedViews 的大小为0的时候,那么就不会缓存到 mCachedViews 中,直接加入到 RecycledViewPool 中。

或者调用 notifyDataSetChanged,notifyItemChanged , notifyItemRemoved 的时候也不会不会缓存到 mCachedViews 中。因为此时,这个条件不满足。

!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)

在滚动时候的复用

LinearLayoutManager 的 layoutChunk 方法。

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {//注释1处,获取子View,可能是从缓存中或者新创建的View。View view = layoutState.next(recycler);if (view == null) {//注释2处,如果获取到的子View为null,将LayoutChunkResult的mFinished置为true,用于跳出循环然后直接return。result.mFinished = true;return;}RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {//注释3处addView(view);  } else {//注释4处addView(view, 0);}} //...}

上篇文章 RecyclerView 源码分析之一 我们分析了layoutChunk方法。在注释1处,获取子View,可能是从缓存中或者新创建的View。我们现在就看一看其中细节。

View view = layoutState.next(recycler);

LayoutState 的 next 方法。

View next(RecyclerView.Recycler recycler) {if (mScrapList != null) {//在我们的分析场景中为nullreturn nextViewFromScrapList();}//注释1处,调用Recycler的getViewForPosition方法获取View,mCurrentPosition是当前的位置。final View view = recycler.getViewForPosition(mCurrentPosition);mCurrentPosition += mItemDirection;return view;
}

注释1处,调用Recycler的getViewForPosition方法获取View,mCurrentPosition是当前的位置。

@NonNull
public View getViewForPosition(int position) {return getViewForPosition(position, false);
}View getViewForPosition(int position, boolean dryRun) {return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

tryGetViewHolderForPositionByDeadline方法里面的逻辑就是先从缓存里面取ViewHolder进行复用,如果没有可复用的ViewHolder,则进行创建。

@Nullable
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处,预布局的时候,从mChangedScrap中获取ViewHolder。 正常滚动的时候,不是预布局状态,不会从这里找。if (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;}// 1) Find by position from scrap/hidden list/cache//注释1处,从Recycler.mAttachedScrap、ChildHelper.mHiddenViews、Recycler.mCachedViews中获取ViewHolderif (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be usedif (!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());}//获取ViewTypefinal int type = mAdapter.getItemViewType(offsetPosition);// 2) Find from scrap/cache via stable ids, if exists//注释2处,通过stable ids从mAttachedScrap、mCachedViews中获取ViewHolder。if (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}//注释3处,从我们自定义的缓存扩展mViewCacheExtension中获取ViewHolderif (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 (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());}}}if (holder == null) { // fallback to poolif (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline("+ position + ") fetching from shared pool");}//注释4处,从RecycledViewPool中获取ViewHolderholder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}if (holder == null) {long start = getNanoTime();if (deadlineNs != FOREVER_NS&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {// abort - we have a deadline we can't meetreturn null;}//注释5处,从适配器中创建一个新的ViewHolderholder = mAdapter.createViewHolder(RecyclerView.this, type);if (ALLOW_THREAD_GAP_WORK) {// only bother finding nested RV if prefetchingRecyclerView 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);//注释6处,绑定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;}rvLayoutParams.mViewHolder = holder;rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;//最终返回ViewHolder对象。return holder;
}

注释0处,预布局的时候,从mChangedScrap中获取ViewHolder。 正常滚动的时候,不是预布局状态,不会从这里找。

注释1处,从Recycler.mAttachedScrap、ChildHelper.mHiddenViews、Recycler.mCachedViews中获取ViewHolder。

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

RecyclerView.Recycler的getScrapOrHiddenOrCachedHolderForPosition方法。

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {final int scrapCount = mAttachedScrap.size();//注释1处,先从mAttachedScrap查找for (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) {//注释2处,从mHiddenViews中查找View,然后根据View获取ViewHolderView 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);//将获取到的View从mHiddenViews移除。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);//注释3处,将View对应的ViewHolder加入到mAttachedScrap或者mChangedScrap中scrapView(view);//添加标记位,多了一个FLAG_BOUNCED_FROM_HIDDEN_LIST的标记位vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);return vh;}}//注释4处,从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) {//找到ViewHolder后,从mCachedViews中移除。mCachedViews.remove(i);}if (DEBUG) {Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position+ ") found match in cache: " + holder);}return holder;}}return null;
}

注释1处,先从mAttachedScrap查找。正常在滚动状态的时候, mAttachedScrap 是空的。

注释2处,从mHiddenViews中查找View,然后根据View获取ViewHolder。正常在滚动状态的时候 mHiddenViews 也是空的。

注释4处,从mCachedViews中获取。正常在这里可以找到。从mCachedViews中获取的ViewHolder 不需要重新绑定。

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注释3处,从我们自定义的缓存扩展mViewCacheExtension中获取ViewHolder,这个自定义的ViewCacheExtension就先忽略了。
(你自定义过ViewCacheExtension吗?,没有。你自定义过?我也没有?正常人谁自定义ViewCacheExtension呀。是,自定义ViewCacheExtension的叫正常人吗?哈哈,纯属搞笑。)

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注释4处,从RecycledViewPool中获取ViewHolder。 从 RecycledViewPool 中获取的 ViewHolder,需要重新 bindViewHolder。

holder = getRecycledViewPool().getRecycledView(type);

RecycledView.RecycledViewPool类,这里提一下,RecycledViewPool可以用来在多个RecyclerView之间来复用View。

/*** RecycledViewPool可以让你在多个RecyclerViews之间复用Views。 lets you share Views between multiple RecyclerViews.* <p>* 如果你想在多个RecyclerView之间复用Views,可以创建一个RecycledViewPool实例,然后调用 RecyclerView的 setRecycledViewPool(RecycledViewPool) 方法设置。* <p>* 如果你不提供RecycledViewPool实例,RecyclerView会自动创建一个。*/
public static class RecycledViewPool {//每个viewType类型的ViewHolder默认缓存5个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缓存不ViewType类型的ViewHolder,SparseArray的key就是ViewType类SparseArray<ScrapData> mScrap = new SparseArray<>();@Nullablepublic ViewHolder getRecycledView(int viewType) {//注释1处,先根据ViewType类获取ScrapData,然后从ScrapData获取ViewHolder。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;}//...
}

注释1处,从RecycledViewPool中获取ViewHolder,先根据ViewType类获取ScrapData,然后从ScrapData获取ViewHolder。

回到 RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注释5处,无法从缓存中获取ViewHolder,就使用适配器中创建一个新的ViewHolder。

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注释6处,绑定ViewHolder。

bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);

Recycler的tryBindViewHolderByDeadline方法。

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;
}

内部调用了适配器的 bindViewHolder 方法。

参考链接:

  • RecyclerView 源码分析之一

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/754025.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++通关攻略 · 基础篇】数据类型

✍作者&#xff1a;黑洞极客 &#x1f4d4;专栏&#xff1a;C&#xff1a;通关攻略 本篇文章将介绍 C 中的数据类型&#xff0c;有帮助的话别忘点赞收藏&#xff0c;关注专栏哦&#xff01; 数据类型 数据类型&#xff0c;顾名思义&#xff0c;就是内存中存储的数据的类型&…

U盘变本地磁盘?数据恢复有妙招!

一、U盘异变本地磁盘现象 在日常工作和生活中&#xff0c;U盘作为便携式的存储设备&#xff0c;广受大家喜爱。然而&#xff0c;有时我们会遇到一个奇怪的现象&#xff1a;原本应该显示为可移动磁盘的U盘&#xff0c;在插入电脑后却变成了“本地磁盘”。这种情况不仅让人困惑&…

论文解读之Attention-based Deep Multiple Instance Learning

前言 多实例学习是由监督学习演变而来的&#xff0c;我们都知道&#xff0c;监督学习在训练的时候是一个实例&#xff08;或者说一个样本、一条训练数据&#xff09;对应一个确定的标签。而多实例的特点就是&#xff0c;我们在训练的时候的输入是多个实例对应一个确定的标签&a…

JUC之CAS比较并交换

CAS 原子类引入 加入原子整型类的操作后&#xff0c;无锁化的操作 CAS 比较并交换 硬件级别的保证 源码分析 引出UnSafe类 UnSafe源码分析 unsafe中的do-while保证自旋 public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 this…

JSON基础知识

目录 一、定义二、作用三、特点四、语法JSON具有以下这些形式&#xff1a;4.1 对象(JSONObject)&#xff1a;4.2 数组(JSONArray)&#xff1a;4.3 值4.4 字符串4.5 数值 五、常用的JSON解析方式5.1 org.json解析5.1.1 常用api5.1.2 get方法与opt方法对比5.1.3 使用示例5.1.3 参…

jS 正则

概念 正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript中&#xff0c;正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法, 以及 String 的 match、matchAll、replace、search 和 split 方法。 创建正则表达式 两种方法&#xff1a;字面量方式、构造…

Spring-3

目录 Spring AOP和AspectJ AOP 在Spring AOP 中&#xff0c;关注点和横切关注的区别 Spring 框架中的单例 Bean 是线程安全的吗 Spring 是怎么解决循环依赖的&#xff1f; 事务隔离级别 事务的传播级别 Spring 事务实现方式 Spring框架的事务管理有哪些优点 事务注解的…

2、高级语言的语法描述

常用的高级程序设计语言 程序语言的定义 语法 一组规则&#xff0c;用它可以形成和产生合适的程序 词法规则&#xff1a;单词符号的形成规则。 单词符号的形成规则单词符号是语言中具有独立意义的最基本结构 一般包括:常数、标识符、基本字、算符、界符等 描述工具:有限自动机…

膨胀 卷积

1.作用 Dilated convolution、Atrous convolution 增大感受野保持原输入大小 2.膨胀因子 描述的是相邻元素之间的距离 r 2 3.gridding effect 不合理的多个膨胀卷积之前&#xff0c;设计的膨胀因子不合理导致&#xff0c;在增大感受野的同时丢失了细节信息。 丢失&…

7.测试教程-自动化测试selenium-2

文章目录 1.webdriver API1.1元素的定位1.2id定位1.3name 定位1.4tag name 定位和class name 定位1.5CSS 定位(常用)1.5.1概念1.5.2实操1.5.3语法 1.6XPath 定位1.6.1概念1.6.2实操1.6.3语法 1.7link text定位1.8Partial link text 定位1.9一个简单的测试实战1.10CSS定位和XPat…

sqllab第二十六A关通关笔记

知识点&#xff1a; 布尔注入 只能爆破出不带空格的语句信息database() version() 等空格、注释都被过滤了错误不回显了 感觉和26关应该差不多 构造payload:id0||11 发现可以绕过 尝试进行错误注入 构造payload:id||exp(710)1 发现页面没有有价值的回显信息&#xff1b;…

一个可商用私有化部署的基于JAVA的chat-gpt网站

目录 介绍一、核心功能1、智能对话2、AI绘画3、知识库4、一键思维导图5、应用广场6、GPTS 二、后台管理功能1、网站自定义2、多账号登录支持3、商品及会员系统4、模型配置5、兑换码生成6、三方商户用户打通 结语 介绍 java语言的私有化部署的商用网站还是比较少的 这里给大家介…

C语言经典面试题目(十七)

1、C语言中的位域和结构体对齐有什么关系&#xff1f; 在C语言中&#xff0c;位域是一种特殊的结构体成员&#xff0c;用于对结构体中的位进行分组和命名。结构体对齐是指编译器根据平台和数据类型的要求将结构体成员按照一定的规则进行内存对齐。 位域和结构体对齐的关系在于…

Go语言gin框架中加载html/css/js等静态资源

Gin框架没有内置静态文件服务&#xff0c;但可以使用gin.Static或gin.StaticFS中间件来提供静态文件服务。 效果图如下&#xff1a; 一、gin 框架加载 Html 模板文件的方法 方式1&#xff1a;加载单个或多个html文件&#xff0c;需要指明具体文件名 r.LoadHTMLFiles("vie…

手写简易操作系统(十)--中断概述

前情提要 我们还是总结一下前面做了什么 1、计算机启动&#xff0c;BIOS将MBR导入到内存&#xff0c;并跳转到相应位置执行 2、MBR将Loader导入到内存&#xff0c;并跳转执行 3、Loader中开启保护模式&#xff0c;准备好GDT表&#xff0c;开启内存分页&#xff0c;从此之后…

通过GroovySDL实现GradleScript

文章目录 Groovy是什么Groovy DependencyDSL是什么初识Groovy DSLClosure, Delegate, Script & ShellCompilation CustomizersDSL Style CustomizerDSL风格脚本展示 Groovy是什么 Groovy是一种在JVM上运行的敏捷开发语言 Groovy 80%的语法和Java完全一致&#xff0c;同时…

9成省份“鸿蒙化”,它真起来了?

自去年9月华为宣布鸿蒙原生应用全面启动以来&#xff0c;鸿蒙正以不可阻挡之势&#xff0c;快速在全国千行百业的移动应用领域推进。不仅有支付宝、快手、淘宝、京东等超200家头部互联网企业加入鸿蒙生态&#xff1b;2024年以来&#xff0c;上海、浙江、广西等多省市政务民生、…

C++_day6

思维导图&#xff1a; 2试编程 封装一个动物的基类&#xff0c;类中有私有成员: 姓名&#xff0c;颜色&#xff0c;指针成员年纪 再封装一个狗这样类&#xff0c;共有继承于动物类&#xff0c;自己拓展的私有成员有:指针成员:腿的个数(整型 int count)&#xff0c;共有成员函数…

2024全新红娘交友系统定制版源码 | 相亲交友小程序源码 全开源可二开

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 全新红娘交友系统定制版源码 | 相亲交友小程序源码 全开源可二开 定制版红娘交友平台小程序源码&#xff0c;很牛逼的东西&#xff0c;虽然是小程序&#xff0c;但是有700多M大&…

modbus客户端

通信方式支持 串口 / udp / tcp通信&#xff1b; 设备协议支持RTU / ASCII / TCP&#xff1b; 读取类型支持bool / short / int / float / double / long / batchbool / batchword