RecyclerView缓存机制和刷新原理 最近刚好在看RecyclerView源码方面知识,包括缓存机制和刷新原理,所以打算写下来,就当复习整理了。 使用RecyclerView时会使用adapter和viewHolder来实现,且viewHolder实际上是跟itemView一一对应的,所以当我们说缓存view时,其实是缓存viewHolder。
一.缓存机制 RecyclerView有四个缓存机制:
1.scrap:缓存当前屏幕内的viewHolder。 scrap层的缓存在代码中的实现是用mAttachedScrap和mChangedScrap来实现的。mAttachedScrap :缓存屏幕内的viewHolder。从RecyclerView的onLayout开始,调用了onLayoutChildren把所有的view都存在这里。通过item的position或者itemid找到对应的viewHolder,一般情况下,viewHolder并不会走onBindViewHolder方法。mChangedScrap :同样也是缓存屏幕内的viewHolder。从名字可以看出这是缓存了有变化的viewHolder。使用场景是当调用了notifyItemChanged等时才会使用mChangedScrap来缓存viewHolder。但是只有在预布局的时候才会用到,真正布局时只会创建新的viewHolder。
2.cache:使用mCachedViews 来缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存。 出现场景是滑动RecyclerView或者调用notify。默认最多存放2个,开发者可以调用setItemViewCacheSize(int size)来增大缓存量。从源码可以发现当超出了限制后,会根据FIFO原则把viewHolder拿出来并存放到下一级缓存中(剧透一下:RecyclerViewPool)
3.ViewCacheExtension:这是需要开发者去自定义的缓存,一般来说页面上的所有数据都可以缓存到这里。 使用场景待会再讲。虽然自定义缓存有一定的自由度,但是也是因为完全由开发者自己实现缓存,也就会有一定的问题,所以慎用!
4.RecycledViewPool:ViewHolder缓存池。 如上所说,在有限的mCachedViews中如果存不下ViewHolder时,就会把ViewHolder存入mRecyclerViewPool 中。从这里拿出来的viewHolder是根据viewType而不是position或itemid,而且会重置清空viewHolder。
以上就是RecyclerView的四级缓存机制。它们之间的关系如下图
RecyclerView 四级缓存关系
让我们再看一下第三级缓存ViewCacheExtension。为什么需要这个呢? 可以假设这样一个场景:当我们有一个itemView(或者说viewHolder)它被移出了屏幕外,这时进入了mCacheViews里面,如果超出了限制又会进入到mRecyclerViewPool里面。根据前面我们得知从mRecyclerViewPool里面出来的viewHolder数据会被清空,那么势必会重新绑定adapter。如果只是普通的itemView并且数据会一直变化的话,那么有没有这个自定义无所谓。但是如果这个特定的itemView比如说position为0的,我们希望它一直就是保持position为0,并且它的数据不会改变,那么我们就可以使用ViewCacheExtension来缓存它,使它不会进入mRecyclerViewPool里面,就可以节省内存提高效率了。
1 2 3 4 5 6 7 8 9 10 class MyViewCacheExtension : RecyclerView.ViewCacheExtension() { private val mAdapter: RecyclerView.Adapter<*>? = null override fun getViewForPositionAndType ( recycler: RecyclerView.Recycler, position: Int, type: Int ) : View? { return if (type == TYPE_SPECIAL) mAdapter.data(position) else null } }
在activity调用mRecyclerView.setViewCacheExtension(mExtensionCache); 就行了
网上有个具体使用例子:https://github.com/shineM/RecyclerViewCacheDemo
二、大致的源码解析
1.初始化调用 View的绘制流程是从onMeasure开始的,所以先从RecyclerView的onMeasure入手。比较核心的是onMeasure里面的dispatchLayoutStep2,里面通过mLayout(即LayoutManager).onLayoutChildren,以LinearLayoutManager为例来分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override public void onLayoutChildren (RecyclerView.Recycler recycler, RecyclerView.State state) { …… detachAndScrapAttachedViews(recycler); if (mAnchorInfo.mLayoutFromEnd) { fill(recycler, mLayoutState, state, false ); fill(recycler, mLayoutState, state, false ); endOffset = mLayoutState.mOffset; } else { fill(recycler, mLayoutState, state, false ); fill(recycler, mLayoutState, state, false ); startOffset = mLayoutState.mOffset; } }
进入detachAndScrapAttachedViews->scrapOrRecycleView:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void scrapOrRecycleView (Recycler recycler, int index, View view) { …… if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { …… } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } }
然后进入recycler.scrapView(view):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void scrapView (View view) { final ViewHolder holder = getChildViewHolderInt(view); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view." + " Invalid views cannot be reused from scrap, they should rebound from" + " recycler pool." + exceptionLabel()); } holder.setScrapContainer(this , false ); mAttachedScrap.add(holder); } else { if (mChangedScrap == null ) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this , true ); mChangedScrap.add(holder); } }
再回到scrapOrRecycleView里面,看看if的情况:
1 2 3 4 5 6 7 if (viewHolder.isInvalid()&&!viewHolder.isRemoved() &&!mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); }
来到了recycleViewHolderInternal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 void recycleViewHolderInternal (ViewHolder holder) { …… boolean cached = false ; 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 ) { recycleCachedViewAt(0 ); cachedViewSize--; } …… mCachedViews.add(targetCacheIndex, holder); cached = true ; } if (!cached) { addViewHolderToRecycledViewPool(holder, true ); recycled = true ; } } …… mViewInfoStore.removeViewHolder(holder); if (!cached && !recycled && transientStatePreventsRecycling) { holder.mOwnerRecyclerView = null ; } } void recycleCachedViewAt (int cachedViewIndex) { ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); addViewHolderToRecycledViewPool(viewHolder, true ); mCachedViews.remove(cachedViewIndex); } void addViewHolderToRecycledViewPool (@NonNull ViewHolder holder, boolean dispatchRecycled) { clearNestedRecyclerViewIfNotNested(holder); View itemView = holder.itemView; ······ holder.mOwnerRecyclerView = null ; getRecycledViewPool().putRecycledView(holder); }
现在回到LinearLayout的onLayoutChildren,继续往下看,看到一直调用fill->layoutChunk,通过layoutState.next来获取view
1 2 3 4 5 6 7 8 9 10 View next (RecyclerView.Recycler recycler) { if (mScrapList != null ) { return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }
getViewForPosition->tryGetViewHolderForPositionByDeadline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @Nullable ViewHolder tryGetViewHolderForPositionByDeadline (int position, boolean dryRun, long deadlineNs) { if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null ; } if (holder == null ) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); …… } if (holder == null && mViewCacheExtension != null ) { final View view = mViewCacheExtension .getViewForPositionAndType(this , position, type); …… } if (holder == null ) { …… } if (holder == null ) { holder = mAdapter.createViewHolder(RecyclerView.this , type); } if (mState.isPreLayout() && holder.isBound()) { holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } }
至此,大概的流程就这样结束了。很明显当第一次初始化时还没有任何缓存的viewHolder,所以会一直调用createViewHolder去创建。由于在onLayout的时候也会调用onLayoutChildren,所以初始化之后需要缓存,避免重复初始化
PS:从Scrap中取视图的同时就被移出了缓存。在onLayout这里最终会调用到dispatchLayoutStep3方法,如果Scrap还有缓存,那么缓存会被清空,清空的缓存会被添加到mCachedViews或者RecyclerPool中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void dispatchLayoutStep3 () { mLayout.removeAndRecycleScrapInt(mRecycler); if (mRecycler.mChangedScrap != null ) { mRecycler.mChangedScrap.clear(); } } void removeAndRecycleScrapInt (RecyclerView.Recycler recycler) { recycler.clearScrap(); } void clearScrap () { mAttachedScrap.clear(); if (mChangedScrap != null ) { mChangedScrap.clear(); } }
2.滑动时的调用 从LinearLayout的scrollBy入手分析,最后又会调用fill
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int fill (RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { final int start = layoutState.mAvailable; if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { if (layoutState.mAvailable < 0 ) { layoutState.mScrollingOffset += layoutState.mAvailable; } recycleByLayoutState(recycler, layoutState); } …… }
以recycleViewsFromEnd为例,->recycleChildren->removeAndRecycleViewAt->recycler.recycleView->recycleViewHolderInternal,这个方法之前有分析,就是把viewHolder缓存到mCachedViews或mRecycledViewPool里面,这也说明了移除屏幕外的viewHolder就会存到里面。 当然回收后还会继续走layoutChunk(),复用或重新建立viewHolder。
三、既然都看了源码了,不妨再分析一个东西:预布局pre-layout
1.什么叫做预布局?为什么要用到这个呢? 当你现在的列表有{1,2,3},此时屏幕只能显示{1,2}。当你想要删除掉2时,调用notifyItemRemove时,能够产生把3上移到2这样的动画效果,是通过预布局来实现的。 预布局起到什么作用呢?首先,RecyclerView先通过预布局(preLayout)生成快照{1,2,3}(即使此时屏幕不可见,也要把3的“画”出来,然后删掉2后的结果快照(postLayout)就是{1,3}。把这两张快照做对比,得到3之前后位置点信息,就可以做这样的动画了。
那怎么实现呢?看源码 让我们再次愉快地从onMeasure->dispatchLayoutStep1,发现有一句:mState.mInPreLayout = mState.mRunPredictiveAnimations; 假设它为true(我们现在仅分析它是true的作用),既然mState.mInPreLayout为true了,那么mState.mRunPredictiveAnimations肯定也是true 继续看dispatchLayoutStep1的后续代码
1 2 3 4 5 6 7 8 9 10 11 12 if (mState.mRunPredictiveAnimations) { saveOldPositions(); …… mLayout.onLayoutChildren(mRecycler, mState); …… }
这部分代码的大概内容是在删除item时,会先保存未删除前的布局和位置信息。 然后跟之前分析的onLayoutChildren一样,最后会调用fill来填充布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int fill (RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { …… int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0 ) && layoutState.hasMore(state)) { …… if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; remainingSpace -= layoutChunkResult.mConsumed; } …… } …… } 补充: if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true ; }
通过以上代码可以大概得知,当remainingSpace>0时调用layoutChunk来画item,可以理解为循环几次就画几个item。
此时再把目光放到if那里,如果是非预布局的情况下(即删掉后的布局postLayout),不管其他条件是否满足,remainingSpace会一直减掉使用的空间。以之前的例子,删掉2后只需要画{1,3},所以循环了两次后remainingSpace就会小于0 当在预布局的情况下呢,那么要进入if条件就必须满足layoutChunkResult.mIgnoreConsumed为false或者layoutState.mScrapList != null 先看layoutState.mScrapList,通过查看代码默认初始化是空的,在完成fill之后才会对mScrapList赋值一个list,然后再做一遍fill后又清空了。所以在预布局调用fill时,mScrapList一定是空的 那么如果要满足条件就只能是layoutChunkResult.mIgnoreConsumed为false 看了上面的代码,发现当params是isItemRemoved或isItemChanged时,mIgnoreConsumed才会为true。当我们调用notifyItemRemoved时,最终会把layoutParam.isItemRemoved返回true,这里就不具体分析。因此在绘制item2时,由于被标记了removed,所以mIgnoreConsumed为true,remainingSpace就不会减少,因此就可以多画一个item3。
总结:在preLayout时,由于被删除项所占的位置会被忽略,所以可以多画几个item
前面分析,在postLayout时只会画两个item,那么是怎么保证画的就是{1,3}而不是{1,2}呢? 在调用notifyItemRemoved,会添加一个FLAG_REMOVED。RecyclerView的缓存机制会把当前屏幕的item都保存到mAttachedScrap里面,然后最终在layoutChunk的拿holder时调用getScrapOrHiddenOrCachedHolderForPosition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ViewHolder getScrapOrHiddenOrCachedHolderForPosition (int position, boolean dryRun) { final int scrapCount = mAttachedScrap.size(); 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())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } }
因此被标记为removed的item2就不会被拿出来了
通过我们前面看到的源码,在dispatchLayoutStep2时也会再次调用mLayout.onLayoutChildren
1 2 3 4 5 private void dispatchLayoutStep2 () { mState.mInPreLayout = false ; mLayout.onLayoutChildren(mRecycler, mState); }
所以实际上是布局了两次,step1是旧布局,step2是真正的新布局
2.数据更新: 在更新RecyclerView的数据时,我们一般会使用notifyDataSetChanged 或 notifyItemChanged(position),这两个有什么区别呢? notifyDataSetChanged->mObservable.notifyChanged()->onChanged->processDataSetCompletelyChanged->markKnownViewsInvalid
1 2 3 4 if (holder !=null &&!holder.shouldIgnore()){ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); }
最终调用requestLayout
补充知识:requestLayout会引发view的重绘,会调用View的onMeasure和onLayout,从前面也得到最后其实还是走了onLayoutChildren这些
再次看一下 recycleViewHolderInternal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void recycleViewHolderInternal (ViewHolder holder) { …… if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { …… mCachedViews.add(targetCacheIndex, holder); cached = true ; } if (!cached) { addViewHolderToRecycledViewPool(holder, true ); recycled = true ; } …… }
中间省略了一些细节,可以大概看出如果holder在没有那几个特定的flag(包括FLAG_UPDATE和FLAG_INVALID)时,holder是会存到mCachedViews里面,如果有那些flag则会直接进入到RecycledViewPool里面。但是!RecycledViewPool是有一定的容量限制,如果数量过大则会重新创建ViewHolder 。
那么notifyItemChanged呢? notifyItemChanged->mObservable.notifyItemRangeChanged->notifyItemRangeChanged
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void onItemRangeChanged (int positionStart, int itemCount, Object payload) { assertNotInLayoutOrScroll(null ); if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { triggerUpdateProcessor(); } } boolean onItemRangeChanged (int positionStart, int itemCount, Object payload) { if (itemCount < 1 ) { return false ; } mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); mExistingUpdateTypes |= UpdateOp.UPDATE; return mPendingUpdates.size() == 1 ; }
请注意,onItemRangeChanged有个参数叫payload。 onItemRangeChanged除了通过返回ture/false来控制会不会走下一步外,还保存了一些参数如 UpdateOp.UPDATE 和 payload。 返回ture后->triggerUpdateProcessor->requestLayout();
同样的,调用了requestLayout后会重新绘制,即又会再次走到onLayoutChildren这些。但在此之前要先补充看下 跟前面一样也先从onMeasure入手->dispatchLayoutStep1->processAdapterUpdatesAndSetAnimationFlags
1 2 3 4 5 if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); }
这里会先判断是否支持预测项目动画 ,3.0以上默认开启“预测项目动画”。 ①如果开启支持的话会这样走:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void preProcess () { mOpReorderer.reorderOps(mPendingUpdates); final int count = mPendingUpdates.size(); for (int i = 0 ; i < count; i++) { UpdateOp op = mPendingUpdates.get(i); switch (op.cmd) { case UpdateOp.UPDATE: applyUpdate(op); break ; } } mPendingUpdates.clear(); }
applyUpdate->postponeAndUpdateViewHolders(op)->markViewHoldersUpdated->viewRangeUpdate
1 2 3 4 5 6 7 8 9 10 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { holder.addFlags(ViewHolder.FLAG_UPDATE); holder.addChangePayload(payload); ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true ; }
与payload有关的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void addChangePayload (Object payload) { if (payload == null ) { addFlags(FLAG_ADAPTER_FULLUPDATE); } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0 ) { createPayloadsIfNeeded(); mPayloads.add(payload); } } private void createPayloadsIfNeeded () { if (mPayloads == null ) { mPayloads = new ArrayList<Object>(); mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); } }
②不支持开启“预测项目动画”,则是这样走: mAdapterHelper.consumeUpdatesInOnePass,里面也会根据mPendingUpdates之前赋值的UpdateOp.UPDATE,最后走mCallback.markViewHoldersUpdated,这个刚刚也分析过了,就是把holder添加FLAG_UPDATE和payload。
所以不管怎么样,最后都是给holder增加FLAG_UPDATE和payload
结合我们上述说的layoutChildren一步步走,最后在scrapView那里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void scrapView (View view) { final ViewHolder holder = getChildViewHolderInt(view); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { mAttachedScrap.add(holder); } else { if (mChangedScrap == null ) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this , true ); mChangedScrap.add(holder); } } boolean isUpdated () { return (mFlags & FLAG_UPDATE) != 0 ; } @Override public boolean canReuseUpdatedViewHolder (@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) { return !payloads.isEmpty() || super .canReuseUpdatedViewHolder(viewHolder, payloads); }
scrapView里面的条件判断,首先调用了notifyItemChanged的item一定会被标记update,所以holder.isUpdated返回了true,那么决定是把holder传到mAttachedScrap还是mChangedScrap就得看canReuseUpdatedViewHolder是否返回true。通过上面的代码发现了是跟payload是否为空有关。即如果有payload,那么就会把holder保存到mAttachedScrap里面;没有payload,就会保存到mChangedScrap里面。
回到layoutChildren,接着就是fill这些,最终来到tryGetViewHolderForPositionByDeadline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @Nullable ViewHolder tryGetViewHolderForPositionByDeadline (int position, boolean dryRun, long deadlineNs) { if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null ; } if (holder == null ) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); …… } …… if (holder == null ) { holder = mAdapter.createViewHolder(RecyclerView.this , type); } if (mState.isPreLayout() && holder.isBound()) { holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } } private boolean tryBindViewHolderByDeadline (@NonNull RecyclerView.ViewHolder holder, int offsetPosition, int position, long deadlineNs) { holder.mOwnerRecyclerView = RecyclerView.this ; …… mAdapter.bindViewHolder(holder, offsetPosition); …… return true ; } public final void bindViewHolder (@NonNull VH holder, int position) { …… onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); } List<Object> getUnmodifiedPayloads () { if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0 ) { if (mPayloads == null || mPayloads.size() == 0 ) { return FULLUPDATE_PAYLOADS; } return mUnmodifiedPayloads; } else { return FULLUPDATE_PAYLOADS; } }
当dispatchLayoutStep1时,开始preLayout,假设没有payload参数,那么holder就是从mChangedScrap里面拿,并且不会做更新绑定 当dispatchLayoutStep2时,开始postLayout ①如果没有payload参数,那么之前的缓存holder由于是保存在mChangedScrap里面,现在不是preLayout阶段,所以只能从recycledViewPool中拿(详情看前面recycleViewHolderInternal那部分的分析)。如果没有找到,就只能通过createHolder,然后再重新绑定adapter。 ②如果有payload参数,那么之前的缓存holder就是保存在mAttachScrap里面,就可以从那个列表拿出来了。注意!此时holder.needsUpdate是true, 所以会重新绑定adapter。所以如果没有notify时,保存到mAttachScrap里面的holder是不用重新绑定adapter的。如果调用了notify就需要重新绑定adapter了。
再一次看payload,它是从onItemRangeChanged来,而这又是从notifyItemChanged来:
1 2 3 4 5 6 public final void notifyItemChanged (int position, @Nullable Object payload) { mObservable.notifyItemRangeChanged(position, 1 , payload); } public final void notifyItemChanged (int position) { mObservable.notifyItemRangeChanged(position, 1 ); }
此外还有一点需要注意的是,adapter的onBindViewHolder也有两个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void onBindViewHolder (@NonNull RecyclerView.ViewHolder holder, int position) { } @Override public void onBindViewHolder (@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) { if (payloads!=null ){ }else { onBindViewHolder(holder,position); } }
总结:1.notifyDataSetChanged最终会重新创建viewHolder,而notifyItemChange是从mChangedScrape里面拿viewHolder。 2.即使你调用 notifyItemChanged 多次,其实只有第一次会触发 requestLayout() 。 3.如果想实现局部刷新,那么需要调用带有payload的notifyItemChanged(position,payload)
四、简单了解ListView的缓存机制 ListView的缓存只有两层:mActiveViews和mScrapViews。 mActiveViews:快速重用屏幕上可见的列表项,不需要重新createView和bindView; mScrapViews:缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用. 流程如下图:
ListView缓存机制
参考自:真正带你搞懂 RecyclerView 的缓存机制,再也不怕面试被虐了 RecyclerView局部刷新和原理介绍 换个姿势看源码 | RecyclerView预布局(pre-layout) 深入理解Android RecyclerView的缓存机制