Analyze the recycling and reuse mechanism of RecyclerView from the source code

As can be seen from the name of RecyclerView, its main function is to recycle the View. Compared with ListView, its advantage is that it does not need to care about the view, it does not need to care about how to place the child views in the right position, how to divide these child views, let alone the appearance of these child views. All it has to do is to recycle and reuse. Now let's take a look at how the work of such an excellent and powerful RecyclerView is unfolded.

Before we start, let's take a look at the three questions about the soul of RecyclerView, and take these three questions to find the answers in the source code, so that you can better understand the source code and understand its workflow.

Question 1: What is recycled by RecyclerView? What is being reused?

Question 2: Where did the recovered items go? Where do you get the reused stuff?

Question 3: When will it be recycled? When is it reused?

The reason for the design of recycling and reuse must be to improve the performance of lists with a lot of data. Therefore, recycling and reuse must be related to sliding. Therefore, we start the analysis from the sliding event of RecyclerView's onTouchEvent.

Let's first look at the recycling mechanism.

Recycling: When an itemView changes from visible to invisible, RecyclerView uses the recycling mechanism to store the itemView in the buffer pool. When other itemViews appear, there is no need to create a new itemView every time, you can just bind the data onBindViewHolder. Up.

In the Action_Move event, the scrollByInternal method of RecyclerView is called. code show as below:

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

The internal key part of the scrollByInternal method is to call the scrollHorizontallyBy or mLayout.scrollVerticallyBy method of mLayout according to the parameter x or y. mLayout is an instance object of LayoutManager. LayoutManager is an abstract class, we might as well take a look at its subclass, LinearLayoutManager. Speaking of calling the scrollHorizontallyBy/scrollVerticallyBy methods of mLayout just now, let’s look directly at the implementation of these two methods in LinearLayoutManager. The code is no longer posted, both call the scrollBy method. In this scrollBy method, the fill method is called. Now look at the code of the fill method.

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

            // 。。。。
    }

There are two key points, the first recycleByLayoutState, through the method name, probably means recycling through the layout state. The second key point is layoutChunk, which is related to reuse. First look at a recycleByLayoutState (recycling). Code follow-up:

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

The function of the method is to call the appropriate recycling method according to the current layout direction . We only pick one direction, the recycleViewsFromStart method, which calls the recycleChildren method. The code of the recycleChildren method is as follows:

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

The function of the method is to recover the child View between the pointing positions. Look directly at the removeAndRecycleViewAt method. code show as below:

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

The function of this method is to remove a child View and use the given Recycler to recycle it. Now I finally saw it, calling the recycling method recycleView. The code is as follows:

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

The role of recycleView is to recycle separated views. The specified view will be added to a view pool for later rebinding and reuse. The key code is the final recycleViewHolderInternal method. code show as below:

/**
         * 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 {
                
                    // 。。。。
                
            }
            // 。。。            
        }

There are three key codes. The first condition for its execution is that the size of mCacheViews is greater than mViewCacheMax (the default is 2) and mCacheViews is not empty. mCacheViews is an ArrayList collection of generic ViewHodler. So far we can know that the so-called recycling and reuse are actually for ViewHolder. The key point is the recycleCachedViewAt method, the code is as follows:

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

The code is relatively small, and the logic is to first take out the ViewHolder object at the corresponding position from the mCacheViews collection, and then perform the corresponding operation on this ViewHolder as a parameter of the addViewHolderToRecycledViewPool method before removing it from the mCacheViews collection. Finally, it was deleted from the mCacheViews collection. Now we look at what the addViewHolderToRecycledViewPool method does. The code is as follows:

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

The function of this method is: prepare the ViewHolder to be removed or recycled, and put it in and out of the RecyclerViewPool. In this method, there are two key points. The first is how the dispatchViewRecycled method handles this holder; the second is getRecycledViewPool().putRecycledView(holder). The dispatchViewRecycled method code is as follows:

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

At this point, the ViewHolder is recycled by the Adapter. Let's look at the key code 2 above. Just follow up the code directly.

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

The important part of this method is the getScrapDataForType method. The code is as follows:

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

The key point of this method is to add an object with viewType as the key and scrapData as the value to the mScrap of the SparseArray type. mScrap is a map collection of SparseArray structure maintained in RecycledViewPool. The recovered things are put here.

So far, the recycling mechanism of RecyclerView has been introduced. Next, analyze the reuse mechanism.

When analyzing the recycling mechanism above, the fill method was mentioned. There are two key points: one is the recycleByLayoutState method, and the other is the layoutChunk method. The recycling mechanism starts from recycleByLayoutState. Now we analyze the entry layoutChunk method of the recycling mechanism, and we will analyze the entire process according to the process. code show as below:

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

The layoutChunk method code is very long, but the key points have been marked out in the source code. The first is the next method of LayoutState. The next method has two functions:

First: Get the view of the next element to be laid out.

Second: Update the index of the current item to the next item.

code show as below:

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

There is not much code in the next method, and the key point is the getViewForPosition method of the recycler. Before analyzing the reuse mechanism, it is necessary to take a look at the main code in the Recycler class.

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;

        // 。。。。


    }

The class has a lot of code (it has to be 1000 lines), but the structure is not too complicated. The four-level caches involved in reuse are all declared here.

Level 1 cache: mAttachScrap, mChangedScrap

Secondary cache: mCacheViews

Three-level cache: mViewCacheExtension (custom cache)

Four-level cache: mRecyclerPool (buffer pool).

After understanding the four-level cache in the Recycler class, we will continue the above analysis. The getViewforPosition method will eventually call the tryGetViewHolderForPositionByDeadline method to see the implementation. code show as below:

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

The description of its function in the comment is: try to get the ViewHolder at a given location, which can be obtained from the recycler scrap, cache, RecyclerViewPool, or it can be created directly. This method has a lot of code, even if it is simplified, there are more. Now analyze the key points.

Key code 1: getChangedScrapViewHolderForPosition method. code show as below:

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

Its function is to find ViewHolder from mChangedScrap in Recycler through position and stableIds.

Key 2: getScrapOrHiddenOrCachedHolderForPosition method. As the name knows, its function is to find the ViewHolder by position from the scrap or hidden list or cache. code show as below:

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

First, get it from mScrap, if not, get it from hiddenView, and finally get it from CacheView. After getting it from CacheView, judge whether the ViewHolder is valid and whether the position is the same.

Key code 3: Check whether the current ViewHolder matches the current position.

In the recycling method recycleViewHolderInternal, the operation is to place the obtained ViewHolder in mCacheViews or RecycledViewPool cache pool.

Key code 4: getScrapOrCachedViewForId method. The function is to get ViewHolder from Scrap or CachedView by id. Before calling this method, the getItemViewType method is called, which corresponds to the case of RecyclerVIew multi-style items. From this method, the item type of the current item can be obtained. Now look at the code of the getScrapOrCachedViewForId method.

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

The judgment logic here is almost the same as the judgment logic in the getScrapOrHiddenOrCachedHolderForPosition method above. Therefore, I think that the first level of cache is mAttachedScrap. The two methods just introduced are to obtain the ViewHolder from mAttachedScrap according to the position or id. The second level of cache is to get ViewHolder from mCachedViews. In the first level cache, the second level cache is mixed. Now let's look down.

Key code 5: Custom cache, by calling the getChildViewHolder method, the getChildViewHolderInt method is called in the getChildViewHolder method. Among them, the ViewHolder is obtained through the child's LayoutParams.

Key code 6: getRecycledViewPool().getRecycledView(type). This is the fourth level cache. Get ViewHolder through RecycledViewPool. Take a look at the code for these two methods.

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

SparseArray is used in RecycledViewPool, which is similar to a HashMap, the internal key is our viewType, and the value stored is ArrayList<ViewHolder>. The default size of each ArrayList<ViewHolder> is 5. The getRecycledView method gets the ViewHolder through the remove method.

Finally, analyze the key code 7. After the above four-level cache, if the ViewHolder is still null at this time, the mAdapter.createViewHolder method will be called to create the viewHolder.

So far, our reuse mechanism has also been introduced. Finally, let's focus on the three issues mentioned at the beginning.

Question 1: What is recycled by RecyclerView? What is being reused?

This question has already been answered. In the whole process, we mentioned most of this object ViewHolder which is recycled and reused.

Question 2: Where did the recovered items go? Where do you get the reused stuff?

Recycled things are placed in the RecycledViewPool buffer pool. As for the question of where to get the reused stuff, I don’t need to answer it again. Isn't the four-level cache in the reuse process just talking about this problem?

Question 3: When will it be recycled? When is it reused?

Recycling is when the itemView is about to disappear, and reuse occurs when the itemView is invisible.

Finally, attaching the flowchart of these two processes, it is time to end this article. These two pictures are directly "taken" ready-made. If there is any infringement, please inform and delete it immediately.

Recycling process.

Reuse process:

At this point, this article is over. Thank you all readers for persevering in reading.

Here I am very grateful, the authors of these two flowcharts are not surprised very much.

Guess you like

Origin blog.csdn.net/zhourui_1021/article/details/106244168