Cache is an important reason of RecyclerView time performance. The cache pool is the slowest of all caches, and the ViewHodler in it is dirty and onBindViewHolder() has to be re-executed. This article starts with the source code to explore when “entries are recycled into the cache pool”.

Cache pool structure

Before looking at the different reclamation scenarios, let’s review the question “What is a cache pool?”

Entries are recycled into the cache pool, and entries in the source code are stored in the RecycledViewPool as ViewHolder instances:

public class RecyclerView {
    public final class Recycler {
        // Reclaim entry view
        public void recycleView(@NonNull View view) {
            ViewHolder holder = getChildViewHolderInt(view);
            // Retrieve the entry ViewHolder
            recycleViewHolderInternal(holder);
        }
        / / recycling ViewHolder
        void recycleViewHolderInternal(ViewHolder holder) {...// Store ViewHolder into the cache pool
            addViewHolderToRecycledViewPool(holder, true);
        }

        // Store ViewHolder instances into RecycledViewPool structures
        void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {... getRecycledViewPool().putRecycledView(holder); }// Get RecycledViewPool instance
        RecycledViewPool getRecycledViewPool(a) {
            if (mRecyclerPool == null) {
                mRecyclerPool = new RecycledViewPool();
            }
            returnmRecyclerPool; }}/ / the buffer pool
    public static class RecycledViewPool {
        // List of single-type caches
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        }
        // Cache pool with multiple types of cache lists (int as key)
        SparseArray<ScrapData> mScrap = new SparseArray<>();
        public void putRecycledView(ViewHolder scrap) {
            // Get the ViewHolder type
            final int viewType = scrap.getItemViewType();
            // Get the ViewHolder cache list of the specified type
            finalArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; .// The ViewHolder instance is stored in the cache listscrapHeap.add(scrap); }}}Copy the code

RecycledViewPool uses a SparseArray to cache different types of ViewHolder instances in memory, with a list for each type. When an entry of the same type is inserted into the list, instead of recreating the ViewHolder instance (onCreateViewHolder()), it can be retrieved from the cache pool.

Detailed resolution about the buffer pool can click RecyclerView caching mechanism | recycling where?

1. Entries are removed from the screen

This scenario of reclaiming entries is the most common. The renderings are as follows:

Why is item 1 recycled right after Item 3 rolls off the screen, and item 2 recycled right after item 4 rolls off the screen?

This is because of mCachedViews, which is a list with a default size of 2. The ViewHolder used to cache the off-screen entry.

All removed entries will be cached in sequence. When mCachedViews is full, the ViewHolder instance stored first will be removed and transferred to RecycledViewPool (cache pool) according to the fifo principle.

So when items 1 and 2 move off the screen, they fill up mCachedViews, and when item 3 moves off the screen, item 1 gets squeezed out and stored in the cache pool. More detailed source tracing analysis can click RecyclerView caching mechanism | recycling where?

So how does RecyclerView determine which entries should be recycled in the process of scrolling?

In the last article, I analyzed in detail how the entries are recycled when the list is scrolling. Now I cite the conclusion and illustration as follows.

  1. RecyclerView determines how many new entries need to be filled into the list based on the expected scrolling displacement before scrolling occurs. When filling entries, the entries are reclaimed based on the limit invisible line.

  2. Limit invisible line is a line calculated by RecyclerView before rolling occurs according to the rolling displacement, it is an important basis to decide which entries should be recycled. It can be interpreted as: the current position of the invisible line will overlap with the top of the list after scrolling.

  3. Limit The initial value of the invisible line is the distance from the bottom of the visible entry to the bottom of the list. That is, the maximum distance that the list can slide without filling new entries. The pixel value consumed by each newly populated entry is appended to the limit value, which means that the invisible line of the limit is continuously moved down as new entries are populated.

  4. When the reclaim logic is triggered, all current entries are traversed. If the bottom of an entry is below the limit invisible line, all entries above the entry are reclaimed.

This is illustrated graphically in the figure belowLimit contact line(Red dotted line in figure) :

The collection logic is implemented in the source code, which is the following call chain (0-5) :

public class RecyclerView {
    public final class Recycler {
        / / 5
        public void recycleView(View view) {...}
    }
    
    public abstract static class LayoutManager {
        public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
            final View view = getChildAt(index);
            removeViewAt(index);
            / / 4recycler.recycleView(view); }}}public class LinearLayoutManager {
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        // 3: reclaims the entries whose indexes are endindex-1 to startIndex
        for (int i = endIndex - 1; i >= startIndex; i--) { removeAndRecycleViewAt(i, recycler); }}private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace) {...for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                / / 2
                recycleChildren(recycler, 0, i); }}}private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        / / 1
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
    
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...// Cycle the entry
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            // Populate a single entrylayoutChunk(recycler, state, layoutState, layoutChunkResult); .if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed;// 0: Reclaims the entryrecycleByLayoutState(recycler, layoutState); }... }}}Copy the code

Each entry is filled with all loaded entries to check whether any of them can be reclaimed.

If interested in conclusion source code analysis, you can click RecyclerView interview questions | rolling table when the item is being filled or recycled?

2. The entry is squeezed out of the screen

Item reclamation also occurs when an entry is inserted into the list, forcing the existing entry out of the screen. The renderings are as follows:

In this scenario, item 2 will be recycled, and when the animation of the entry is complete, the item recycling logic will be touched:

// RecyclerView Default item animator
public class DefaultItemAnimator extends SimpleItemAnimator {
    // Start the entry shift animation
    void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final ViewPropertyAnimator animation = view.animate();
        animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animator) {
                // Distribute the animation to end the eventdispatchMoveFinished(holder); . } }).start(); }}public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
    public final void dispatchMoveFinished(RecyclerView.ViewHolder item) {
        // Go ahead and distribute the animation ending eventdispatchAnimationFinished(item); }}public class RecyclerView {
    public abstract static class ItemAnimator {
        private ItemAnimatorListener mListener = null;
        public final void dispatchAnimationFinished(ViewHolder viewHolder) {
            // Distributes the end of animation event to the listener
            if(mListener ! =null) { mListener.onAnimationFinished(viewHolder); }}}private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
        @Override
        public void onAnimationFinished(ViewHolder item) {
            // Set ViewHolder to be recyclable
            item.setIsRecyclable(true);
            // Reclaim the entry
            if(! removeAnimatingView(item.itemView) && item.isTmpDetached()) { removeDetachedView(item.itemView,false); }}}boolean removeAnimatingView(View view) {
        startInterceptRequestLayout();
        final boolean removed = mChildHelper.removeViewIfHidden(view);
        // The entry does move off the screen after it is animated
        if (removed) {
            final ViewHolder viewHolder = getChildViewHolderInt(view);
            mRecycler.unscrapView(viewHolder);
            / / recycling ViewHoldermRecycler.recycleViewHolderInternal(viewHolder); }...returnremoved; }}Copy the code

RecyclerView’s RecyclerView animator passes the end of moving items to RecyclerView’s internal listeners, who can notify the Recycler to recycle.

3. The Cache hit ViewHolder becomes dirty

Dirty means that the entry needs to be redrawn, that is, onBindViewHolder() is called to rebind the data for the entry.

There is a tier 4 cache in RecyclerView, which prioritizes looking for ViewHolder instances in the cache. The cache pool is the slowest of them, because the ViewHolder taken from it needs to be re-executed onBindViewHolder(). Scrap and View cache are both faster, but additional checks are required for hits (for more information on tier 4 caches, click here) :

public class RecyclerView
    public final class Recycler {
        // RecyclerView access to ViewHolder
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
        	// Get the ViewHolder instance from the Scrap or View cache
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                // If the cache is hit
                if(holder ! =null) {
                    / / check ViewHolder
                    if(! validateViewHolderForOffsetPosition(holder)) {// Verification failed
                        if(! dryRun) {// dryRun is always false.// Retrieve hit ViewHolder (drop into cache pool)
                            recycleViewHolderInternal(holder);
                        }
                        // Failed to fetch the cache from scrap or View cache
                        // Triggers the continuation of the ViewHolder instance from another cache
                        holder = null;
                    } else {
                    	// The verification is successful
                        fromScrapOrHiddenOrCache = true; }}... }}}Copy the code

ViewHolder hit from Scrap or View Cache is validated in three ways:

  1. Whether the entry is removed
  2. Check whether the viewtypes of the entries are the same
  3. Check whether the entry ids are the same
public class RecyclerView{
    public final class Recycler {
        // Verify ViewHolder validity
        boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
            // If the entry has been removed
            if (holder.isRemoved()) {
                // Is in the preLayout phase
                return mState.isPreLayout();
            }

            if(! mState.isPreLayout()) {// Check whether the ViewHolder retrieved from the cache has the same viewType as the ViewHolder at the location of the Adapter
                final int type = mAdapter.getItemViewType(holder.mPosition);
                if(type ! = holder.getItemViewType()) {return false; }}// Check whether the ViewHolder retrieved from the cache has the same ID as the ViewHolder at the location of the Adapter
            if (mAdapter.hasStableIds()) {
                return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
            }
            return true; }}}Copy the code

Hit caches in scrap and View cache will only be used if they have the same viewType or ID as the specified location entry. Otherwise the ViewHolder will be deemed invalid even if hit and thrown into the cache pool.

4. The cached entries in mCachedViews are deleted

After the entry is removed from the screen, it is immediately recycled into the mCachedViews structure. If the entry is deleted again, the ViewHolder corresponding to the entry is removed from the mCachedViews structure and added to the cache pool:

public class RecyclerView {
    public final class Recycler {
        void recycleCachedViewAt(int cachedViewIndex) {
            // Get the ViewHolder instance at the specified location from the mCacheViews structure
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            // Store ViewHolder into the cache pool
            addViewHolderToRecycledViewPool(viewHolder, true);
            // Remove ViewHolder from mCacheViews
            mCachedViews.remove(cachedViewIndex);
        }
        
        void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {... getRecycledViewPool().putRecycledView(holder); }}}Copy the code

5. The extra entries in pre-layout are removed from post-Layout

pre-layout & post-layout

The pre – layout and post – layout in RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache has introduced, quoted as follows:

RecyclerView is going to animate entries,

To determine the type and end point of the animation, you need to compare two “entry snapshots” before and after the animation

To get two snapshots, you need to do a pre-layout and a post-layout (layout is filling a list with entries).

In order for the two layouts to not affect each other, you have to erase the contents of the previous layout before each layout (like cleaning a canvas and repainting it),

However, some of the entries needed in both layouts are likely to be the same, and if all information about the entries is erased when the canvas is cleared, it will take more time to redraw (recreate the ViewHolder and bind the data).

RecyclerView adopts the practice of using space for time: the table items are cached in scrap cache when the canvas is cleared, so that the filling table items can hit the cache and shorten the filling time.

In the Gif scene, item 1, item 2, and item 3 are filled into the list during the pre-layout stage, forming a snapshot of the items before animation. Post-layout fills item 1 and item 3 into the list to create an animated snapshot of the entries.

Compare the position of item 3 in these two snapshots to see where it should be panted to and where it should be panted to. Also, item 2 needs to be animated to disappear. When the animation is over, item 2’s ViewHolder will be reclaimed to the cache pool with the same call chain as “the item is squeezed out of the screen”. Both are triggered by the end of the animation.

Populate additional entries in the pre-layout phase

Consider another scenario. Instead of removing item 2, we update it. For example, if we update item 2 to item 2.1, will pre-Layout still populate item 3 in the list?

RecyclerView animation principle | change the posture to see the source code (pre – layout) are analyzed in detail, in the pre – layout stage, how the additional list items are filled to the list, again took out look at the key source code:

public class LinearLayoutManager{
    // Populate the list with entries
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {...// Calculate the remaining space
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        // Cycle through the entries until there is no space left
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            // Populate a single entrylayoutChunk(recycler, state, layoutState, layoutChunkResult); .// Deduct the space used to fill the entry from the remaining space in the list
            if(! layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList ! =null| |! state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; remainingSpace -= layoutChunkResult.mConsumed; }... }... }}Copy the code

Intuitively, the space consumed by each populated entry should be deducted, but the deduction logic is wrapped in an if, that is, the deduction is conditional.

There are three conditions in a conditional expression, in the pre-layout phase! State.isprelayout () must be false, layoutstate.mscraplist! = null is also false (breakpoint tells me), the last condition! LayoutChunkResult mIgnoreConsumed played a decisive role, it is filled with a single table item assigned:

public class LinearLayoutManager {
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
        // Get the next entry view to be populatedView view = layoutState.next(recycler); .// Omits the specific logic for implementing the population
        MIgnoreConsumed is set to true if the entry is removed or updated
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true; }... }}Copy the code

LayoutChunkResult was passed as a parameter to layoutChunk (), and when the filling table item is to be deleted or updated, will layoutChunkResult. MIgnoreConsumed set to true. Indicates that this entry is populated in the list but the space it occupies should be ignored. Thus it can be concluded that:

During the pre-layout phase, if an entry is removed or updated, the space it occupies is ignored, and the extra space is used to load additional entries that are off-screen and would not otherwise be loaded.

While this conclusion is the intent of the code, there is one thing I don’t understand. It is easy to ignore the space occupied by the removed entries, so why are updated entries also ignored?

That is because, depending on the implementation of onBindViewHolder(), the layout of an entry may change when it is updated, causing other entries to be squeezed out of the screen if the entry layout becomes longer, or new entries to be moved onto the screen if the entry layout becomes shorter.

Record entry animation information

In RecyclerView animation principle | how to store and use animation attribute values? RecyclerView is how to store the value of animation properties, now quoted as follows:

  1. RecyclerView encapsulates the item animation data in two layers, which are ItemHolderInfo and InfoRecord successively. They record the position information of the pre-layout and post-layout items of the list, that is, the position of the rectangle area of the item relative to the upper left corner of the list. It also uses a flag bit of type int to record which layout stages the entry has gone through to determine what type of animation the entry should do (appear, disappear, hold).

  2. InfoRecord is centrally stored in a store class, ViewInfoStore. ViewHolder and InfoRecord of all table entries participating in the animation are stored as key-value pairs.

  3. RecyclerView in the third stage of the layout will iterate over all the key-value pairs in the store class and determine which animation to execute based on the flag bits in the InfoRecord. List items after preliminary layout and the layout of the position information will be passed to the RecyclerView. ItemAnimator, to trigger the animations.

In the pre-layout stage, the code for storing animation information is as follows:

public class RecyclerView {
    private void dispatchLayoutStep1(a) {...// Iterate over the existing entries in the list
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                // Build an ItemHolderInfo instance for the entry
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),holder.getUnmodifiedPayloads());
                // Store the ItemHolderInfo instance into ViewInfoStoremViewInfoStore.addToPreLayout(holder, animationInfo); }.../ / layout
            mLayout.onLayoutChildren(mRecycler, mState);
            // After prelayout, iterate over all children again (prelayout may be filled with additional entries)
            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                // Filter out entries with the FLAG_PRE flag
                if(! mViewInfoStore.isInPreLayout(viewHolder)) {// Builds an ItemHolderInfo instance for the extra populated entries
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    // Store the ItemHolderInfo instance into ViewInfoStoremViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); }}... }}class ViewInfoStore {
    void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info;
        // Add the FLAG_PRE flag
        record.flags |= FLAG_PRE;
    }
    
    void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        // Add the FLAG_APPEAR flagrecord.flags |= FLAG_APPEAR; record.preInfo = info; }}Copy the code

Before and after the pre-layout, the entry is traversed twice.

For the first iteration of the Demo scene, the animation properties of items 1 and 2 are stored in ViewInfoStore and the FLAG_PRE flag bit is added. At the end of the walk, the pre-layout is performed to populate the list with items 3 that are not on screen. In the second iteration, the animation properties of item 3 are also stored in ViewInfoStore and the FLAG_APPEAR bit is added to indicate that the item was filled in during the pre-layout.

In the post-layout phase, to create an animated snapshot of the entries, the list is emptied and the entries are repopulated. For the sake of time performance, the ViewHolder of the removed entries is cached in the Scrap structure (ViewHodler instances for item 1, 2 and 3).

Repopulate the list with item 1 and updated item 2. Their ViewHolder instances can be quickly retrieved from the Scrap structure without having to execute onCreateViewHolder(). After populating, the list runs out of space, and there is one ViewHolder instance of Item 3 left in the Scrap structure. It adds a new flag bit in the post-Layout phase:

public class LinearLayoutManager {
    // Call onLayoutChildren() a second time in dispatchLayoutStep2() for post-layout
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...// Layout for animation
        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    }
    
    private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,RecyclerView.State state, int startOffset,int endOffset) {
        final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
        final int scrapSize = scrapList.size();
        // Walk through the scrap structure
        for (int i = 0; i < scrapSize; i++) {
            RecyclerView.ViewHolder scrap = scrapList.get(i);
            final int position = scrap.getLayoutPosition();
            final intdirection = position < firstChildPos ! = mShouldReverseLayout? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;// Calculate the space occupied by the corresponding entry in the scrap structure
            if(direction == LayoutState.LAYOUT_START) { scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);  }else{ scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); }}// mlayoutstate. mScrapList is assigned
        mLayoutState.mScrapList = scrapList;
        // Try populating the entry again
        if (scrapExtraStart > 0) {... fill(recycler, mLayoutState, state,false);
        }

        if (scrapExtraEnd > 0) {... fill(recycler, mLayoutState, state,false);
        }
        mLayoutState.mScrapList = null;
    }
    
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        // Branch 1: populates the list with entries
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0); }}// Branch 2: Stores the entry animation information in ViewInfoStore
        else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
                // Delegate to the parent class LayoutManger
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0); }}... }}Copy the code

This time the layoutChunk() is populated because layoutstate.mscraplist is not empty and will go to a different branch, i.e. call addDisappearingView() :

public class RecyclerView {
    public abstract static class LayoutManager {
        public void addDisappearingView(View child) {
            addDisappearingView(child, -1);
        }
        
        public void addDisappearingView(View child, int index) {
            addViewInt(child, index, true);
        }
        
        private void addViewInt(View child, int index, boolean disappearing) {
            final ViewHolder holder = getChildViewHolderInt(child);
            if (disappearing || holder.isRemoved()) {
                // Set flag_lights
                mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
            } else{ mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); }... }}}class ViewInfoStore {
    // Set flag_lights
    void addToDisappearedInLayout(RecyclerView.ViewHolder holder) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) { record = InfoRecord.obtain(); mLayoutHolderMap.put(holder, record); } record.flags |= FLAG_DISAPPEARED; }"Copy the code

After item 3 has gone through pre-layout and post-layout, its animation information is stored in ViewInfoStore and two flag bits are added, FLAG_APPEAR and FLAG_disappear.

In the third phase of the layout, viewinfostore.process () is called to trigger the animation:

public class RecyclerView {
    private void dispatchLayoutStep3(a) {...// Touch publish item execution animationmViewInfoStore.process(mViewInfoProcessCallback); . }}class ViewInfoStore {
    void process(ProcessCallback callback) {
        // Iterate over the positions of all participating entries
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            // Get the entry ViewHolder
            final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            // Get the animation information corresponding to ViewHolder
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            // Determine the animation type based on the flag bit of the animation information to execute the corresponding ProcessCallback callback
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                callback.unused(viewHolder);
            } else if((record.flags & FLAG_DISAPPEARED) ! =0) {... }}}}Copy the code

Item 3 in Demo hits the first if condition because:

class ViewInfoStore {
    static class InfoRecord {
        // Disappear in post-layout
        static final int FLAG_DISAPPEARED = 1;
        // Appear in pre-layout
        static final int FLAG_APPEAR = 1 << 1;
        // A combination of the two
        static final intFLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED; }}Copy the code

The logic for retrieving item 3 to the cache pool is in callback.unused(viewHolder) :

public class RecyclerView {
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = new ViewInfoStore.ProcessCallback() {
                ...
                @Override
                public void unused(ViewHolder viewHolder) {
                    // Reclaim unused entriesmLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); }};public abstract static class LayoutManager {
        public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) {
            removeView(child);
            // Delegate to Recyclerrecycler.recycleView(child); }}public final class Recycler {
            public void recycleView(@NonNull View view) {
                // Reclaim the entry to the cache pool
                recycleViewHolderInternal()
        }
    }
}
Copy the code

Thus it can be concluded that:

Any entries that are added to the list during the pre-layout phase and are not eventually added to the list during the post-Layout phase will be returned to the cache pool.

Recommended reading

RecyclerView series article directory is as follows:

  1. RecyclerView caching mechanism | how to reuse table?

  2. What RecyclerView caching mechanism | recycling?

  3. RecyclerView caching mechanism | recycling where?

  4. RecyclerView caching mechanism | scrap the view of life cycle

  5. Read the source code long knowledge better RecyclerView | click listener

  6. Proxy mode application | every time for the new type RecyclerView is crazy

  7. Better RecyclerView table sub control click listener

  8. More efficient refresh RecyclerView | DiffUtil secondary packaging

  9. Change an idea, super simple RecyclerView preloading

  10. RecyclerView animation principle | change the posture to see the source code (pre – layout)

  11. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

  12. RecyclerView animation principle | how to store and use animation attribute values?

  13. RecyclerView list of interview questions | scroll, how the list items are filled or recycled?

  14. RecyclerView interview question | what item in the table below is recycled to the cache pool?

  15. RecyclerView performance optimization | to halve load time table item (a)

  16. RecyclerView performance optimization | to halve load time table item (2)

  17. RecyclerView performance optimization | to halve load time table item (3)

  18. How does RecyclerView roll? (a) | unlock reading source new posture

  19. RecyclerView how to achieve the scrolling? (2) | Fling