RecyclerView 和 ListView 的回收机制非常相似,但是 ListView 是以 View 作为单位进行回收,RecyclerView 是以 ViewHolder 作为单位进行回收。相比于 ListView,RecyclerView 的回收机制更为完善。
Recycler 是 RecyclerView 回收机制的实现类,他实现了四级缓存:
mAttachedScrap: 缓存在屏幕上的 ViewHolder。
mCachedViews: 缓存屏幕外的 ViewHolder,默认为 2 个。ListView 对于屏幕外的缓存都会调用 getView()
。
mViewCacheExtensions: 需要用户定制,默认不实现。
mRecyclerPool: 缓存池,多个 RecyclerView 共用。
要想理解 RecyclerView 的回收机制,我们就必须从其数据展示谈起,我们都知道 RecyclerView 使用 LayoutManager 管理其数据布局的显示。
RecyclerView$LayoutManager LayoutManager 是 RecyclerView 下的一个抽象类,Google 提供了 LinearLayoutManager,GridLayoutManager 以及StaggeredGridLayoutManager 基本上能满足大部分开发者的需求。这三个类的代码都非常长,这要分析下来可了不得。本篇文章只分析 LinearLayoutManager 的一部分内容
与分析 ListView 时类似,RecyclerView 作为一个 ViewGroup,肯定也跑不了那几大过程,我们依然还是只分析其 layout 过程
[RecyclerView.java]
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 @Override protected void onLayout (boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true ; } void dispatchLayout () { if (mAdapter == null ) { Log.e(TAG, "No adapter attached; skipping layout" ); return ; } if (mLayout == null ) { Log.e(TAG, "No layout manager attached; skipping layout" ); return ; } mState.mIsMeasuring = false ; if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this ); dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { mLayout.setExactMeasureSpecsFrom(this ); dispatchLayoutStep2(); } else { mLayout.setExactMeasureSpecsFrom(this ); } dispatchLayoutStep3(); }
不过,无论什么情况,最终都是完成 dispatchLayoutStep1,dispatchLayoutStep2 和 dispatchLayoutStep3 这三步,这样的情况区分只是为了避免重复计算。
其中第二步的 dispatchLayoutStep2 是真正的布局!
1 2 3 4 5 6 7 private void dispatchLayoutStep2 () { ...... mState.mInPreLayout = false ; mLayout.onLayoutChildren(mRecycler, mState); ...... }
由上面的代码可以知道布局的具体操作都交给了具体的 LayoutManager,那我们来分析其中的 LinearLayoutManager
[LinearLayoutManager.java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public void onLayoutChildren (RecyclerView.Recycler recycler, RecyclerView.State state) { ...... onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); detachAndScrapAttachedViews(recycler); ...... fill(recycler, mLayoutState, state, false ); }
上面就是真正的布局代码,跟分析 ListView 一样,下面我们开始分析 RecyclerView 的两次 Layout 过程。
Layout 第一次 Layout 从上面的 onLayoutChildren()
可以看出,这里有两个比较重要的函数:detachAndScrapAttachedViews()
和 fill(recycler, mLayoutState, state, false)
。
第 1 个重要函数 [RecyclerView$LayoutManager]
1 2 3 4 5 6 7 8 9 10 11 12 13 / ** * 暂时detach和scrap所有当前附加的子视图。视图将被丢弃到给定的回收器中(即参数recycler)。 * 回收器(即Recycler)可能更喜欢重用scrap的视图。 * * @param recycler 指定的回收器Recycler */ public void detachAndScrapAttachedViews (Recycler recycler) { final int childCount = getChildCount(); for (int i = childCount - 1 ; i >= 0 ; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } }
在第一次 Layout 时,onLayoutChildren()
会尝试调用 detachAndScrapAttachedViews(recycler)
,将 RecyclerView 中的 View 清除,但是第一次 Layout 时 RecyclerView 是没有 View,所以可以跳过该函数。
第 2 个重要函数 [LinearLayoutManager.java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int fill (RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ...... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0 ) && layoutState.hasMore(state)) { ...... layoutChunk(recycler, state, layoutState, layoutChunkResult); ...... } if (DEBUG) { validateChildOrder(); } return start - layoutState.mAvailable; }
跟进 layoutChunk
[LinearLayoutManager.java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void layoutChunk (RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next(recycler); ...... addView(view); ...... measureChildWithMargins(view, 0 , 0 ); ...... layoutDecoratedWithMargins(view, left, top, right, bottom); ...... }
继续跟进 next()
[LinearLayoutManager$LayoutState]
1 2 3 4 5 6 7 8 View next (RecyclerView.Recycler recycler) { if (mScrapList != null ) { return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }
getViewForPosition()
方法可以说是 RecyclerView 中缓存策略最重要的方法,该方法是从 RecyclerView 的回收机制实现类Recycler 中获取合适的 View,或者新创建一个 View。
1 2 3 4 5 6 7 View getViewForPosition (int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline (position, dryRun, FOREVER_NS).itemView; }
跟进 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 62 63 64 65 66 67 68 69 70 71 72 73 @Nullable ViewHolder tryGetViewHolderForPositionByDeadline (int position, boolean dryRun, long deadlineNs) { ...... if (holder == null ) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); ...... } if (holder == null ) { ...... final int type = mAdapter.getItemViewType(offsetPosition); if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null ) { holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true ; } } if (holder == null && mViewCacheExtension != null ) { ...... final View view = mViewCacheExtension .getViewForPositionAndType(this , position, type); if (view != null ) { holder = getChildViewHolder(view); ...... } } if (holder == null ) { holder = getRecycledViewPool().getRecycledView(type); if (holder != null ) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null ) { holder = mAdapter.createViewHolder(RecyclerView.this , type); } } ...... if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline (holder, offsetPosition, position, deadlineNs); } ...... return holder; }
那么在第 1 次 layout 时,前 4 步都不能获得 ViewHolder,那么进入第 5, 直接创建,也就是调用 Adapter 中的 onCreateViewHolder()
。初次创建完毕后,便会进入 6 ,导致我们重写的 onBindViewHolder()
被回调,让数据和 View 进行绑定。
第二次 Layout 从 ListView 中我们就知道了再简单的 View 也至少需要两次 Layout,在 ListView 中通过把屏幕的子 View detach 并加入mActivieViews,以避免重复添加 item 并可通过 attach 提高性能,那么在 RecyclerView 中,它的做法与 ListView 十分类似,RecyclerView 也是通过 detach 子 View,并把子 View 对应的 ViewHolder 加入其 1 级缓存 mAttachedScrap。这部分我们就不详细分析了,读者可参照上一篇的步骤进行分析。
RecycledViewPool 的结构 RecycledViewPool 是 RecyclerView 复用机制中的最低级缓存,如果在这里面还找不到想要的缓存,则会重新创建一个新的 View,同时 RecycledViewPool 还是所有 RecyclerView 共享的,默认每种 ViewType 最多只能缓存 5 个。而且从这里复用的 ViewHolder 是针对 ViewType 的,因此需要重新绑定数据。
[RecyclerView.java]
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 71 72 73 74 public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5 ; static class ScrapData { ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0 ; long mBindRunningAverageNs = 0 ; } SparseArray<ScrapData> mScrap = new SparseArray<>(); private int mAttachCount = 0 ; public void clear () { ... } public void setMaxRecycledViews (int viewType, int max) { ... } 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 ; } private ScrapData getScrapDataForType (int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null ) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; } 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); } void onAdapterChanged (Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) { if (oldAdapter != null ) { detach(); } if (!compatibleWithPrevious && mAttachCount == 0 ) { clear(); } if (newAdapter != null ) { attach(newAdapter); } } }
总结 Recycler 有4个层次用于缓存 ViewHolder 对象,优先级从高到底依次为 mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool。如果四层缓存都未命中,则重新创建并绑定 ViewHolder 对象
缓存性能:
缓存
重新创建ViewHolder
重新绑定数据
mAttachedScrap
false
false
mCachedViews
false
false
mRecyclerPool
false
true
缓存容量:
mAttachedScrap
:没有大小限制,但最多包含屏幕可见表项。
mCachedViews
:默认大小限制为2,放不下时,按照先进先出原则将最先进入的ViewHolder
存入回收池以腾出空间。
mRecyclerPool
:对ViewHolder
按viewType
分类存储(通过SparseArray
),同类ViewHolder
存储在默认大小为5的ArrayList
中。
缓存用途:
mAttachedScrap
:用于布局过程中屏幕可见表项的回收和复用。
mCachedViews
:用于移出屏幕表项的回收和复用,且只能用于指定位置的表项,有点像“回收池预备队列”,即总是先回收到mCachedViews
,当它放不下的时候,按照先进先出原则将最先进入的ViewHolder
存入回收池。
mRecyclerPool
:用于移出屏幕表项的回收和复用,且只能用于指定viewType
的表项
缓存结构:
mAttachedScrap
:ArrayList<ViewHolder>
mCachedViews
:ArrayList<ViewHolder>
mRecyclerPool
:对ViewHolder
按viewType
分类存储在SparseArray<ScrapData>
中,同类ViewHolder
存储在ScrapData
中的ArrayList
中