RecyclerView的回收缓存均由内部类Recycler完成

news2024/11/27 21:39:48

1. RecyclerView的三级缓存

通常在RecyclerView中存在着四级缓存,从低到高分别为:

  • 可直接重复使用的临时缓存(mAttachedScrap/mChangedScrap

    • mAttachedScrap中缓存的是屏幕中可见范围的ViewHolder
    • mChangedScrap只能在预布局状态下重用,因为它里面装的都是即将要放到mRecyclerPool中的Holder,而mAttachedScrap则可以在非预布局状态下重用
  • 可重用的缓存(mCachedViews):缓存滑动时即将与RecyclerView分离的ViewHolder,默认最大2个;

  • 自定义实现的缓存(ViewCacheExtension):通常忽略;

  • 需要重新绑定数据的缓存(RecycledViewPool ):ViewHolder缓存池,可以支持不同的ViewType,返回的ViewHolder需要重新Bind数据;

由于绝大多数情况下无需自定义缓存,因此通常我们说RecyclerView有三级缓存

1.1 预布局(PreLayout)

  • 含义:真正布局前的一次布局
  • 触发时机:只有在Adapter的数据集更新的前提下,调用除了notifyDataSetChanged以外的一系列notify方法,预布局才会生效
  • 作用:当Item被删除或者添加时,预布局和真正的布局会生成不同的Item快照,从而根据两个快照执行动画
  • 示例: 删除item2 首先预布局就是先布局一次, 形成一个快照(pre-layout) 如item1234 然后再布局一次(post-layout) 形成另外一张快照 item134 这样我们其实就知道了整个动画轨迹 就可以生成动画

  • 如何开启预布局:重写LayoutManager的**supportsPredictiveItemAnimations**方法并return true,自带的三个LayoutManager已经开启来这个效果

2. 缓存机制

为了方便,我们不讨论预布局的过程,首先我们要了解三级缓存对象:mAttachedScrapmCachedViewsmRecyclerPool,其中mAttachedScrapmCachedViews都是ArrayList对象,而mRecyclerPool是RecyclerViewPool对象:

public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;

    static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }

    SparseArray<ScrapData> mScrap = new SparseArray<>();

    private int mAttachCount = 0;

    /**
     * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
     * present.
     *
     * @param viewType ViewHolder type.
     * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none
     * are present.
     */
    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            for (int i = scrapHeap.size() - 1; i >= 0; i--) {
                if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                 
                    return scrapHeap.remove(i);
                }
            }
        }
        return null;
    }
    
    ...

可以发现

  • RecyclerViewPool中有一个SparseArray<ScrapData>对象,而ScrapData对象中的mScrapHeap就是缓存ViewHolderArrayList

  • 当从RecyclerViewPool中获取ViewHolder时,是通过list.remove(index)方法

2.1 缓存机制起点:tryGetViewHolderForPositionByDeadline

当RecyclerView绘制的时候,会走到LayoutManager里面的next()方法,在next()里面是正式开始使用缓存机制,这里以LinearLayoutManager为例子:

onLayoutChildren -> fill -> layoutChunk -> LayoutState.next

//com.android.internal.widget.LinearLayoutManager.LayoutState#next
View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

getViewForPosition 又会调用到tryGetViewHolderForPositionByDeadline方法:

//com.android.internal.widget.RecyclerView.Recycler#tryGetViewHolderForPositionByDeadline
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    if (position < 0 || position >= mState.getItemCount()) {
        throw new IndexOutOfBoundsException("Invalid item position " + position
                + "(" + position + "). Item count:" + mState.getItemCount());
    }
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 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;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                    + "position " + position + "(offset:" + offsetPosition + ")."
                    + "state:" + mState.getItemCount());
        }

        final int type = mAdapter.getItemViewType(offsetPosition);
        // 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;
            }
        }
        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");
                } else if (holder.shouldIgnore()) {
                    throw new IllegalArgumentException("getViewForPositionAndType returned"
                            + " a view that is ignored. You must call stopIgnoring before"
                            + " returning this view.");
                }
            }
        }
        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);
                }
            }
        }
        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");
            }
        }
    }

    // This is very ugly but the only place we can grab this information
    // before the View is rebound and returned to the LayoutManager for post layout ops.
    // We don't need this in pre-layout since the VH is not updated by the LM.
    if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
        holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
        if (mState.mRunSimpleAnimations) {
            int changeFlags = ItemAnimator
                    .buildAdapterChangeFlagsForAnimations(holder);
            changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
            final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                    holder, changeFlags, holder.getUnmodifiedPayloads());
            recordAnimationInfoIfBouncedHiddenView(holder, info);
        }
    }

    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);
        }
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
    return holder;
}

2.2 根据位置从mAttachedScrap和mCachedViews中获取View Holder

  • 2.2.1 getScrapOrHiddenOrCachedHolderForPosition

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

    // Try first for an exact, non-invalid match from 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);
            return holder;
        }
    }

    if (!dryRun) {
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
           ...
           // 可忽略
            return vh;
        }
    }

    // Search in our first-level recycled view cache.
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // invalid view holders may be in cache if adapter has stable ids as they can be
        // retrieved via getScrapOrCachedViewForId
        if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            return holder;
        }
    }
    return null;
}
  • 第一步先从mAttachedScrap中获取viewHolder:如果可以拿到指定的viewHolder,接着判断:

    • 如果viewHolder不是从Scrap中返回的(标志位不包含FLAG_RETURNED_FROM_SCRAP);
    • viewHolder.mPosition == position
    • viewHolder为有效
    • 为预布局或者viewHolder的状态不是removed
  • 如果第一步没有获得有效的viewHolder,第二步从mChildHelper中获取Hidden状态的View,但通常RecyclerView中没有Hidden状态的View,因为在addView的时候,就将hide值置为false

  • 如果前两步都没有获得有效的viewHolder,则会从mCachedView中取得viewHolder,由于dryRun参数值默认为false,所以取出来的viewHolder会被remove掉

2.1.2 检查viewHolder是否有效

#validateViewHolderForOffsetPosition:

boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
    // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
    // if it is not removed, verify the type and id.
    if (holder.isRemoved()) {
        if (DEBUG && !mState.isPreLayout()) {
            throw new IllegalStateException("should not receive a removed view unless it"
                    + " is pre layout");
        }
        return mState.isPreLayout();
    }
    if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
        throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
                + "adapter position" + holder);
    }
    if (!mState.isPreLayout()) {
        // don't check type if it is pre-layout.
        final int type = mAdapter.getItemViewType(holder.mPosition);
        if (type != holder.getItemViewType()) {
            return false;
        }
    }
    if (mAdapter.hasStableIds()) {
        return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
    }
    return true;
}

如果步骤1 返回null,那么会进行步骤2.3:

2.3 根据Id从mAttachedScrap和mCachedViews中获取viewHolder

// 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;
    }
}
  • 可以看到,当Adapter有固定ID时,我们可以根据Id来获取viewHolder,但是默认情况下,需要我们进行以下两个步骤才会生效:
    • 设置Apapter.setHasStableIds(true)

    • 重写Adapter.getItemId(position)方法

//com.android.internal.widget.RecyclerView.Recycler#getScrapOrCachedViewForId
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    // Look in our attached views first
    final int count = mAttachedScrap.size();
    for (int i = count - 1; i >= 0; i--) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            if (type == holder.getItemViewType()) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                if (holder.isRemoved()) {
                    // this might be valid in two cases:
                    // > item is removed but we are in pre-layout pass
                    // >> do nothing. return as is. make sure we don't rebind
                    // > item is removed then added to another position and we are in
                    // post layout.
                    // >> remove removed and invalid flags, add update flag to rebind
                    // because item was invisible to us and we don't know what happened in
                    // between.
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                return holder;
            } else if (!dryRun) {
                // if we are running animations, it is actually better to keep it in scrap
                // but this would force layout manager to lay it out which would be bad.
                // Recycle this scrap. Type mismatch.
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    // Search the first-level cache
    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        final ViewHolder holder = mCachedViews.get(i);
        if (holder.getItemId() == id) {
            if (type == holder.getItemViewType()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

根据IdmAttachedScrapmCachedViews中获取viewHolder,如果获取到了并且ItemViewType一致,则直接返回

2.4 从mRecyclerPool中获取viewHolder

//3.1 获取viewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
    // 3.2 重置viewHolder
    holder.resetInternal();
    if (FORCE_INVALIDATE_DISPLAY_LIST) {
        invalidateDisplayListInt(holder);
    }
}

2.4.1 获取viewHolder:getRecycledView

//com.android.internal.widget.RecyclerView.RecycledViewPool#getRecycledView
public ViewHolder getRecycledView(int viewType) {
// 根据viewType获取对应的viewHolder缓存
    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;
}

2.4.2 重置viewHolder状态

void resetInternal() {
    mFlags = 0;
    mPosition = NO_POSITION;
    mOldPosition = NO_POSITION;
    mItemId = NO_ID;
    mPreLayoutPosition = NO_POSITION;
    mIsRecyclableCount = 0;
    mShadowedHolder = null;
    mShadowingHolder = null;
    clearPayload();
    mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
    mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
    clearNestedRecyclerViewIfNotNested(this);
}

2.5 创建新的viewHolder

如果前三步得到的holder为空,则需要新建一个viewHolder:

holder = mAdapter.createViewHolder(RecyclerView.this, type);

2.6 根据viewHolder状态确定是否需要调用bindViewHolder


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);
    }
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    // 重写bindView
    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}

private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
        int position, long deadlineNs) {
    holder.mOwnerRecyclerView = RecyclerView.this;
    final int viewType = holder.getItemViewType();
    long startBindNs = getNanoTime();
    if (deadlineNs != FOREVER_NS
            && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
        // abort - we have a deadline we can't meet
        return false;
    }
    // 熟悉的bindViewHolder
    mAdapter.bindViewHolder(holder, offsetPosition);
    long endBindNs = getNanoTime();
    mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
    attachAccessibilityDelegate(holder.itemView);
    if (mState.isPreLayout()) {
        holder.mPreLayoutPosition = position;
    }
    return true;
}
  1. 回收机制

3.1 回收方法

LayoutManager提供了各种回收方法:

detachAndScrapView(View child, Recycler recycler)
detachAndScrapViewAt(int index, Recycler recycler)
detachAndScrapAttachedViews(Recycler recycler)


removeAndRecycleView(View child, Recycler recycler)
removeAndRecycleViewAt(int index, Recycler recycler)
removeAndRecycleAllViews(Recycler recycler)
  • 前三个方法负责将View回收到一级缓存(Recycler.mAttachedMap)中,而一级缓存只是一个临时缓存,用于初始化或者数据集变化时,将所有的View放到临时放到缓存中,即只在布局(调用****onLayoutChildren )时才会调用( detachAndScrapAttachedViews detachAndScrapView/detachAndScrapViewAt没有看到有调用的地方)。

  • 后三个方法负责将View回收到二级缓存(mCachedViews)或者四级缓存(RecyclerViewPool)中,mCachedViews默认大小为2(但目前存在mPrefetchMaxCountObserved参数,值为1 ,所以mCachedViews大小可能为3)

3.2 回收时机

3.2.1 回收到一级缓存的时机

只有在Adapter数据集发生变化,调用各种notify方法,因此重新布局后才会对屏幕内的View回收到一级缓存(mAttachedMap)中,此时由onLayoutChildren发起,调用detachAndScrapAttachedViews方法,进而调用到scrapOrRecycleView方法:

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    ...
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        // 回收到一级缓存
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

当viewHolder满足 无效/没有被移除/Adapter没有stableIds 时,会回收到二级缓存或四级缓存中,否则回收到一级缓存

3.2.2 回收到二级缓存或者四级缓存的时机

  1. 数据集发生变化,调用LayoutManage.onLayoutChildren -> LayoutManage.detachAndScrapAttachedViews -> recycler.recycleViewHolderInternal 进行回收;
  2. item滑出屏幕时,调用 scrollBy -> fill-> recycleByLayoutState 进行回收

由于上述两种情况下最终调用的地方有重合,我们直接以第二种情况来做说明:

整体时序图如下

用户在滑动列表时,会使用LinearLayoutManage.scrollBy函数:

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
   ...
    updateLayoutState(layoutDirection, absDelta, true, state);
    // 关键点:调用fill函数
    final int consumed = mLayoutState.mScrollingOffset
            + fill(recycler, mLayoutState, state, false);
    ...
    mLayoutState.mLastScrollDelta = scrolled;
    return scrolled;
}

继续深入fill函数:

//androidx.recyclerview.widget.LinearLayoutManager#fill

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
   
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        // 1. 回收到二级缓存或四级缓存的起点
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        ...
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        ...
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
       
    }
    return start - layoutState.mAvailable;
}

#recycleByLayoutState

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    if (!layoutState.mRecycle || layoutState.mInfinite) {
        return;
    }
    int scrollingOffset = layoutState.mScrollingOffset;
    int noRecycleSpace = layoutState.mNoRecycleSpace;
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}

根据滑动的方向,选择调用recycleViewsFromEnd还是recycleViewsFromStart,最终调用方法一致,我们以recycleViewsFromEnd 为例进行说明

// androidx.recyclerview.widget.LinearLayoutManager#recycleViewsFromEnd
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
        int noRecycleSpace) {
    final int childCount = getChildCount();
   if (mShouldReverseLayout) {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // stop here
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    } else {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // stop here
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    }
}

不管mShouldReverseLayout是什么值(一般为false),都会调用到recycleChildren

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
    if (startIndex == endIndex) {
        return;
    }
    if (DEBUG) {
        Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
    }
    if (endIndex > startIndex) {
        for (int i = endIndex - 1; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    } else {
        for (int i = startIndex; i > endIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    }
}

至此,我们看到了开头讲的那几个回收方法中的一个:removeAndRecycleViewAt ,之后便会调用到Recycler.recyclerView方法:

public void recycleView(@NonNull View view) {
    // This public recycle method tries to make view recycle-able since layout manager
    // intended to recycle this view (e.g. even if it is in scrap or change cache)
    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
    if (holder.isScrap()) {
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
    recycleViewHolderInternal(holder);
   
}

紧接着调用Recycler 内部的recycleViewHolderInternal 方法:

void recycleViewHolderInternal(ViewHolder holder) {
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // Retire oldest cached view
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                // mCachedViews中满了之后,调用addViewHolderToRecycledViewPool,将第一个ViewHolder放入RecycledViewPool
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
        
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
}

通常会将viewHolder添加进二级缓存mCachedViews中,mCachedViews中满员后,会将第一个viewHolder通过addViewHolderToRecycledViewPool函数添加进四级缓存RecycledViewPool

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Material—— 常用材质节点

目录 Coordinates Absolute World Position Actor Position Object Position Utility SphereMask Coordinates 表示坐标类的节点&#xff1b; Absolute World Position 别名为WorldPosition&#xff0c;此节点输出当前像素在世界空间内的位置&#xff1b;常用于查找相机到…

作为过来人:有什么话想对当年高考前的自己说

目录 引言千人千面-有什么话想对当年高考前的自己说怀念高中&#xff0c;数学太难多考一分&#xff0c;人生就会不一样一定要勇敢&#xff0c;不止高考别把高考不当回事6的我没话说想到啥就去做别选**大学/专业强烈想出名的拖鞋哥英语全选C&#xff0c;理综要细心会的全做对当时…

Spring - 注解开发

文章目录 Spring的注解开发一、Bean 基本注解开发1.1 Component Bean的配置1.2 其他注解配置Bean1.3 Component 衍生注解 二、Bean依赖注入注解开发2.1 Value2.2 Autowired2.3 Qualifier2.4 Resource 三、非自定义注解开发3.1 无参非自定义注解开发3.2 有参非自定义注解开发 四…

Domino 14新内核

大家好&#xff0c;才是真的好。 还记得去年&#xff0c;我们不断跟进而放出的Notes/Domino产品路线图吗&#xff1f;是的&#xff0c;HCL正在按照产品路线图稳步推进&#xff0c;而很多人提出的idea&#xff0c;也逐步加入到产品中&#xff0c;等会我们也会聊到。 我最喜欢这…

MySQL安装-Linux版

MySQL-Linux版安装 1、准备一台Linux服务器 云服务器或者虚拟机都可以&#xff1b; Linux的版本为 CentOS7&#xff1b; 2、 下载Linux版MySQL安装包 下载地址 3、上传MySQL安装包 使用FinalShell软件上传即可&#xff01; 4、 创建目录,并解压 mkdir mysqltar -xvf my…

【Web服务器】Nginx网站服务

文章目录 一、Nginx 概述1.什么是 Nginx2.Nginx 的特点3.Nginx 应用场景 二、Nginx 服务基础1.编译安装 Nginx 服务1.1 布置环境1.2 安装依赖包1.3 创建运行用户、组1.4 编译安装 2.Nginx 的运行控制2.1 检查配置文件2.2 启动、停止 Nginx2.3 日志分割以及升级 Nginx 服务2.4 添…

调用腾讯API实现图片滤镜

目录 1. 作者介绍2. 图像滤波介绍3. 实验过程及结果&#xff08;附完整代码&#xff09;3.1 准备工作3.2 实验代码3.3 实验结果 1. 作者介绍 班梦威&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;模式识别与人工智能 电子…

企业如何有效制定企业信息化发展规划?(附信息化模板)

如何有效制定企业信息化发展规划&#xff1f;企业信息化发展规划是一个宏大而又复杂的命题&#xff0c;这篇来掰开揉碎讲一下企业应该如何有效制定信息化发展规划。 这里不给大家灌鸡汤&#xff0c;也不给大家画大饼&#xff0c;就说些实在的。 如果你想找经验方法&#xff0…

Lambda表达式与函数式编程

文章目录 函数式编程——Stream流概述为什么学?函数式编程思想 Lambda表达式概述Lambda表达式的前身省略规则 Stream流概述案例数据准备创建流中间操作终结操作reduce归并注意事项 Optional概述创建对象安全消费值获取值安全获取值过滤数据转换 函数式接口常用的默认方法 方法…

APPSCAN扫描https协议的网站证书安装过程(Googel浏览器)

【1】首先打开我们的appscan,点击外部设备。 【2】点击记录代理配置 【3】弹出选项后&#xff0c;在记录代理下我们可以看到AppScan SSL证书&#xff0c;这点我们点击导出 【4】这里你选择一个合适的位置&#xff0c;点击保存 【5】保存后的文件是一个zip压缩包&#xff0c;…

GPT4和Claude100k测试使用

总述 程序员们通常使用大量代码&#xff0c;找到一个能够使用Claude100k和GPT4的&#xff0c;长代码优化有希望啦&#xff01; Liaobots&#xff1a;支持GPT4和Claude100k 不定期供应GPT4 32k&#xff0c;支持最多24000字符请求 大家有时候会觉得GPT4 8k不够用&#xff0c;…

TensorFlow入门知识

个人理解 TensorFlow是集齐了很多深度学习相关的算法的框架&#xff0c;你可以利用他搭建自己的神经网络模型。对于开发者来说&#xff0c;告诉TensorFlow一批特征数据和最终的答案数据&#xff0c;让其通过一个神经网络模型进行训练&#xff0c;最终输出模型。模型将应用于应…

DSP-OMAPL-138 RTOS开发(1)——报错总结

1 git的作用 第一个问题&#xff0c;也不算错误&#xff1a;工程文件会有一个名称会有一个横着的箭头并且文件名前面都有问好&#xff0c;不算错误&#xff08;算个发现&#xff09;&#xff0c;但是发现将git文件删去即可&#xff0c;如果没有&#xff0c;右击工程文件->选…

用逆向思维学习技术

tip: 作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; 前言 学习任何知识&#xff0c…

新Ubuntu怎么装Nidia驱动,cuda和cudnn

怎么安装nvidia驱动 软件更新->附加驱动 选择一个喜欢的 或者找推荐的 Ubuntu 20.04安装CUDA & CUDNN 手把手带你撸_ubuntu20.04 无图形化安装cuda_哈希Map的博客-CSDN博客 sudo ubuntu-drivers autoinstall 怎么安装cuda gcc 不用降级 &#xff08;我没降级就安装好…

设计模式-02.经典设计原则-第二节[必读]

设计模式经典设计原则-第二节 依赖反转原则&#xff08;DIP&#xff09; 关于 SOLID 原则&#xff0c;我们已经学过单一职责、开闭、里式替换、接口隔离这四个原则。今天&#xff0c;我们再来学习最后一个原则&#xff1a;依赖反转原则。在前面&#xff0c;我们讲到&#xff…

C++ 深入理解多态及拓展

文章目录 1. 理解虚表1.1 虚表1.2 验证1.3 子类虚表1.4 相同类不同对象的虚表 2. 静态绑定和动态绑定2.1 静态绑定2.2 动态绑定 3. 多态的实现原理3.1 向上转型3.2 多继承3.3 原理 4. 拓展4.1 构造函数能不能是虚函数4.2 父类和子类的析构函数在底层的命名问题4.3 对象之间无法…

c++实现smtp发送邮件,支持ssl的465端口发送,支持附件、一次发送多人、抄送等

前言 c实现smtp发送邮件&#xff0c;支持ssl的465端口发送&#xff0c;支持附件、一次发送多人、抄送等。 这里只使用了openssl库&#xff08;用来支持ssl的465端口&#xff09;&#xff0c;其他部分是原生c,支持在win/linux运行。 网上很多都是原始的支持25端口&#xff0c;明…

Fiddler抓包工具之高级工具栏中的重定向AutoResponder的用法

重定向AutoResponder的用法 关于Fiddler的AutoResponder重定向功能&#xff0c;主要是时进行会话的拦截&#xff0c;然后替换原始资源的功能。 它与手动修该reponse是一样的&#xff0c;只是更加方便了&#xff0c;可以创建相应的rules&#xff0c;适合批处理的重定向功能。 …

[SQL Server]数据库入门之多表查询

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…