起因 RV在几乎在所有的有界面的Android应用都会使用到,可以说日常开发中RV是老熟人天天见,可是仔细想想熟悉它的什么呢?熟悉的是怎么去使用,对于它的内部原理太过陌生。曾经多次点看它的源码,但都迷失在成千上万的代码与注释中。幸好网上还有很多关于RV优秀的文章,让我能抓住RV的一些重点,其中一个重点就是它对viewHolder的回收与复用机制,关于这一块网上很多文章已经分析过了,Recycler、四级缓存等等,但我还是有一个疑问,在什么操作下会将什么viewHolder放到哪一个缓存?由于RV源码太复杂了😭,下面就带着这个疑问片面地分析下它的缓存设计。
Recycler与RecyclerPool类 Recycler类是RV的一个内部类,正如它的名字一样它是负责回收的,在它的内部定义了好几种关于回收的成员变量,先熟悉一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public final class Recycler { final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null ; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; static final int DEFAULT_CACHE_SIZE = 2 ; }
有三种类型为ViewHolder的ArrayList,显然它们是都是用来缓存不同时机的ViewHolder的,接下来是一个类成员RecycledViewPool,它的代码如下
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 62 63 64 65 66 67 68 69 70 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<>(); private ScrapData getScrapDataForType (int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null ) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; } @Nullable public ViewHolder getRecycledView (int viewType) { final ScrapData scrapData = mScrap.get(viewType); if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; return scrapHeap.remove(scrapHeap.size() - 1 ); } 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,ScrapData类中出现了ArrayList,显然又是用来保存ViewHolder的,但为什么要单独设计一个类中呢,再看看下面一行,定义了一个SparseArray,SparseArray是一个Android特有的类,类似于HashMap,但它的key只能是int,结合RecycledViewPool的put和get方法可以知道,SparseArray中的key就是RV中的itemType,并且每个itemType最多保存5个,所以,在RecycledViewPool中viewHolder是按照它们的类型来分别保存的,这一点也提醒了我:之前那三个缓存是不分类型的!
ViewHolder类 这是一个在开发中经常使用的类,日常使用中也就是在继承ViewHolder,在其中保存itemView的子View,但今天关注的点不一样,在ViewHolder中定义了很多关于ViewHolder状态的常量,主要的几个如下
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 static final int FLAG_BOUND = 1 << 0 ;static final int FLAG_UPDATE = 1 << 1 ;static final int FLAG_INVALID = 1 << 2 ;static final int FLAG_REMOVED = 1 << 3 ;static final int FLAG_NOT_RECYCLABLE = 1 << 4 ;int mFlags;void addFlags (int flags) { mFlags |= flags; }
FLAG_BOUND :viewHolder与RV中POSITION绑定了,就是调用了onBindViewHolder方法吧
FLAG_INVALID :onBindViewHolder的数据失效了,需要重新绑定
FLAG_UPDATE :viewHolder的view需要重新更新数据
关于缓存的一些关键的类介绍完了,接下来开始分析具体的使用场景中这几个缓存是怎么发挥作用的
调用setAdapter方法 setAdapter这个方法可以多次调用,第一次调用显然没有任何缓存可言,这里只考虑第一次调用后再次调用的情况,如下
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 public void setAdapter (@Nullable Adapter adapter) { setLayoutFrozen(false ); setAdapterInternal(adapter, false , true ); processDataSetCompletelyChanged(false ); requestLayout(); } private void setAdapterInternal (@Nullable Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null ) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this ); } if (!compatibleWithPrevious || removeAndRecycleViews) { removeAndRecycleViews(); } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null ) { adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this ); } if (mLayout != null ) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true ; } void removeAndRecycleViews () { ...... mRecycler.clear(); }
setAdapter会调用setAdapterInternal(adapter, false, true)方法,从中可以看到RV和Adapter之间是观察者模式,每一次的setAdapter都是RV反过来订阅了Adapter,Adapter通过各种notify**通知RV数据更新。这个方法后面传入的两个参数是字面意思都很好理解,第一个是否和上一个Adapter的类型兼容,第二个是否移除回收views,compatibleWithPrevious为false,所以会调用removeAndRecycleViews方法,在这个方法中又会调用Rcycler的clear方法,好了这就进入正题了,下面看看这个方法
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 public void clear () { mAttachedScrap.clear(); recycleAndClearCachedViews(); } void recycleAndClearCachedViews () { final int count = mCachedViews.size(); for (int i = count - 1 ; i >= 0 ; i--) { recycleCachedViewAt(i); } mCachedViews.clear(); ...... } void recycleCachedViewAt (int cachedViewIndex) { ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); addViewHolderToRecycledViewPool(viewHolder, true ); mCachedViews.remove(cachedViewIndex); } void addViewHolderToRecycledViewPool (@NonNull ViewHolder holder, boolean dispatchRecycled) { ...... getRecycledViewPool().putRecycledView(holder); }
一系列的调用可以发现,首先会移除RV中所有的view(代码不贴了,这里只关注回收相关的),然后清空了mAttachedScrap,然后将mCachedViews中的viewholder移动到了RecycledViewPool,之后清空mCachedViews中的viewholder,此时只有RecyclerViewPool中有缓存,但感觉不对,已经换Adapter了所有数据都会变,万一旧的Adapter和新的Adapter的itemType有一样的但viewHolder不一样,那还是会从Pool中去取viewHolder,岂不是会崩?多虑了~回到setAdapterInternal方法中会发现这里最后还调用了mRecycler.onAdapterChanged方法,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void onAdapterChanged (Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) { if (oldAdapter != null ) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0 ) { clear(); } if (newAdapter != null ) { attach(); } } public void clear () { for (int i = 0 ; i < mScrap.size(); i++) { ScrapData data = mScrap.valueAt(i); data.mScrapHeap.clear(); } }
由于一个RecyclerViewPool可以设置给多个RV,mAttachCount表示这个Pool关联的RV的数量,当mAttachCount为0时就清空Pool中的所有的viewHolder,所以最后三种缓存都给清空了,如图所示:
调用setAdapter方法缓存的更新就分析完了,可以总结为:
调用notifyDataChanged方法 在日常使用中一旦给RV设置了Adapter,一般都不会去再次设置adapter,更多时候是调用Adapter的notify***方法更新数据,现在就来分析一下调用notifyDataChanged时缓存的情况。刚才提到过RV与Adapter之间是观察者模式,Adapter是被观察者、RV是观察者,当我们调用adapter的notifyDataChanged时最后会调用到RV内部的RecyclerViewDataObserver的onChanged方法,如下
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 private class RecyclerViewDataObserver extends AdapterDataObserver { RecyclerViewDataObserver() { } @Override public void onChanged () { assertNotInLayoutOrScroll(null ); mState.mStructureChanged = true ; processDataSetCompletelyChanged(true ); if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } } } void processDataSetCompletelyChanged (boolean dispatchItemsChanged) { mDispatchItemsChangedEvent |= dispatchItemsChanged; mDataSetHasChangedAfterLayout = true ; markKnownViewsInvalid(); } void markKnownViewsInvalid () { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0 ; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore()) { holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); } } markItemDecorInsetsDirty(); mRecycler.markKnownViewsInvalid(); } void markKnownViewsInvalid () { final int cachedCount = mCachedViews.size(); for (int i = 0 ; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get(i); if (holder != null ) { holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); holder.addChangePayload(null ); } } if (mAdapter == null || !mAdapter.hasStableIds()) { recycleAndClearCachedViews(); } }
最后会调用到RV的markKnownViewsInvalid方法,在这里会取出所有被添加到RV中viewHoder,然后给它们添加上ViewHolder.FLAG_UPDATE和ViewHolder.FLAG_INVALID的FLAG,这两个标记上面介绍了,标记这个viewHolder的数据、位置失效,接着又调用Recycler的同名方法将mCachedViews中的viewHolder标记为失效,然后再调用recycleAndClearCachedViews方法,这个方法上面也提到过,就是将mCachedViews中的viewHolder移动到了RecyclerViewPool中去。
此刻可以总结为:RV中添加的viewHoder仍然在,但被打了标记,mAttachedScrap为空,mCachedViews为空,RecyclerViewPool中可能有缓存。
回到onChanged方法中,最后会调用requestLayout重新布局,requestLayout会触发RV重新measure、layout、draw,在layout的时候将所有的布局都交给了LayoutManager,调用它的onLayoutChildren方法,以LinearLayoutManager为例,在真正进行布局之前会调用detachAndScrapAttachedViews方法,顾名思义就是要detach掉RV中所有的viewHolder
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 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); ....... if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } void recycleViewHolderInternal (ViewHolder holder) { ....... boolean cached = false ; boolean recycled = 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--; } int targetCacheIndex = cachedViewSize; ...... mCachedViews.add(targetCacheIndex, holder); cached = true ; } if (!cached) { addViewHolderToRecycledViewPool(holder, true ); recycled = true ; } } else { ..... } .... }
这一系列的调用中会根据viewHolder的flag将它放到不同的缓存中去,这时这些被标记的viewHolder的flags会导致它被RV移除并被移动到RecyclerViewPool中去,如图所示:
到这里就明确了在调用notifyDataChanged的时候,RV中所有的viewHolder都会标记失效并被移除到RecyclerPool中去,mCachedView、mAttchedView都为空。
调用notifyItemChanged方法 和notifyDataChanged调用一样,调用链如下
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 62 63 64 65 66 67 68 69 @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 ; } private void postponeAndUpdateViewHolders (UpdateOp op) { if (DEBUG) { Log.d(TAG, "postponing " + op); } mPostponedList.add(op); switch (op.cmd) { case UpdateOp.ADD: mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); break ; case UpdateOp.MOVE: mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); break ; case UpdateOp.REMOVE: mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, op.itemCount); break ; case UpdateOp.UPDATE: mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); break ; default : throw new IllegalArgumentException("Unknown update op type for " + op); } } @Override public void markViewHoldersUpdated (int positionStart, int itemCount, Object payload) { viewRangeUpdate(positionStart, itemCount, payload); mItemsChanged = true ; } void viewRangeUpdate (int positionStart, int itemCount, Object payload) { final int childCount = mChildHelper.getUnfilteredChildCount(); final int positionEnd = positionStart + itemCount; for (int i = 0 ; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt(i); final ViewHolder holder = getChildViewHolderInt(child); if (holder == null || holder.shouldIgnore()) { continue ; } if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { holder.addFlags(ViewHolder.FLAG_UPDATE); holder.addChangePayload(payload); ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true ; } } mRecycler.viewRangeUpdate(positionStart, itemCount); }
AdapterHelper的onItemRangeChanged将这个更新事件添加到一个排队执行的List中,triggerUpdateProcessor或者其他事件会触发List的中的事件被执行,不管怎么调用最后都会调用到AdapterHelper的postponeAndUpdateViewHolders,在这里会将需要更新的viewHolder标记为ViewHolder.FLAG_UPDATE,这些都是在RV真正开始布局之前调用的,之后会走正常的layout流程,又以LienarLayoutManager为例,还是会调用detachAndScrapAttachedViews,但这次只有需要更新的viewHolder被标记为FLAG_UPDATE了,其他都没有格外标记,回收的逻辑如下
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 private void scrapOrRecycleView (Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); ....... if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } 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); } }
RV中所有的viewHolder都会被detach,对不需要更新的viewHolder放到了mAttachedScrap中,对需要更新的viewHolder是放到了mChangedScrap中,其他的缓存没有变,如图所示:
调用notifyItemInserted方法 和notifyItemChanged一样,在layout之前会执行AdapterHelper中的postponeAndUpdateViewHolders,这次是UpdateOp.ADD了,所以会调用RV的offsetPositionsForAdd,如下:
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 @Override public void offsetPositionsForAdd (int positionStart, int itemCount) { offsetPositionRecordsForInsert(positionStart, itemCount); mItemsAddedOrRemoved = true ; } void offsetPositionRecordsForInsert (int positionStart, int itemCount) { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0 ; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + holder + " now at position " + (holder.mPosition + itemCount)); } holder.offsetPosition(itemCount, false ); mState.mStructureChanged = true ; } } mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); requestLayout(); } void offsetPosition (int offset, boolean applyToPreLayout) { if (mOldPosition == NO_POSITION) { mOldPosition = mPosition; } if (mPreLayoutPosition == NO_POSITION) { mPreLayoutPosition = mPosition; } if (applyToPreLayout) { mPreLayoutPosition += offset; } mPosition += offset; if (itemView.getLayoutParams() != null ) { ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true ; } }
这个过程只是改变了ViewHolder的位置,没有改变它的状态,参考刚才对notifyItemChanged的分析,RV重新layout时也只是将所有的viewHolder detach了,然后放进mAttachedScrap中,只是这一次viewHolder中代表位置的mPosition值发生了变化。
滑动 具体的分析参见RecyclerView缓存机制(回收些啥?)
可以概括为:在滑动的时候 ,触发RV的onTouchEvent,在它的ACTION_MOVE中,如果这个滑动被认为是一次SCROLL_STATE_DRAGGING就会将这个滑动事件转发给LayoutMamager,以LinearLayoutManager为例,会调用其中的fill方法去添加新的viewHolder到RV中,同时也会detach和回收掉滑出屏幕的viewHolder,fill方法中会依次调用新的item的measure和layout方法,最后在scrollBy方法中会移动所有的item,导致item的invalidate被调用,绘制view。
对滑出屏幕的viewHoler会调用recycleChildren回收,如下:
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 private void recycleChildren (RecyclerView.Recycler recycler, int startIndex, int endIndex) { if (startIndex == endIndex) { return ; } if (DEBUG) { Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items" ); } 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); } } } public void removeAndRecycleViewAt (int index, @NonNull Recycler recycler) { final View view = getChildAt(index); removeViewAt(index); recycler.recycleView(view); } public void recycleView (@NonNull View view) { ViewHolder holder = getChildViewHolderInt(view); if (holder.isTmpDetached()) { removeDetachedView(view, false ); } if (holder.isScrap()) { holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); }
移出屏幕的viewHolder会被detach,然后调用Recycler的recycleView方法回收,这里又调用到了
recycleViewHolderInternal方法,这次会被放到mCachedView中去,如果mCachedView满了就把以前的发到Pool中去,再把这个viewHolder放进mCachedView中。
复用 前面提到过在LinearLayoutManager的fill方法会去添加新的viewHolder到RV中,那这个viewHolder从哪里来呢?显然是要从缓存中获取的,Recycler提供了一个叫tryGetViewHolderForPositionByDeadline的方法获取缓存的viewHolder,具体分析请看
RecyclerView缓存机制(咋复用?)
可以概括为: RV在每一次需要为某个位置添加viewHolder的时候都会通过这个方法去获取viewHolder,首先是根据position在mAttchScrap中去寻找,没有再根据position去mCachedViews中寻找,如果找到了直接返回使用,没有再去RecyclerViewPool中根据itemType寻找。
参考:
Android ListView与RecyclerView对比浅析–缓存机制
RecyclerView缓存机制