RecyclerView memory performance is superior, which benefits from its unique cache mechanism, this article explores the cache mechanism of RecyclerView in the way of the source code.

RecyclerView Caching this is the first article in a RecyclerView cache series.

  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. RecyclerView animation principle | pre – layout, post – the relationship between the layout and scrap the cache

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

If you want to go straight to the conclusion, go to the end of Canto IV (you’ll regret it, it’s even more exciting).

primers

  • It would be uneconomical to destroy every off-screen entry in the list and recreate it when moving in. So RecyclerView introduces a caching mechanism.
  • Recycling is for reuse, and the benefit of reuse is that it is possible to avoid two expensive operations:
    1. Bind data to the entry view
    2. Create an entry view
  • The following questions are key to understanding the reuse mechanism:
    1. Recycle what? Reuse what?
    2. Where to recycle? Where do you get reuse?
    3. When to recycle? When will it be reused?

This article tries to start from the known knowledge in the source code looking for the unknown “RecyclerView reuse mechanism”.

(PS: The bold italics below indicate the inner drama that guides the source reading)

Looking for

One of the many occasions when reuse is triggered is “when an off-screen entry returns to the interface”. An entry is essentially a View, and an entry on the screen must be attached to a View tree, which means it must be called by a parent containeraddView(). whileRecyclerViewInherited fromViewGroup, hence toRecyclerView.addView()Look up reused code for pointcuts.

RecyclerView() : RecyclerView(); RecyclerView() : RecyclerView();

RecyclerView is a subclass of ViewGroup
public class RecyclerView extends ViewGroup implements ScrollingView.NestedScrollingChild2 {...private void initChildrenHelper(a) {
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            ...
            @Override
            public void addView(View child, int index) {
                if (VERBOSE_TRACING) {
                    TraceCompat.beginSection("RV addView");
                }
                // Call viewGroup.addView () directly
                RecyclerView.this.addView(child, index);
                if(VERBOSE_TRACING) { TraceCompat.endSection(); } dispatchChildAttached(child); }}}... }Copy the code

To ChildHelper. Callback. AddView () as the starting point up the call chains continue to search for, through the following method call:

  • ChildHelper.addView()
  • LayoutManager.addViewInt()
  • LayoutManager.addView()
  • LinearLayoutManager.layoutChunk():
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
        // Get the next entryView view = layoutState.next(recycler); . LayoutParams params = (LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {
            // Insert the entry into the list
            if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0); }}... }Copy the code

The view passed to addView(view) is the return value of the function layoutstate.next (). Guess that this function is used to get the next entry. There is more than one entry. There should be a loop to obtain the next entry. Search further up the chain of calls and you’ll find that there is indeed a loop!

public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler.RecyclerView.SmoothScroller.ScrollVectorProvider {...int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {...Recyclerview The remaining space
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        // Keep filling until the space is exhausted
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            // Fill an entrylayoutChunk(recycler, state, layoutState, layoutChunkResult); . }... }}Copy the code

Fill () is called in onLayoutChildren() :

/** * Lay out all relevant child views from the given adapter. */
public void onLayoutChildren(Recycler recycler, State state) {
 	Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
Copy the code

After reading the comments, I feel that the previous guess should be correct. OnLayoutChildren () is used to layout all the entries in RecyclerView. Looking back at layoutstate.next (), the entry reuse logic should be there.

public class LinearLayoutManager {
    static class LayoutState {       
       /** * Gets the view for the next element that we should layout. */
        View next(RecyclerView.Recycler recycler) {
            if(mScrapList ! =null) {
                return nextViewFromScrapList();
            }
            / / call the Recycler. GetViewForPosition ()
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            returnview; }}}Copy the code

Finally call the Recycler. GetViewForPosition (), Recycler is the meaning of the collector, from looking for feeling more and more closer to the “reuse” logic. What exactly is Recycler used for? :

public class RecyclerView {
    /** * A Recycler is responsible for managing scrapped or detached item views for reuse. * Recycler is responsible for managing the reuse of scrapped and detached items */
    public final class Recycler {... }}Copy the code

Finally found you ~~, used for table reuse! Along the Recycler. GetViewForPosition () invocation chain to continue down the search, found a key function (function is too long, to prevent dizziness, only lists the key node) :

public class RecyclerView {
    public final class Recycler {
       /** * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, * cache, * Try to get a ViewHolder from the Scrap, cache, or RecycledViewPool, or create it directly. */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            //0 Get ViewHolder from the Changed Scrap collection
            if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null;
            }
            //1. Obtain the ViewHolder from attach Scrap or level 1 reclaim cache by position
            if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); . }if (holder == null) {...final int type = mAdapter.getItemViewType(offsetPosition);
                //2. Look for a viewHolder in the Attach Scrap collection and the level 1 recycle cache by id
                if(mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); . }//3. Get a ViewHolder from the custom cache
                if (holder == null&& mViewCacheExtension ! =null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type); . }//4. Get a ViewHolder from the cache pool
                if (holder == null) { // fallback to pool. holder = getRecycledViewPool().getRecycledView(type); . }// All caches are not hit, and only a ViewHolder can be created
                if (holder == null) {... holder = mAdapter.createViewHolder(RecyclerView.this, type); . }}boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            }
            // Only an invalid viewHolder can bind view data
            else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // After obtaining the ViewHolder, bind the view databound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }...returnholder; }}}Copy the code
  • The name of the function begins with “tryGet”, which means it may fail, and the comment says: “Try to get a ViewHolder for the specified position, either from scrap, cache, RecycledViewPool, or from scratch.”Scrap, cache, RecycledViewPool Is used to recycle entries. It is equivalent to the cache of entries. If the cache is not hit, it must be created again.
  • The return value of the function isViewHolder.What recycling and reuse isViewHolder?The local variable is declared at the beginning of the functionViewHolder holder = null;It returns the same local variable in four placesholder == nullJudgment,Isn’t this code structure a bit like a cache? Each null call means the previous cache missed and continue to try new fetching methods? Does caching have more than one form of storage?Let’s look at it again and again:

First try

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if(mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! =null; }... }Copy the code

This attempt will only be made if mstate.ispreLayout () is true, which should be a special case, so ignore it.

Second try

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null) {
          holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
          // The following code contains a clue, buy a foetus, it is omitted. }... }Copy the code
  • When the first attempt fails, the attempt passesgetScrapOrHiddenOrCachedHolderForPosition()To obtainViewHolder.
  • There is a piece of code that has been deliberately omitted here, which is a foetus and will be analyzed later. So let’s go along and getViewHolderThe call chain continues down:
// Omit non-critical code
        /** * Returns a view for the position either from attach scrap, hidden children, or cache. Hidden Children or cache to get a ViewHolder */ at the specified location
        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();
            // Try first for an exact, non-invalid match from scrap.
            //1. Search for ViewHolder in Attached Scrap
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if(! holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && ! holder.isInvalid() && (mState.mInPreLayout || ! holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);returnholder; }}//2. Search for a ViewHolder from the view that removed the screen and save it in the Scrap collection when you find it
            if(! dryRun) { View view = mChildHelper.findHiddenNonRemovedView(position);if(view ! =null) {
                    final ViewHolder vh = getChildViewHolderInt(view);
                    mChildHelper.unhide(view);
                    intlayoutIndex = mChildHelper.indexOfChild(view); . mChildHelper.detachViewFromParent(layoutIndex); scrapView(view); vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);returnvh; }}// Search in our first-level recycled view cache.
            //3. Search the cache for ViewHolder
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                // If a ViewHolder is found, the index of the ViewHolder needs to be matched
                if(! holder.isInvalid() && holder.getLayoutPosition() == position) { ...returnholder; }}return null;
        }  
Copy the code

Search for ViewHolder in three places in turn: 1. MAttachedScrap 2. Hide table entry 3. MCachedViews, find immediately return. Where mAttachedScrap and mCachedViews serve as the member variables of Recycler, which can be used to store a set of ViewHolder:

    public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = newArrayList<>(); .final ArrayList<ViewHolder> mCachedViews = newArrayList<ViewHolder>(); . RecycledViewPool mRecyclerPool; }Copy the code
  • RecyclerView RecyclerView objects are ViewHolder, and they are stored in the recyclerer object in the form of ArrayList.
  • RecycledViewPool mRecyclerPool; It also looks like a recycling container,I wonder if you’ll pick it up hereViewHolder?
  • It is worth noting that when success comes frommCachedViewsTo deriveViewHolderObject, its index also needs to be determined, which meansmCachedViewsThe cacheViewHolderCan only be used to specify the locationFor example, if you swipe up and scroll down the list, the second entry is moved out of the screen and the fourth entry is moved into the screen. When you slide back again, the second entry appears again. During this process, the fourth entry cannot reuse the reclaimed second entryViewHolderBecause they are in different locations, the second entry that enters the screen again can be successfully reused.Later, you can compare other reuse to see if the index is also required
  • Go back to the foreshadowing, put the second attempt to getViewHolderCode completion of:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null) {
          holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
          // The following code contains a clue
          if(holder ! =null) {
               // Verify the validity of ViewHolder
               if(! validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can not 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();
                          }
                          // If the validity test is not satisfied, the ViewHolder is recovered
                          recycleViewHolderInternal(holder);
                    }
                    holder = null;
               } else {
                    fromScrapOrHiddenOrCache = true; }}}... }Copy the code

If the ViewHolder is obtained successfully, it is validated, and if it fails, it is recycled. So you get the ViewHoler object, and you take it back? Are there such rigorous tests for all reused ViewHolder? I can’t answer these questions for now, so let’s finish the reuse logic first:

Third attempt

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...// This lookup is performed only when Adapter id is set
      if (mAdapter.hasStableIds()) {
           holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
           if(holder ! =null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true; }}... }Copy the code

The name of the function this time (” byId “) is different from the last time (” byPosition “) except for the suffix. Last time by entry location, this time by entry ID. The internal implementation is almost the same, judging by the entry id instead of the entry location. Setting the id of an entry is a special case, and is ignored.

Fourth attempt

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null&& mViewCacheExtension ! =null) {
           // We are NOT sending the offsetPosition because LayoutManager does not
           // know it.
          final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
           if(view ! =null) {
                // Get the ViewHolder corresponding to the view
                holder = getChildViewHolder(view);
                if (holder == null) {
                    throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                } else if (holder.shouldIgnore()) {
                   throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view."+ exceptionLabel()); }}}... }Copy the code

After failing to get the ViewHolder from mAttachedScrap and mCachedViews, continue to try to get the ViewHolder from ViewCacheExtension:


    /** * ViewCacheExtension is a helper class to provide an additional layer of view caching that can * be controlled by The developer. * ViewCacheExtension provides an additional layer of entry caching. The user helps the developer control the entry caching themselves * <p> * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
     * {@link* When Recycler fails to find matching entries from the Attached Scrap and first level cache, it can be recycled before it goes to the RecycledViewPool. First try to find * <p> */ from the custom cache
    public abstract static class ViewCacheExtension {

        /** * Returns a View that can be binded to the given Adapter position. * <p> * This method should <b>not</b> create a new View. Instead, it is expected to return * an already created View that can be re-used for the given type and position. * If the View is  marked as ignored, it should first call * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
         * <p>
         * RecyclerView will re-bind the returned View to the position if necessary.
         */
        public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
    }
Copy the code

The comments reveal a lot: ViewCacheExtension is used for developer custom entry caching, and the cache layer is accessed in the order after mAttachedScrap and mCachedViews, and before RecycledViewPool. This and Recycler. TryGetViewHolderForPositionByDeadline () the code logic, then the fifth try, should obtain from RecycledViewPool ViewHolder

Fifth attempt

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null) {...// Get the ViewHolder object from the reclamation pool
          holder = getRecycledViewPool().getRecycledView(type);
          if(holder ! =null) {
               holder.resetInternal();
               if(FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); }}}... }Copy the code

The first four attempts were unsuccessful, finally getting a ViewHolder from the RecycledViewPool. Wait a minute! Compared to getting a ViewHolder from MathedScrap and mCachedViews, there is no strict validation logic here. Why treat different caches differently? There’s a big question mark hanging over your head that you can’t answer right now, so let’s keep looking at the RecycledViewPool structure

public final class Recycler {... RecycledViewPool mRecyclerPool;Get the RecycledViewPool instance
    RecycledViewPool getRecycledViewPool(a) {
          if (mRecyclerPool == null) {
              mRecyclerPool = new RecycledViewPool();
          }
          returnmRecyclerPool; }... }public static class RecycledViewPool {...// Get the ViewHolder object from the reclamation pool
    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; }... }Copy the code

Any time a class member variable is accessed in a function, its complexity increases, because the class member variable acts on more than the function body, making the function coupled to other functions in the class. Read more to help understand the function:

    public static class RecycledViewPool {
        // Maximum number of ViewHolder caches of the same class
        private static final int DEFAULT_MAX_SCRAP = 5;

        /** * Tracks both pooled holders, As well as create/bind timing metadata for the given type. * Reclaim containers in the pool that hold a single ViewHolder type */
        static class ScrapData {
            // Similar ViewHolder is stored in ArrayList
            ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            // A maximum of 5 ViewHolder of each type can be stored
            int mMaxScrap = DEFAULT_MAX_SCRAP;
        }
        // Reclaim the container containing all types of ViewHolder in the pool
        SparseArray<ScrapData> mScrap = newSparseArray<>(); .// The ViewHolder is pooled according to the viewType, a type of viewType stored in a ScrapData
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            // If the limit is exceeded, the pool will be abandoned
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            // When recycled, the ViewHolder is inserted from the end of the list
            scrapHeap.add(scrap);
        }
        // Get the ViewHolder object from the reclamation pool
        public ViewHolder getRecycledView(int viewType) {
              final ScrapData scrapData = mScrap.get(viewType);
              if(scrapData ! =null && !scrapData.mScrapHeap.isEmpty()) {
                  final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                  // When reusing, get the ViewHolder from the end of the list (reuse the newly pooled ViewHoler first)
                  return scrapHeap.remove(scrapHeap.size() - 1);
              }
              return null; }}Copy the code
  • The above code is listedRecycledViewPool One of the most critical member variables and two functions in the. It can be concluded that:RecycledViewPoolIn theViewHolderStored in theSparseArray, and pressviewTypeClass storage (that is, the return value of Adapter.geTitemViewType ()) of the same typeViewHolderStored in aArrayList , and a maximum of five storage devices can be stored by default.
  • Compared withmCachedViews, frommRecyclerPoolObtain successfully inViewHolderThe validity and entry location of the object are not verifiedviewTypeWhether it is consistent. sofrommRecyclerPoolTo retrieve theViewHolderCan only be used for the sameviewTypeThe table of items.

Create a ViewHolder and bind the data

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {...// All caches are not hit, and only a ViewHolder can be created
            if (holder == null) {... holder = mAdapter.createViewHolder(RecyclerView.this, type); . }...boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            }
            // If no data is bound to the entry, the entry needs to be updated, or the entry is invalid, and the entry is not removed, bind the entry data
            else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // Bind data to the entrybound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }}Copy the code
  • After all the above attempts, if still noViewHolder, you can only recreate and bind data. As you go down the call chain, you’ll find the familiaronCreateViewHolder()andonBindViewHolder().
  • The logic for binding data is nested in a large if (It turns out that you don’t bind data every time, but only when certain conditions are met.
  • So when do you need to bind and when do you not? This leads to the concept of “cache priority”.

Cache priority

  • Cache has a priority, in the use of image cache level 2 (memory + disk), will first try to high priority memory to obtain, if not hit and then remove the disk to obtain. Higher priority means better performance.RecyclerViewCan we also apply the cache priority logic to the caching mechanism?

  • Although five attempts have been made to obtain the ViewHolder (from six places in total), excluding the three special cases (from mChangedScrap, from ID, and from custom cache), the normal process leaves only three methods of obtaining the ViewHolder, in order of priority from highest to lowest:

    1. frommAttachedScrapTo obtain
    2. frommCachedViewsTo obtain
    3. frommRecyclerPool To obtain
  • Does this cache priority mean that the corresponding reuse performance is also high to low? (Better reuse means less expensive operations to do)

    1. Worst case: re-createViewHodlerAnd rebind the data
    2. The next best thing: reuseViewHolderBut rebind the data
    3. Best case: reuseViewHolderThe data is not rebound

    It goes without saying that the worst-case scenario occurs when all caches are missed. The remaining two cases should be divided by three ways to get, guess the lowest priority mRecyclerPool way should hit the second best case, and the highest priority mAttachedScrap should hit the best case, go to the source code to check:

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
       final int scrapCount = mAttachedScrap.size();

       // Try first for an exact, non-invalid match from scrap.
       //1. Recycle from the Attached Scrap collection
       for (int i = 0; i < scrapCount; i++) {
           final ViewHolder holder = mAttachedScrap.get(i);
           // Return only if the holder is valid
           if(! holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && ! holder.isInvalid() && (mState.mInPreLayout || ! holder.isRemoved())) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);returnholder; }}}ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {...if (holder == null) {...// Get the ViewHolder object from the reclamation pool
          holder = getRecycledViewPool().getRecycledView(type);
          if(holder ! =null) {
               / / reset ViewHolder
               holder.resetInternal();
               if(FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); }}}...// If no data is bound to the entry, the entry needs to be updated, or the entry is invalid, and the entry is not removed, bind the entry data
      else if(! holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {if (DEBUG && holder.isRemoved()) {
              throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
          }
          final int offsetPosition = mAdapterHelper.findPositionOffset(position);
          // Bind data to the entrybound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }... }public abstract static class ViewHolder {
        /** * This ViewHolder has been bound to a position; MPosition, mItemId and mItemViewType * are all valid. * Binding flag bits */
        static final int FLAG_BOUND = 1 << 0;
        /** * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId * are not to be trusted and may * This ViewHolder must be fully rebound to different data. * Invalid flag bit */
        static final int FLAG_INVALID = 1 << 2;
        // Determine whether the ViewHolder is invalid
        boolean isInvalid(a) {
            // Bitwise the flag and invalid flag bits of the current ViewHolder object
            return(mFlags & FLAG_INVALID) ! =0;
        }
        // Determine whether the ViewHolder is bound
        boolean isBound(a) {
            // Set the flag and binding flag bits of the current ViewHolder object
            return(mFlags & FLAG_BOUND) ! =0;
        }
        /** * resets ViewHolder */
        void resetInternal(a) {
            // Set the ViewHolder flag to 0
            mFlags = 0;
            mPosition = NO_POSITION;
            mOldPosition = NO_POSITION;
            mItemId = NO_ID;
            mPreLayoutPosition = NO_POSITION;
            mIsRecyclableCount = 0;
            mShadowedHolder = null;
            mShadowingHolder = null;
            clearPayload();
            mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
            mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
            clearNestedRecyclerViewIfNotNested(this); }}Copy the code

After successfully obtaining a ViewHolder object, reset the ViewHolder object immediately (set the flag to 0). This satisfies the criteria for binding data (since 0 and non-0 bits and must be followed by 0). Similarly, when retrieving a ViewHolder from smart Scrap, the ViewHolder is returned only if it is valid. So the guess is true: ViewHolder that recycles from mRecyclerPool needs to rebind data, ViewHolder that recycles from mAttachedScrap does not need to re-create or rebind data.

conclusion

  1. In RecyclerView, the ViewHolder object is not recreated every time an entry is drawn, nor is the ViewHolder data rebound every time.
  2. RecyclerView throughRecyclerGets the next entry to be drawn.
  3. RecyclerThere are four levels for caching ViewHolder objects, from the highest to the lowest in priorityArrayList<ViewHolder> mAttachedScrap,ArrayList<ViewHolder> mCachedViews,ViewCacheExtension mViewCacheExtension,RecycledViewPool mRecyclerPool. If none of the four caches is hit, the ViewHolder object is recreated and bound.
  4. RecycledViewPool The ViewHolder according toviewTypeStorage by category (PassedSparseArray), the same ViewHolder is stored with a default size of 5ArrayListIn the.
  5. frommRecyclerPoolThe reuse ViewHolder needs to rebind the data frommAttachedScrap The ViewHolder that is reused does not need to be recreated or rebound.
  6. frommRecyclerPoolThe ViewHolder can only be reusedviewTypeSame entry frommCachedViewsCan only duplicate entries for a specified location.
  7. mCachedViewsIs an off-screen cache, used to cache a ViewHolder at a specified location, which can only be hit in a “list rollback” scenario, in which an entry that has just been rolled off the screen enters the screen again. The cache is stored in a default size of 2ArrayListIn the.

This article gives a cursory answer to four questions about reuse, namely “Reuse what?” “, “Where do you get reuse?” “, “When will it be reused?” , Reuse Priority. Reading this, you may have many questions:

  1. scrap viewWhat is?
  2. changed scrap viewandattached scrap viewWhat’s the difference?
  3. When is a reused ViewHolder cached?
  4. Why four levels of caching? What’s the difference between their uses?

After analyzing “reuse”, the following articles will further analyze “recycling”, and hopefully these problems will be solved by then.

Recommended reading

RecyclerView

  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. RecyclerView table item child control click on the listener

  8. More efficient refresh RecyclerView | DiffUtil secondary packaging

  9. Change a thought, super simple RecyclerView preload

  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. RecyclerView rolling is how to achieve? (a) | unlock reading source new posture

  19. RecyclerView when rolling how to achieve? (2) | Fling

  20. RecyclerView Refreshes list data at notifyDataSetChanged() Why is it expensive?