Preface: Life is always to have faith, have a dream can always move forward, even if walk slowly, but also in advance.

An overview,

RecyclerView as an official specified high efficiency, high expansion of the list control, do a good packaging, flexible and easy to use, we like. Official description: flexible view of a limited window for large amounts of data. How does it manage to cram a lot of data into the phone’s limited memory and keep it OOM free? Here around RecyclerView cache mechanism to talk about, RecyclerView recycling mechanism is what?

We print logs in the Adapter onCreateViewHolder() and onBindViewHolder(), where onCreateViewHolder() is called when a new view is created. OnBindViewHolder () is called when an existing view is bound to data. So, if it’s a newly created view, onCreateViewHolder() is called to create the view, and onBindViewHolder() is called to bind the data; If the view is reused, the onCreateViewHolder() creation method is not called, only onBindViewHolder() is called to bind the data.

    private int sum = 0;
	
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        Log.e("LinearVerticalAdapter"."onCreateViewHolder == " + sum);
        sum += 1; · · · · · ·return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Log.e("LinearVerticalAdapter"."onBindViewHolder"); ......}Copy the code

Print the following data when entering for the first time:

Here we see 13 data prints (my phone screen is full of 13 data), all using onCreateViewHolder() and onBindViewHolder() methods; When we scroll back and forth, only onBindViewHolder() is used to bind data, but onCreateViewHolder() is not used to create ViewHolder:

Because RecyclerView can recycle automatically, it must be supported by a powerful cache mechanism. The cache mechanism of RecyclerView is the core part of RecyclerView.

In order to facilitate the understanding of the following article, let’s first understand the meanings of several methods:

methods The corresponding Flag meaning A scenario
isInvalid() FLAG_INVALID The ViewHolder data is invalid 1. Call adapter setAdapter()

2. The Adapter calls notifyDataSetChanged();

3. Call RecyclerView invalidateItemDecorations ().
isRemoved() FLAG_REMOVED The ViewHolder has been removed and part of the source data has been removed The Adapter calls notifyItemRemoved()
isUpdated() FLAG_UPDATE The ViewHolder data for the item is out of date and needs to be rebound 1. All three cases of isInvalid() above;

2. Call adapter onBindViewHolder();

3. The Adapter notifyItemChanged() is called.
isBound() FLAG_BOUND The ViewHolder is already bound to an item at a location where the data is valid The onBindViewHolder() method is called

Two, several levels of Recycler cache

RecyclerView does not need to recycle logic like ListView does if(contentView== NULL) {}else{}. RecyclerView is recyclable. It is responsible for managing ViewHolder – scrapped or detached – for reuse. To understand the recycling principle of RecyclerView, first understand the Recycler structure:

 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;
    }
Copy the code

Recycler has four tiers of cache pools, each in order of priority: Scrap, CacheView, ViewCacheExtension, RecycledViewPool. Scrap includes mAttachedScrap and mChangedScrap, ViewCacheExtension is not implemented by default, it RecyclerView for developers to expand the recycling pool.

  • AttachedScrap: does not participate in the recycling of sliding, save only the invalid, unremoved, unupdated holder items separated from RecyclerView during relayout. Because RecyclerView in onLayout, will first remove all children, and then add into mAttachedScrap temporarily save these holder reuse.

  • ChangedScrap: MChangedScrap is similar to mAttachedScrap in that it does not participate in the reuse of sliding, but is only used as a temporary save variable. It will only be responsible for saving the invalid, unremoved holder of the item that changed during the relayout.

  • CachedViews: Used to save the latest ViewHolder removed and RecyclerView separated; If a new ViewHolder is needed for scrolling reuse, it will accurately match (according to position/ ID) the item that was removed. If so, the ViewHolder is returned directly without rebinding the data; If not, do not return, then go to mRecyclerPool to find the holder instance to return, and bind data again. The cache at this level is capacity limited, with a maximum of 2.

  • ViewCacheExtension: RecyclerView cache pool reserved for developers, developers can expand the recycle pool, generally not used, use RecyclerView system has enough.

  • RecyclerPool: Is the ultimate recycle bin, literally storing ViewHolder cache pools that have been identified as obsolete (no other pool wants to recycle), If a ViewHolder is not found on mAttachedScrap, mChangedScrap, mCachedViews, or mViewCacheExtension, then a discarded ViewHolder instance is returned from mRecyclerPool. However, the ViewHolder here has been erased without any trace of binding, and the data needs to be rebound. It is stored according to the itemType and holds the ViewHolder as SparseArray nested an ArraryList.

Let’s take a closer look at each cache pool:

2.1 Cache Pool (Scrap)

Scrap is the lightest RecyclerView cache, including mAttachedScrap and mChangedScrap. It does not participate in the recycling of list scrolling. It serves as a temporary cache for relayout. Caches viewholders that appear before and after the relayout of the interface. These Viewholers are invalid, unremoved, and unmarked. Of these invalid, unremoved, and unmarked Viewholders, mAttachedScrap is responsible for saving the viewholers that have not changed; The rest is saved by mChangedScrap. MAttachedScrap and mChangedScrap also work together to save different viewholders.

Note: Scrap is only used as a temporary cache for the layout, it has nothing to do with the cache when sliding, its detach and atach are only temporary during the layout. At the end of the layout, the Scrap list should be empty, and the cached data should either be rearranged or cleared; There should be nothing in the Scrap list after the layout is finished.

Our above analysis:

In a cell phone screen, remove the itemB and call the notifyItemRemoved() method. If the item is invalid and removed it is recycled to another cache, otherwise it is cached to the Scrap. So mAttachedScrap and mChangedScrap will store itemView separately, itemA will be stored in mAttachedScrap, itemB will be removed, but it will still be valid. Also stored in mAttachedScrap (but marked REMOVED); ItemC and itemD have changed, moved up, and will be stored in mChangedScrap. When deleted, ABCD will go into Scrap; After deletion, ACD will come back, A has no change, CD only changed the position, the content has not changed.

A partial refresh of RecyclerView is a temporary cache that relies on Scrap. When notifyItemRemoved(), notifyItemChanged(), The ViewHolder that has not changed is cached by mAttachedScrap, and the other ViewHolder is cached by mChangedScrap, which is quickly removed when adding an itemView to complete a partial refresh. Note that if we use notifyDataSetChanged() to notify RecyclerView refresh, the itemView on the screen is flagged as FLAG_INVALID and not removed, so Scrap cache is not used, Instead, throw it directly into the CacheView or RecycledViewPool and re-bind it when you come back.

Note: itemE does not appear on screen. It is not covered by Scrap. Scrap will only replace the holder of the itemView already loaded on screen.

2.2 Cache Pool 2 (CacheView)

CacheView is used to recycle views that have just moved off the screen when the RecyclerView list position changes. According to position/ ID, it will accurately match whether the original item is the same or not. If so, it will be directly returned to use without rebinding data. If not, go to RecycledViewPool to retrieve the holder instance and re-bind the data.

CacheView has a maximum capacity of 2. If a new ViewHolder is cached and the maximum size is exceeded, the CacheView cache’s first data is added to the RecycledViewPool and removed before a new ViewHolder is added. When we recycle RecyclerView, the Recycler can cache invisible views that have just been removed from the screen into CacheView. When CacheView reaches its limit, the Recycler can replace the ViewHolder in CacheView. Throw them into the RecycledViewPool. If you scroll in one direction all the time, CacheView does nothing to help efficiency. It simply caches ViewHolder that has been slid behind. If you scroll back and forth frequently, reuse of items from the CacheView for their location without rebinding will be a good use.

Here’s a picture of a CacheView reuse scenario:

As you can see in the figure, the CacheView caches a view that has just become invisible. If the current view enters the screen again and the itemView is matched precisely, the ViewHolder will be retrieved from the CacheView and reused. If you keep sliding in one direction, the CacheView will replace viewholders in the cache (CacheView can store up to two viewholders) and RecycledViewPool the viewholders. If you can’t get a ViewHolder in CacheView, you’ll have to get it in RecycledViewPool.

2.3 Cache Pool 3 (ViewCacheExtension)

ViewCacheExtension is a helper class for caching extensions that provides an additional layer of cache pooling for developers. Developers can decide whether to use ViewCacheExtension to increase the pool. Recycler can first look for recycled views in Scrap and CacheView. If not, Recycler can look for recycled views in ViewCacheExtension. Then finally go to RecycledViewPool to find reusable View. ViewCacheExtension is not covered in this tutorial, just so you know.

Note: Recycler does not cache any views into ViewCacheExtension. So no data is cached in ViewCacheExtension.

2.4 RecycledViewPool 4

RecycledViewPool RecycledViewPool is the ultimate Recycler when Scrap, CacheView and ViewCacheExtension are not recyclable.

RecycledViewPool actually saves ViewHolder as SparseArray nested ArraryList, because RecycledViewPool saves ViewHolder as itemType. This makes it convenient for different ItemTypes to hold different ViewHolder. It only retrieves the ViewHolder object of the viewType and does not store the original data. The onBindViewHolder() method is used to rebind the data.

RecycledViewPool RecycledViewPool

    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;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();
    }
Copy the code

SparseArray

mScrap RecycledViewPool SparseArray

mScrap RecycledViewPool ScrapData contains ArrayList

mScrapHeap. MScrapHeap is the ArrayList that holds the ViewHolder under the itemType.


DEFAULT_MAX_SCRAP = 5. This number does not mean that only one ViewHolder can be cached in the entire cache pool. Instead, it is the number of mScrap caches for lists of viewholders of different itemtypes. Note A maximum of five groups of different types of mScrapHeap can be created. MMaxScrap = DEFAULT_MAX_SCRAP By default, five ViewHolder viewholers of different types are saved. Of course, the value of mMaxScrap can be set. The RecycledViewPool can then cache ViewHolder of different viewtypes by type.

The Scrap cache pool is not used for rolling reuse. The CacheView cache pool is called level 1 cache, and the ViewCacheExtension cache pool is used for developers, so the RecycledViewPool is used as Level 2 cache. So there are really only two levels of caching.

Iii. Source code parsing (recycling and reuse)

Just look at the above explanation may be more abstract, stiff, do not understand the meaning of this paragraph expressed. Here we combine the source code to analyze RecyclerView recycling process, follow the source code you will understand the overall structure of RecyclerView cache. Taking LinearLayoutManager as an example, the layout process of RecyclerView is analyzed in The Drawing process and Sliding Principle of RecyclerView, but the recycling mechanism of RecyclerView is not involved. We know the layout of the RecyclerView and recovery and reuse are in RecyclerView. LayoutManager.

Warm prompt: in this paper, the source code based on androidx. Recyclerview: recyclerview: 1.2.0 – alpha01

3.1 Recycling Process

Inside the LinearLayoutManager, go to the itemView layout entry method onLayoutChildren() :

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if(mPendingSavedState ! =null|| mPendingScrollPosition ! = RecyclerView.NO_POSITION) {if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);// Remove all child views
                return;
            }
        }
        ensureLayoutState();
        mLayoutState.mRecycle = false;// Disallow collection
        // Draw the layout upside down
        resolveShouldLayoutReverse();
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);

        // Temporarily detach the attached view, detach all child detach and recycle it through Scrap
        detachAndScrapAttachedViews(recycler);
    }
Copy the code

RemoveAndRecycleAllViews () remove all child Views according to the actual situation, those ViewHolder is not available; Then through detachAndScrapAttachedViews () temporarily separation has additional ItemView, cache to the List.

If we insert items or delete items or shuffle the order of the list, how do we rearrange items? How do I place an existing item on the screen in a new location? The easiest way to do this is to separate each item from the screen, save it, and rearrange it according to its location.

DetachAndScrapAttachedViews () the role of the current screen all item and screen separation, will they get it from the layout of the RecyclerView, save to a list, in to layout, the ViewHolder again one by one into a new position. After taking the ViewHolder off the screen from the RecyclerView layout, store it in Scrap, which includes mAttachedScrap and mChangedScrap, which is a list, Used to hold off from RecyclerView layout ViewHolder list, detachAndScrapAttachedViews () will only be in onLayoutChildren () call, only in the layout, Note that Scrap only saves the ViewHolder of the item currently displayed on the RecyclerView layout, and does not participate in recycling. Pure for now from RecyclerView to take down and re-layout up. For items not saved, they will be put into mCachedViews or RecycledViewPool cache to participate in recycling and reuse.

   public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
        final int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            finalView v = getChildAt(i); scrapOrRecycleView(recycler, i, v); }}Copy the code

Loop through all views to separate Items that have been added to RecyclerView. Recycler wastes them and recycles them in the cache list.

   private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
        if(viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index);/ / remove the VIew
            recycler.recycleViewHolderInternal(viewHolder);// Cache to CacheView or RecycledViewPool
        } else {
            detachViewAt(index);/ / the View
            recycler.scrapView(view);/ / scrap cachemRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

DetachViewAt () and scrapView() to scrap: detachViewAt()

    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
        if(holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || ! holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { holder.setScrapContainer(this.false);
            mAttachedScrap.add(holder);// Save to mAttachedScrap
        } else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this.true);
            mChangedScrap.add(holder);// Save to mChangedScrap}}Copy the code

The ViewHolder going into the if() branch is saved to mAttachedScrap and the else branch is saved to mChangedScrap.

Back to scrapOrRecycleView (), enter the if () branch if viewHolder is invalid, has not been removed, not marked on the recycleViewHolderInternal cached (), While removeViewAt() removes the viewHolder,

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

                int cachedViewSize = mCachedViews.size();
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {// If the capacity limit is exceeded, remove the first one
                    recycleCachedViewAt(0); cachedViewSize--; }... mCachedViews. Add (targetCacheIndex, holder);/ / mCachedViews recycling
                cached = true;
            }
            if(! cached) { addViewHolderToRecycledViewPool(holder,true);// Add it to RecycledViewPool
                recycled = true; }}}Copy the code

If the conditions are met, mCachedViews will be cached preferentially. If the maximum limit of mCachedViews is exceeded, Recyecachedviewat () adds the first CacheView data to RecycledViewPool and removes it before adding ViewHolder () to mCachedViews.

Do not conform to the conditions of rest by addViewHolderToRecycledViewPool () to the RecycledViewPool cache.

    void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { clearNestedRecyclerViewIfNotNested(holder); View itemView = holder.itemView; · · · · · · holder. MOwnerRecyclerView =null;
        getRecycledViewPool().putRecycledView(holder);// Add holder to RecycledViewPool
    }
Copy the code

Fill () recycles out-of-screen Views into mCachedViews or RecycledViewPool:

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) { recycleByLayoutState(recycler, layoutState);// Recycle the view removed from the screen}}Copy the code

RecycleByLayoutState can be used to recycle recycler. RecycleView can be used to recycle recycler.

  public void recycleView(@NonNull View view) {
        ViewHolder holder = getChildViewHolderInt(view);
        if (holder.isTmpDetached()) {
            removeDetachedView(view, false);
        }
        recycleViewHolderInternal(holder);
    }
Copy the code

Separation recycling view to cache pool, convenient later rebinding and reuse, here again came to the recycleViewHolderInternal (holder), as well as the above, according to the priority of the cache mCachedViews > RecycledViewPool.

So that’s the end of the recycling process.

3.2 Reuse Process

After analyzing the recycling process of itemView, when and where should these recycled ViewHolder be taken out for use? Back to the LinearLayoutManager layout entry method onLayoutChildren() :

  @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if(mPendingSavedState ! =null|| mPendingScrollPosition ! = RecyclerView.NO_POSITION) {if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);// Remove all child views
                return; }}// Temporarily detach the attached view, detach all child detach and recycle it through Scrap
        detachAndScrapAttachedViews(recycler);
        
        if (mAnchorInfo.mLayoutFromEnd) {
            Fill in the ItemView layout from the start position
            updateLayoutStateToFillStart(mAnchorInfo);
            fill(recycler, mLayoutState, state, false);// Populate all ItemViews
           
 			Fill in the ItemView layout from the end position
            updateLayoutStateToFillEnd(mAnchorInfo);
            fill(recycler, mLayoutState, state, false);// Populate all ItemViews
            endOffset = mLayoutState.mOffset;
        }else {
            Fill in the ItemView layout from the end position
            updateLayoutStateToFillEnd(mAnchorInfo);
            fill(recycler, mLayoutState, state, false);
 
            Fill in the ItemView layout from the start position
            updateLayoutStateToFillStart(mAnchorInfo);
            fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; }}Copy the code

After the view is reclaimed, the view is populated. As mentioned above, the view is temporarily cached during the relayout and ViewHolder are populated one by one in the correct position. Fill () fills the given layout defined by layoutState:

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
        recycleByLayoutState(recycler, layoutState);// Retrieve the view that slides out of the screen
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {// Loop until there is no data
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);// Add a child· · · · · ·if (layoutChunkResult.mFinished) {// The layout ends, and the loop exits
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;// Based on the height offset of the added child} · · · · · ·return start - layoutState.mAvailable;// Returns the size of the region to be filled this time
    }
Copy the code

While () executes layoutChunk() to fill an itemView to the screen. LayoutChunk () completes the layout:

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);// Get the view for reuse......}Copy the code

This method can be used by layoutState.next(Recycler) to get views, let’s see how it can get views:

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

    @NonNull
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }

    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
Copy the code

TryGetViewHolderForPositionByDeadline () method is to obtain the view, it will be given according to the position/id to scrap, cache, RecycledViewPool, or create a get a ViewHolder:

    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
        ViewHolder holder = null;
        // 0) If it is changed to scrap ViewHolder, look on scrap mChangedScrap
        if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null;
        }
        // 1) Select mAttachedScrap, mChildHelper, mCachedViews, respectively, according to position
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }

        if (holder == null) {
            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2) Select mAttachedScrap, mCachedViews by ID
            if (mAdapter.hasStableIds()) {
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
            }
            if (holder == null&& mViewCacheExtension ! =null) {
                //3) Query in ViewCacheExtension
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if(view ! =null) { holder = getChildViewHolder(view); }}// RecycledViewPool RecycledViewPool
            holder = getRecycledViewPool().getRecycledView(type);
            if(holder ! =null) {
                holder.resetInternal();
                if(FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); }}}//5) Create a new ViewHolder if no reuse ViewHolder is found
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
Copy the code

This method does a number of things: retrieve the ViewHolder from scrap, CacheView, ViewCacheExtension, and RecycledViewPool. If not, create a new ViewHolder.

Step 1: If the ViewHolder is discarded and changed, find the mChangedScrap view on the Scrap by Position and ID respectively. When we call the Adapter notifyItemChanged() method, the data changes, the item is cached in mChangedScrap, and subsequent ViewHolder data needs to be rebound.

   ViewHolder getChangedScrapViewForPosition(int position) {
        / / by position
        for (int i = 0; i < changedScrapSize; i++) {
            final ViewHolder holder = mChangedScrap.get(i);
            return holder;
        }
        / / by id
        if (mAdapter.hasStableIds()) {
            final long id = mAdapter.getItemId(offsetPosition);
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
                returnholder; }}return null;
    }
Copy the code

Step 2: If no view is found, search for mAttachedScrap, mChildHelper, and mCachedViews of the Scrap according to position. In getScrapOrHiddenOrCachedHolderForPosition (position, dryRun) this method to find in the following order:

  • First of all, from themAttachedScrapFind, accurately match valid ViewHolder;
  • Then, inmChildHelperIn themHiddenViewsFind hidden ViewHolder;
  • And then finally from our level 1 cachemCachedViewsLookup.
    // Select mAttachedScrap, mChildHelper, mCachedViews, respectively, according to position
    ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
        final int scrapCount = mAttachedScrap.size();

        // Select valid ViewHolder from mAttachedScrap
        for (int i = 0; i < scrapCount; i++) {
            final ViewHolder holder = mAttachedScrap.get(i);
            return holder;
        }
        // Then mHiddenViews looks for the hidden ViewHolder in mChildHelper
        if(! dryRun) { View view = mChildHelper.findHiddenNonRemovedView(position);if(view ! =null) {
                final ViewHolder vh = getChildViewHolderInt(view);
                scrapView(view);
                returnvh; }}// Finally look up mCachedViews from our level 1 cache.
        final int cacheSize = mCachedViews.size();
        for (int i = 0; i < cacheSize; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            returnholder; }}Copy the code

Step 3: If no view is found, search for mAttachedScrap and mCachedViews of Scrap by ID. This method the getScrapOrCachedViewForId () in the following order:

  • First of all, from themAttachedScrapFind, accurately match valid ViewHolder;
  • And then from our level 1 cachemCachedViewsLook for;

Note: this step is based on id, similar to the previous step based on position.

  ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
        // Select mAttachedScrap from Scrap
        final int count = mAttachedScrap.size();
        for (int i = count - 1; i >= 0; i--) {
            final ViewHolder holder = mAttachedScrap.get(i);
            return holder;
        }

        // Look in the level 1 cache mCachedViews
        final int cacheSize = mCachedViews.size();
        for (int i = cacheSize - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            returnholder; }}Copy the code

Step 4: Search for Recycler in mViewCacheExtension. This cache pool is a layer of caching policy defined by developers. Recycler doesn’t cache any views there. It’s not defined here, so you can’t find the corresponding view.

if (holder == null&& mViewCacheExtension ! =null) {
        final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
        if(view ! =null) { holder = getChildViewHolder(view); }}Copy the code

Step 5: RecycledViewPool, which caches the ViewHolder’s List into SparseArray using the itemType, GetRecycledViewPool ().getRecycledView(type) ScrapData from SparseArray and ArrayList

To get the ViewHolder.

    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);// Get the corresponding ScrapData according to the viewType
        if(scrapData ! =null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            for (int i = scrapHeap.size() - 1; i >= 0; i--) {
                if(! scrapHeap.get(i).isAttachedToTransitionOverlay()) {returnscrapHeap.remove(i); }}}return null;
    }
Copy the code

Step 6: if you don’t have access to the ViewHolder, by mAdapter. CreateViewHolder () to create a new ViewHolder returns.

  //5) Create a new ViewHolder if no reuse ViewHolder is found
  holder = mAdapter.createViewHolder(RecyclerView.this, type);
Copy the code

So this is the end of the reuse process. The whole process is roughly as follows:

Four,

4.1 Recycle principle of RecyclerVIew

When RecyclerView is rearranging onLayoutChildren() or filling layout fill(), it will first separate or remove the necessary items from the screen, mark them and save them in the list. During the relayout, Take the ViewHolde out and put it one by one in a new place.

(1) If RecyclerView is not scrolling cache (such as delete item), when relayout, the ViewHolder on the screen is separated from the screen and stored in Scrap, that is, the changed ViewHolder is cached in mChangedScrap. The ViewHolder with no change is stored in mAttachedScrap; The remaining ViewHolder will be cached in mCachedViews or RecycledViewPool according to the priority of mCachedViews>RecycledViewPool.

(2) If it is RecyclerVIew scrolling cache (such as sliding list), fill the layout when sliding, remove items that slide out of the screen first, the first-level cache mCachedViews cache these ViewHolder first, but the maximum capacity of mCachedViews is 2, When mCachedViews is full, the old ViewHolder will be stored in RecycledViewPool and removed to make space, and then the new ViewHolder will be added to mCachedViews. The remaining ViewHolder will be cached in the RecycledViewPool, which will cache different types of ArrayList

based on itemType. The maximum size is 5.

4.2 Reuse principle of RecyclerVIew

So far, there are five cache recyclerPool, mChangedScrap, mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool, In addition to mViewCacheExtension, which is provided by the system for developers to extend and is not used, there are four pools that participate in the reuse process.

(1) When RecyclerView needs to take a reusable ViewHolder, if it is preloaded, it will go to the mChangedScrap to find the corresponding ViewHolder accurately (according to position and ID respectively), if there is, it will return;

(2) If not, check the mAttachedScrap and mCachedViews to see if the ViewHolder is the same as the original ViewHolder. If yes, the ViewHolder has just been removed.

(3) If not, then go to mRecyclerPool. If itemType matches the ViewHolder, then return the instance and let it rebind the data.

If mRecyclerPool does not return ViewHolder, createViewHolder() will be used to create a new one.

Note here: In mChangedScrap, mAttachedScrap, and mCachedViews, the ViewHolder is an exact match, but the mChangedScrap has changed. Call onBindViewHolder() to rebind the data. MAttachedScrap and mCachedViews have not changed, they are used directly and do not need to bind data again, while ViewHolder content information in mRecyclerPool has been erased, so data needs to be bound again. Therefore, when RecyclerView is rolling back and forth, mCachedViews cache pool is the most efficient.

In summary:RecyclerViewFocus on two scenarios of cache and recovery optimization, one is: when data is updated, useScrapLocal update, reuse the original viewHolder as much as possible, reduce binding data work; Second, the original ViewHolder is reused during sliding to minimize the duplication of creating ViewHolder and binding data. The final idea is to create if you can, and rebind if you can, to minimize duplication of unnecessary work.

Pay attention and don’t get lost


Well everyone, that’s all for this article. Thank you very much for reading it. I am Suming, thank you for your support and recognition, your praise is the biggest motivation for my creation. Landscape has to meet, we will see you in the next article!

My level is limited, the article will inevitably have mistakes, please criticism correction, very grateful!

I hope we can be friends inGithub,The Denver nuggetsShare knowledge together and encourage each other! Keep Moving!