RecyclerView Cache policy

First of all, we still look at the RecycerView typical Adapter implementation:

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerviewAdapter.MyViewHolder> { private Context context; private List<String> data; public MyRecyclerViewAdapter(Context context,List<String> data){ this.context = context; this.data = data; } @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recycler_item_my, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) { holder.name.setText(data.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(" here is the response event for clicking on each line item ",""+position+item "); }}); } @Override public int getItemCount() { return data.size(); } public class MyViewHolder extends RecyclerView.ViewHolder{ TextView name; public MyViewHolder(View itemView) { super(itemView); name = itemView.findViewById(R.id.name); }}}Copy the code

As you can see, the Adapter mainly has two methods that play a role in the cache: onCreateViewHolder, which is used to create a new ViewHolder, and onBindViewHolder, which is used to display data.

2.1 Recycler

As with RecycleBin in ListView, caching in RecyclerView is managed by an internal class Recycler. There are four different levels of Recycler, which are richer than ListView, and RecyclerView is more scalable.

The fields of Recycler are as follows:

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

The explanation is as follows:

  • MAttachedScrap, mChangedScrap

    Level 1 caching, like ActionViews in the ListView, stores the ViewHolder on the screen before a layout occurs for reuse in a layout

  • mCachedViews

    The second level cache, keep the default size in DEFAULT_CACHE_SIZE = 2, can be achieved by RecyclerView. SetItemViewCacheSize number (int) method to set the mCachedViews if beyond the limit, The old ones are moved to RecyclerViewPool based on the index

  • ViewCacheExtension

    Level 3 cache, developers can customize the cache

  • RecyclerViewPool

    Level 4 cache, can be shared in multiple RecyclerView cache

    Cache ViewHolder according to ViewType. DEFAULT_MAX_SCRAP = 5 for each ViewType. SetMaxRecycledViews (int viewType, int Max) can be used to control the size of the cache pool of the corresponding type.

The Recycler methods are essentially operations on these data structures. The main methods are:

  • recycleView(View)

    Move the ViewHolder corresponding to the View to mCachedViews. Scrapped; scrapped; Scrapped

  • recycleViewHolderInternal(ViewHolder)

    Save the ViewHolder to mCachedViews

  • addViewHolderToRecycledViewPool(ViewHolder, boolean)

    Save the ViewHolder to RecycledViewPool

  • scrapView(View)

    Save an Attached View to mAttachedScrap or mChangedScrap

  • getChangedScrapViewForPosition(int)

    Find the matching ViewHolder from mChangedScrap

  • getScrapOrHiddenOrCachedHolderForPosition(int, boolean)

    Find the ViewHolder matching from mAttachedScrap and mCachedViews in turn

  • getScrapOrCachedViewForId(long, int, boolean)

    Find the ViewHolder matching from mAttachedScrap and mCachedViews in turn

  • tryGetViewHolderForPositionByDeadline(int, boolean, long)

    Matching from mChangedScrap, mAttachedScrap, mCachedViews, ViewCacheExtension and RecycledViewPool; If no match is found, the adapter.createViewholder method is called directly

  • tryBindViewHolderByDeadline(ViewHolder, int, int, long)

    Call adapter. bindViewHolder to bind the View

2.2 Cache Process

RecyclerView cache process is the same as ListView, is also reflected in the layout process. Because there are many steps in RecyclerView layout process, and these contents are not the focus of this chapter, so here only gives the general process, focus on the cache process.

The layout process of RecyclerView is divided into three methods, corresponding to the three steps of layout step. The table is as follows:

Our focus in this chapter is obviously in the dispatchLayoutStep2 method:

/** * The second layout step where we do the actual layout of the views for the final state. * This step might be run multiple times if necessary (e.g. measure). */ private void dispatchLayoutStep2() { eatRequestLayout(); onEnterLayoutOrScroll(); mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); mAdapterHelper.consumeUpdatesInOnePass(); mState.mItemCount = mAdapter.getItemCount(); mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; // Step 2: Run layout mState.mInPreLayout = false; mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = false; mPendingSavedState = null; // onLayoutChildren may have caused client code to disable item animations; re-check mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator ! = null; mState.mLayoutStep = State.STEP_ANIMATIONS; onExitLayoutOrScroll(); resumeRequestLayout(false); }Copy the code

This method is simple and focuses on mLayoutChildren (mRecycler, mState) in line 15. Here we choose the most commonly used LinearLayoutManager for mLayout analysis. LinearLayoutManager. OnLayoutChildren method is as follows:

/**
  * {@inheritDoc}
  */
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // layout algorithm:
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    //  item position.
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state
    if (DEBUG) {
        Log.d(TAG, "is pre layout:" + state.isPreLayout());
    }
    if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler);
            return;
        }
    }
    if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
        mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
    }

    ensureLayoutState();
    mLayoutState.mRecycle = false;
    // resolve layout direction
    resolveShouldLayoutReverse();

    final View focused = getFocusedChild();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // calculate anchor position and coordinate
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
            || mOrientationHelper.getDecoratedEnd(focused)
            <= mOrientationHelper.getStartAfterPadding())) {
        // This case relates to when the anchor child is the focused view and due to layout
        // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
        // up after tapping an EditText which shrinks RV causing the focused view (The tapped
        // EditText which is the anchor child) to get kicked out of the screen. Will update the
        // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
        // the available space in layoutState will be calculated as negative preventing the
        // focused view from being laid out in fill.
        // Note that we won't update the anchor position between layout passes (refer to
        // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
        // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
        // child which can change between layout passes).
        mAnchorInfo.assignFromViewAndKeepVisibleRect(focused);
    }
    if (DEBUG) {
        Log.d(TAG, "Anchor info:" + mAnchorInfo);
    }

    // LLM may decide to layout items for "extra" pixels to account for scrolling target,
    // caching or predictive animations.
    int extraForStart;
    int extraForEnd;
    final int extra = getExtraLayoutSpace(state);
    // If the previous scroll delta was less than zero, the extra space should be laid out
    // at the start. Otherwise, it should be at the end.
    if (mLayoutState.mLastScrollDelta >= 0) {
        extraForEnd = extra;
        extraForStart = 0;
    } else {
        extraForStart = extra;
        extraForEnd = 0;
    }
    extraForStart += mOrientationHelper.getStartAfterPadding();
    extraForEnd += mOrientationHelper.getEndPadding();
    if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION
            && mPendingScrollPositionOffset != INVALID_OFFSET) {
        // if the child is visible and we are going to move it around, we should layout
        // extra items in the opposite direction to make sure new items animate nicely
        // instead of just fading in
        final View existing = findViewByPosition(mPendingScrollPosition);
        if (existing != null) {
            final int current;
            final int upcomingOffset;
            if (mShouldReverseLayout) {
                current = mOrientationHelper.getEndAfterPadding()
                        - mOrientationHelper.getDecoratedEnd(existing);
                upcomingOffset = current - mPendingScrollPositionOffset;
            } else {
                current = mOrientationHelper.getDecoratedStart(existing)
                        - mOrientationHelper.getStartAfterPadding();
                upcomingOffset = mPendingScrollPositionOffset - current;
            }
            if (upcomingOffset > 0) {
                extraForStart += upcomingOffset;
            } else {
                extraForEnd -= upcomingOffset;
            }
        }
    }
    int startOffset;
    int endOffset;
    final int firstLayoutDirection;
    if (mAnchorInfo.mLayoutFromEnd) {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                : LayoutState.ITEM_DIRECTION_HEAD;
    } else {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                : LayoutState.ITEM_DIRECTION_TAIL;
    }

    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mInfinite = resolveIsInfinite();
    mLayoutState.mIsPreLayout = state.isPreLayout();
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        final int lastElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            // start could not consume all it should. add more items towards end
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }

    // changes may cause gaps on the UI, try to fix them.
    // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
    // changed
    if (getChildCount() > 0) {
        // because layout from end may be changed by scroll to position
        // we re-calculate it.
        // find which side we should check for gaps.
        if (mShouldReverseLayout ^ mStackFromEnd) {
            int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
            startOffset += fixOffset;
            endOffset += fixOffset;
            fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
            startOffset += fixOffset;
            endOffset += fixOffset;
        } else {
            int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
            startOffset += fixOffset;
            endOffset += fixOffset;
            fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
            startOffset += fixOffset;
            endOffset += fixOffset;
        }
    }
    layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    if (!state.isPreLayout()) {
        mOrientationHelper.onLayoutComplete();
    } else {
        mAnchorInfo.reset();
    }
    mLastStackFromEnd = mStackFromEnd;
    if (DEBUG) {
        validateChildOrder();
    }
}
Copy the code

The method is long, but thankfully has some comments.

  1. First, from the beginning to line 112, the first step is to calculate the anchor coordinates and the position of the anchor item. Line 112 is the onAnchorReady method, too obvious.

  2. Note line 113 detachAndScrapAttachedViews method, this method will be of all child View call scrapOrRecycleView method. All child views will be detach temporarily and stored in mAttachedScrap or mChangedScrap or mCachedViews for reuse.

/** * Temporarily detach and scrap all currently attached child views. Views will be scrapped * into the given Recycler.  The Recycler may prefer to reuse scrap views before * other views that were previously recycled. * * @param recycler Recycler to scrap views into */ public void detachAndScrapAttachedViews(Recycler recycler) { final int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } } private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.shouldIgnore()) { if (DEBUG) { Log.d(TAG, "ignoring view " + viewHolder); } return; } if (viewHolder.isInvalid() && ! viewHolder.isRemoved() && ! mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); }}Copy the code

As mentioned earlier, mCachedViews, if running out of space, moves the old ones to RecyclerViewPool based on the index, so that this method covers all caches except ViewCacheExtension.

  1. Call the fill method several times to fill the child View based on the calculated value.

Obviously, the fill method is the new focus. This method is similar to ListView fillDown, which is a circular computation-fill-calculation. We will directly look at the fill part. The population part calls the layoutChunk method: the method first calls the layoutstate. next method to get a view; Then addView will be added. In the add process, if it is detach, the view will be attached to RecyclerView again, otherwise it is remove, directly addView; The last call measureChildWithMargins, pair layoutDecoratedWithMargins method measure the View and layout. The layoutChunk method code is as follows:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    if (view == null) {
        if (DEBUG && layoutState.mScrapList == null) {
            throw new RuntimeException("received null view when unexpected");
        }
        // if we are laying out views in scrap, this may return null which means there is
        // no more items to layout.
        result.mFinished = true;
        return;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (mOrientation == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {
        top = getPaddingTop();
        bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            right = layoutState.mOffset;
            left = layoutState.mOffset - result.mConsumed;
        } else {
            left = layoutState.mOffset;
            right = layoutState.mOffset + result.mConsumed;
        }
    }
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (DEBUG) {
        Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
    }
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}
Copy the code

Obviously, the key to caching is the layoutstate.next method:

/** * Gets the view for the next element that we should layout. * Also updates current item index to the next item, based on {@link #mItemDirection} * * @return The next element that we should layout. */ View next(RecyclerView.Recycler recycler) { if (mScrapList ! = null) { return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }Copy the code

We’ll skip mScrapList and consider it null for now, but we’ll analyze it later. So here call RecyclerView. GetViewForPosition method:

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

Are a step closer to the truth, tryGetViewHolderForPositionByDeadline method inside the cache at all levels matching section explained here.

A. If there is mChangedScrap, try to match it

// 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder ! = null; }Copy the code

Here isPreLayout () and mState. MRunPredictiveAnimations has a direct relationship, can be seen as the value of the former depends on the latter, this value is updated in the process of dispatchLayoutStep1; When an Item is updated, the scrapView method saves the ViewHolder to the mChangedScrap.

B. Try to find ViewHolder matching from mAttachedScrap and mCachedViews. Once found, the ViewHolder is checked, and if the criteria are not met and dryRun is false (which it is), the ViewHolder is cleared and saved to mCachedViews. When adding caches to mCachedViews, if the allowed limit is exceeded (i.e., mViewCacheMax), the old cache will be moved to a RecycledViewPool.

// 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder ! = null) { if (! validateViewHolderForOffsetPosition(holder)) { // recycle holder (and unscrap if relevant) since it can't be used if (! dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; }}}Copy the code

C. If adapter.hasstableids () is true, ViewHolder will be found among mAttachedScrap and mCachedViews based on ItemId and ViewType. This property defaults to false in Adapter.

 // 2) Find from scrap/cache via stable ids, if exists
 if (mAdapter.hasStableIds()) {
     holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
             type, dryRun);
     if (holder != null) {
         // update position
         holder.mPosition = offsetPosition;
         fromScrapOrHiddenOrCache = true;
     }
 }
Copy the code

D. if there are ViewCacheExtension, call ViewCacheExtension getViewForPositionAndType find ViewHolder

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

E. Fallback to RecycledViewPool to see whether ViewHolder is available

if (holder == null) { // fallback to pool if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder ! = null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); }}}Copy the code

F. If none of the preceding conditions is met, call Adapter.createViewHolder to create a ViewHolder

if (holder == null) { long start = getNanoTime(); if (deadlineNs ! = FOREVER_NS && ! mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = mAdapter.createViewHolder(RecyclerView.this, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(holder.itemView); if (innerView ! = null) { holder.mNestedRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); }}Copy the code

After obtaining the ViewHolder bind, and if necessary will call tryBindViewHolderByDeadline method, this method and then calling Adapter. BindViewHolder give developers to complete the binding work method.

boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition  = position; } 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); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }Copy the code

TryGetViewHolderForPositionByDeadline method will always return to LinearLayoutManager. After completing a layoutChunk method, then according to the source of the ViewHolder, the attach attach, The addView of the addView, and finally measure and layout, the layout process of a child View is complete.

I conclude this section with a flow chart:

ListView and RecyclerView cache mechanism comparison

The principle of ListView and RecyclerView cache mechanism is roughly similar: In the sliding process, the off-screen ItemView is recycled to the cache, and the in-screen ItemView is preferentially obtained from the cache, but the implementation details of ListView and RecyclerView are different (this is just one of the scenarios used by the cache, as well as such as refresh). The schematic diagram is as follows:

The comparison of the two cache mechanisms has the following differences:

  1. Different levels of

    RecyclerView is two levels more than ListView cache, support multiple off-screen ItemView cache, support developers to customize the cache processing logic, support all RecyclerView to share the same RecyclerViewPool(cache pool).

ListView and RecyclerView cache mechanisms are basically the same:

A. mActiveViews has similar functionality to mAttachedScrap and is used to quickly reuse list items visible on the screen without the need for re-creation and binding

B. MScrapViews is similar to mCacheViews + mRecyclerPool, which is used to cache items that leave the screen and reuse items that are about to enter the screen

The advantages of RecyclerView are:

MRecyclerPool can be used for multiple RecyclerView common use in specific scenarios (such as ViewPager+ multiple list pages, Or vertical list items nested in horizontal lists, etc.) has advantages.Copy the code

Objectively speaking, RecyclerView has strengthened and improved the cache mechanism of ListView in specific scenarios.

  1. The cache is different
  • RecyclerView Cache recyclerView. ViewHolder, abstract can be understood as: View + ViewHolder
  • The ListView caches the View and manually adds the custom ViewHolder to the View tag

Caches are different, and there are slight differences in the use of caches. To be specific:

  • In RecyclerView, mCacheViews is obtained by matching POS to obtain the cache of the target location. The advantage of this is that, when the data source is unchanged, there is no need to re-bindView; As for the off-screen cache, ListView fetches pos from mScrapViews, but does not use it directly. Instead, it re-calls Adapter’s getView method, which must cause our bind code to execute.

  • The ListView gets the View through pos; RecyclerView gets ViewHolder via POS.

In addition, RecyclerView provides a local refresh interface, which can avoid calling many useless BindViews.

The biggest difference between ListView and RecyclerView is the processing logic of the cache when the data source changes. ListView is a “one end”, all the mActiveViews are moved into the secondary cache mScrapViews. RecyclerView is more flexible to modify the symbol bit of each View, distinguish whether to re-bindView.