从源码分析RecyclerView的回收和复用机制

从RecyclerView的名字就可以看出,它的主要作用就是对View的回收。相比较ListView它的优势是它本身不需要关心视图的问题,它不需要关心如何将子View放在合适的位置,不需要关心如何分割这些子View,更不用关心这些子View的外观。它要做的仅仅是回收和复用的工作。现在我们就来看一下如此优秀强大的RecyclerView它负责的工作都是怎样展开的。

在开始之前先来看一下关于RecyclerView的灵魂三问,带着这三个问题去源码中找答案,这样可以更好的理解源码,理解它的工作流程。

问题一:RecyclerView回收的是什么?复用的是什么?

问题二:回收来的东西放到了哪里?复用的东西又是从哪里取得的?

问题三:什么时候回收?什么时候复用?

之所以将回收和复用设计出来,其原因一定是为了提升有很多数据的列表的性能。所以,回收和复用一定是和滑动有关的。因此,我们就从RecyclerView的onTouchEvent的滑动事件开始分析。

我们先来看一下回收机制。

回收:当一个itemView从可见到不可见时,RecyclerView利用回收机制将itemView存放到缓存池中,当其他itemView出现时,不需要每次都去创建一个新的itemView,可以只是onBindViewHolder绑定数据就可以了。

在Action_Move事件中,调用了RecyclerView的scrollByInternal方法。代码如下:

   /**
     * Does not perform bounds checking. Used by internal methods that have already validated input.
     * <p>
     * It also reports any unused scroll request to the related EdgeEffect.
     *
     * @param x The amount of horizontal scroll request
     * @param y The amount of vertical scroll request
     * @param ev The originating MotionEvent, or null if not from a touch event.
     *
     * @return Whether any scroll was consumed in either direction.
     */
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        
        if (mAdapter != null) {
            eatRequestLayout();
            onEnterLayoutOrScroll();
            TraceCompat.beginSection(TRACE_SCROLL_TAG);
            fillRemainingScrollValues(mState);
            if (x != 0) {
                // 关键代码1
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                // 关键代码2
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
            
        }
        
        return consumedX != 0 || consumedY != 0;
    }

scrollByInternal方法的内部关键部分就是根据参数x或者y调用了mLayout的scrollHorizontallyBy或者mLayout.scrollVerticallyBy方法。mLayout就是LayoutManager的实例对象。LayoutManager是一个抽象类,我们不妨直接看一下它的子类,LinearLayoutManager。刚才说到调用了mLayout的scrollHorizontallyBy/scrollVerticallyBy方法,现在直接看一下LinearLayoutManager中这两个方法的实现。代码不再贴出来,两者都是调用了scrollBy方法。在这个scrollBy方法中,调用了fill方法。现在看一下fill方法的代码。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        
        // 代码省略。。。

        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // 关键代码1
            recycleByLayoutState(recycler, layoutState);
        }       

            // 代码省略。。。

            // 关键代码2

            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            

            // 。。。。
    }

关键处就两个地方,第一个recycleByLayoutState,通过方法名,大概意思就是通过布局状态回收。第二个关键点就是layoutChunk,这个方法跟复用有关系。先来看一个recycleByLayoutState(回收)。代码跟进:

   /**
     * Helper method to call appropriate recycle method depending on current layout direction
     *
     * @param recycler    Current recycler that is attached to RecyclerView
     * @param layoutState Current layout state. Right now, this object does not change but
     *                    we may consider moving it out of this view so passing around as a
     *                    parameter for now, rather than accessing {@link #mLayoutState}
     * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int)
     * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int)
     * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
     */
    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }

方法作用是,根据当前的布局方向调用适当的回收方法。我们只挑一个方向说明,recycleViewsFromStart方法,其中调用了recycleChildren方法。recycleChildren方法的代码如下所示:

   /**
     * Recycles children between given indices.
     *
     * @param startIndex inclusive
     * @param endIndex   exclusive
     */
    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);
            }
        }
    }

方法作用是,回收指点位置之间的子View。直接看removeAndRecycleViewAt方法。代码如下:

  /**
    * Remove a child view and recycle it using the given Recycler.
    *
    * @param index Index of child to remove and recycle
    * @param recycler Recycler to use to recycle child
    */
public void removeAndRecycleViewAt(int index, Recycler recycler) {
      final View view = getChildAt(index);
      removeViewAt(index);
      recycler.recycleView(view);
}

该方法的作用是,移除一个子View,并且使用给定的Recycler回收它。到现在终于看到了,调用回收的方法recycleView。其代码如下:

/**
         * Recycle a detached view. The specified view will be added to a pool of views
         * for later rebinding and reuse.
         *
         * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
         * View is scrapped, it will be removed from scrap list.</p>
         *
         * @param view Removed view for recycling
         * @see LayoutManager#removeAndRecycleView(View, Recycler)
         */
        public void recycleView(View view) {
           
            // 。。。。
            // 关键代码
            recycleViewHolderInternal(holder);
        }

recycleView的作用是,回收分离的视图。指定的视图将被添加到一个视图池中,以供以后重新绑定和重用。其关键代码就是最后的recycleViewHolderInternal方法。代码如下:

/**
         * internal implementation checks if view is scrapped or attached and throws an exception
         * if so.
         * Public version un-scraps before calling recycle.
         */
        void recycleViewHolderInternal(ViewHolder holder) {
            
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        // 关键代码1。。。
                        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 views
                        int cacheIndex = cachedViewSize - 1;
                        while (cacheIndex >= 0) {
                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                            if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                break;
                            }
                            cacheIndex--;
                        }
                        targetCacheIndex = cacheIndex + 1;
                    }
                    // 关键代码2。。。
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    // 关键代码3。。。
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                
                    // 。。。。
                
            }
            // 。。。            
        }

关键代码有三处。看第一处其执行的条件是mCacheViews的大小大于mViewCacheMax(默认为2)且mCacheViews不为空。mCacheViews是一个泛型为ViewHodler的ArrayList集合,至此我们就能知道,所谓的回收和复用其实就是针对ViewHolder而言的。关键1处,recycleCachedViewAt方法,代码如下:

void recycleCachedViewAt(int cachedViewIndex) {
            if (DEBUG) {
                Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
            }
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            if (DEBUG) {
                Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
            }
            addViewHolderToRecycledViewPool(viewHolder, true);
            mCachedViews.remove(cachedViewIndex);
}

代码比较少,逻辑就是先从mCacheViews集合中拿出相应位置的ViewHolder对象,再在将其从mCacheViews集合中移除之前,此ViewHolder作为addViewHolderToRecycledViewPool方法的参数执行了相应的操作。最后,将其从mCacheViews集合中删除了。现在我们看一下addViewHolderToRecycledViewPool方法做了些什么。代码如下所示:

       /**
         * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool.
         *
         * Pass false to dispatchRecycled for views that have not been bound.
         *
         * @param holder Holder to be added to the pool.
         * @param dispatchRecycled True to dispatch View recycled callbacks.
         */
        void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
            //。。。
            if (dispatchRecycled) {
                // 代码1
                dispatchViewRecycled(holder);
            }
            holder.mOwnerRecyclerView = null;
            // 代码
            getRecycledViewPool().putRecycledView(holder);
        }

该方法作用是:准备好ViewHolder以被移除或回收,并且将其出入到RecyclerViewPool中。此方法中,有两个关键点,第一就是dispatchViewRecycled方法对此holder的处理;第二就是getRecycledViewPool().putRecycledView(holder)。dispatchViewRecycled方法代码如下:

    void dispatchViewRecycled(ViewHolder holder) {
            if (mRecyclerListener != null) {
                mRecyclerListener.onViewRecycled(holder);
            }
            if (mAdapter != null) {
                // 调用Adapter的view回收方法
                mAdapter.onViewRecycled(holder);
            }
            if (mState != null) {
                mViewInfoStore.removeViewHolder(holder);
            }
            if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
    }

到此,该ViewHolder就被Adapter回收了。下面接着看,上面的关键代码2。直接跟进代码就是了。

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

这个方法中重要的就是getScrapDataForType方法了。其代码如下:

private ScrapData getScrapDataForType(int viewType) {
    ScrapData scrapData = mScrap.get(viewType);
    if (scrapData == null) {
        scrapData = new ScrapData();
        // 关键代码
        mScrap.put(viewType, scrapData);
    }
    return scrapData;
}

这一方法关键之处就是往SparseArray类型的mScrap中添加了以viewType为key,scrapData为value的对象。mScrap是RecycledViewPool中维护的一个SparseArray结构的map集合。回收来的东西放到了这里。

至此,RecyclerView的回收机制介绍完了。下面接着分析复用机制。

上面分析回收机制的时候提到了fill方法,其中有两个关键点一个是recycleByLayoutState方法,另一个是layoutChunk方法。回收机制就是从recycleByLayoutState开始,现在我们分析回收机制的入口layoutChunk方法,并且我们会按照流程整个分析一遍。代码如下:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        // 关键代码next方法。。。
        View view = layoutState.next(recycler);
        // 。。。。
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                // 添加View
                addView(view);
            } else {
                // 添加View
                addView(view, 0);
            }
        } else {
            // 。。。。
        }
        // 测量childView
        measureChildWithMargins(view, 0, 0);
        // 。。。。
        // To calculate correct layout position, we subtract margins.
        // 布局childView
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        // 。。。
    }

layoutChunk方法代码很长,但是关键的几点已经在源码中标注出来了。首先就是LayoutState的next方法。next方法作用有两点:

第一:获取要布局的下一个元素的视图。

第二:将当前项的索引更新到下一项。

代码如下:

View next(RecyclerView.Recycler recycler) {
       if (mScrapList != null) {
           return nextViewFromScrapList();
       }
        // 关键代码。。。
       final View view = recycler.getViewForPosition(mCurrentPosition);
       mCurrentPosition += mItemDirection;
       return view;
}

next方法代码不多,关键点也就一个,那就是recycler的getViewForPosition方法。在分析复用机制之前有必要先来看一下Recycler类中的主要代码。

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

        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        int mViewCacheMax = DEFAULT_CACHE_SIZE;

        RecycledViewPool mRecyclerPool;

        private ViewCacheExtension mViewCacheExtension;

        static final int DEFAULT_CACHE_SIZE = 2;

        // 。。。。


    }

类的代码很多(得有1000行),但是结构不太复杂。复用涉及到的四级缓存都在这里有声明。

一级缓存:mAttachScrap,mChangedScrap

二级缓存:mCacheViews

三级缓存:mViewCacheExtension(自定义缓存)

四级缓存:mRecyclerPool(缓存池)。

了解了Recycler类中的四级缓存后,我们接着上面的分析继续往下走。getViewforPosition方法最终会调用到tryGetViewHolderForPositionByDeadline方法,看一下其中的实现。代码如下:

       /**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            
            
            if (mState.isPreLayout()) {
                // 关键代码1。。。
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                // 关键代码2。。。
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    // 关键代码3。。。
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!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;
                    } 
                }
            }
            if (holder == null) {
                
                // 获取当前position对应的item的type
                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    // 关键代码4。。。
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    
                }
                // 关键代码5。。。(自定义缓存)
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    // 通过getViewForPositionAndType来获取view
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        //。。。
                    }
                }
                if (holder == null) { // fallback to pool
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                                + position + ") fetching from shared pool");
                    }
                    // 关键代码6。。。
                    holder = 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 meet
                        return null;
                    }
                    // 关键代码7。。。创建ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    
                }
            }

            // 代码省略。。。。            

            return holder;
        }

注释中对它功能的描述是:尝试获取给定位置的ViewHolder,可以从回收器scrap,cache,RecyclerViewPool获取,也可以直接创建。该方法代码很多,即便精简下来也比较多。现在针对它关键的几处进行分析。

关键代码1:getChangedScrapViewHolderForPosition方法。代码如下:

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;
            }
            // find by position
            for (int i = 0; i < changedScrapSize; i++) {
                // 通过position从mChangedScrap中获取ViewHolder
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
            // find by 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++) {
                        // 通过stableIds从mChangedScrap中查找ViewHolder
                        final ViewHolder holder = mChangedScrap.get(i);
                        if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                            return holder;
                        }
                    }
                }
            }
            return null;
        }

它的作用就是通过position和stableIds从Recycler中的mChangedScrap中查找ViewHolder。

关键2:getScrapOrHiddenOrCachedHolderForPosition方法。见名知意,其作用是从scrap或隐藏列表或缓存中通过position查找ViewHolder。代码如下:

       /**
         * Returns a view for the position either from attach scrap, hidden children, or cache.
         *
         * @param position Item position
         * @param dryRun  Does a dry run, finds the ViewHolder but does not remove
         * @return a ViewHolder that can be re-used for this position.
         */
        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();

            // Try first for an exact, non-invalid match from scrap.
            // 先从mAttachScrap中查找
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                    return holder;
                }
            }
            // dryRun为false
            if (!dryRun) {
                // 从HiddenView中获得View
                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.
                    // 通过View的LayoutParams获得ViewHolder
                    final ViewHolder vh = getChildViewHolderInt(view);
                    // 从HiddenView中移除
                    mChildHelper.unhide(view);
                    。。。
                    mChildHelper.detachViewFromParent(layoutIndex);
                    // 回收到scrap中
                    scrapView(view);
                    
                    return vh;
                }
            }

            // Search in our first-level recycled view cache.
            // 从CacheView中获取
            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 getScrapOrCachedViewForId
                // 判断ViewHolder是否有效,position是否相同
                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    。。。。
                    return holder;
                }
            }
            return null;
        }

首先就是从mScrap中获取,如果没有就从hiddenView中获取,最后从CacheView中获取,在CacheView中获取到之后,进行判断ViewHolder是否有效,position是否相同。

关键代码3:就是检查当前ViewHolder与当前的position是否相匹配。

回收方法recycleViewHolderInternal中,做的操作就是,将得到的ViewHolder放置到mCacheViews中或者RecycledViewPool缓存池中。

关键代码4:getScrapOrCachedViewForId方法。作用就是通过id从Scrap或者CachedView中获取ViewHolder。在调用此方法之前,调用了getItemViewType方法,这个是对应RecyclerVIew多样式条目的情况,从此方法可以获取当前item的条目类型。现在看一下getScrapOrCachedViewForId方法的代码。

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
            // Look in our attached views first
            final int count = mAttachedScrap.size();
            for (int i = count - 1; i >= 0; i--) {
                // 从mAttchedScrap中获取ViewHolder
                final ViewHolder holder = mAttachedScrap.get(i);
                if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                    if (type == holder.getItemViewType()) {
                      
                        return holder;
                    } else if (!dryRun) {
                        // 从scrap中移除
                        mAttachedScrap.remove(i);
                        removeDetachedView(holder.itemView, false);
                        // 加入cachedView或者RecycledViewPool中
                        quickRecycleScrapView(holder.itemView);
                    }
                }
            }

            // Search the first-level cache
            // 从cachedView中获取
            final int cacheSize = mCachedViews.size();
            for (int i = cacheSize - 1; i >= 0; i--) {
                final ViewHolder holder = mCachedViews.get(i);
                if (holder.getItemId() == id) {
                    if (type == holder.getItemViewType()) {
                        if (!dryRun) {
                            mCachedViews.remove(i);
                        }
                        return holder;
                    } else if (!dryRun) {
                        recycleCachedViewAt(i);
                        return null;
                    }
                }
            }
            return null;
        }

这里的判断逻辑跟上边getScrapOrHiddenOrCachedHolderForPosition方法中的判断逻辑几乎一致。所以,我认为,第一级缓存,就是mAttachedScrap。刚刚介绍的两个方法就是从mAttachedScrap中根据position或者id这两种方式去获取ViewHolder。第二级缓存,就是从mCachedViews中获取ViewHolder。第一级缓存中,混合了第二级缓存。现在接着往下看。

关键代码5:自定义缓存,通过调用getChildViewHolder方法,getChildViewHolder方法中调用了getChildViewHolderInt方法。其中是通过child的LayoutParams获取ViewHolder。

关键代码6:getRecycledViewPool().getRecycledView(type)。也就是第四级缓存。通过RecycledViewPool来获取ViewHolder。看一下这两个方法的代码。

RecycledViewPool getRecycledViewPool() {
    if (mRecyclerPool == null) {
        mRecyclerPool = new RecycledViewPool();
    }
    return mRecyclerPool;
}

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数据结构
        SparseArray<ScrapData> mScrap = new SparseArray<>();

        private int mAttachCount = 0;
}

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

RecycledViewPool中使用了SparseArray,它类似于一个HashMap,内部的key就是我们的viewType,而value存放的就是ArrayList<ViewHolder>。默认的每个ArrayList<ViewHolder>大小是5。getRecycledView方法拿到ViewHolder,是通过remove方法的。

最后分析一下关键代码7。经过上述四级缓存之后,如果此时ViewHolder还为null,这样就会调用mAdapter.createViewHolder方法来创建viewHolder了。

至此,我们的复用机制也介绍完了。最后我们来集中看一下最开始提到的那三个问题。

问题一:RecyclerView回收的是什么?复用的是什么?

这个问题,已经回答过了。在整个的过程中我们提到最多的也是这个回收和复用的对象ViewHolder。

问题二:回收来的东西放到了哪里?复用的东西又是从哪里取得的?

回收来的东西放到了RecycledViewPool缓存池中。至于复用的东西从哪里取得的这个问题,想必不用我再回答了吧。复用过程四级缓存不就是讲的这个问题吗。

问题三:什么时候回收?什么时候复用?

回收是在itemView将要消失的时候,复用则发生在itemView由不可见到可见的时候。

最后附上这两个过程的流程图就该结束本文了。这两张图是直接“拿”的现成的。若有侵权请告知,立即删除。

回收过程。

复用过程:

至此,本文就结束了。谢谢各位看官能坚持看完。

在此我特别感谢,这两张流程图的作者波澜不惊非常感谢。

猜你喜欢

转载自blog.csdn.net/zhourui_1021/article/details/106244168