RecyclerView 回收复用机制

400 阅读6分钟

前言

我们都知道,RecyclerView 在 layout 子 View 时,都通过回收复用机制来管理。

在我没有深入了解ReyclerView缓存机制之前,我对RecyclerView认识是:RecyclerView维护着一个容器保存着ItemView,当我们在用的时候就从这里面取,这样就达到了复用的效果。是不是很简单,哈哈!!!然而现实狠狠给了我三个耳光(我们有四级缓存)。

what fuck!!!为什么需要四级缓存?这样的疑问估计就是我这种码农和Google 🐂🍺工程师之间的差距吧。自叹不如!所以为了提升自己的能力,我得好好 read the fuck code 了。

经过自己阅读代码,以及网上关于回收复用机制的分析讲解的文章也有一大堆了,分析得也都很详细,什么四级缓存啊,先去 mChangedScrap 取再去哪里取啊之类的。所以我决定,我要写一篇只有我能看懂的文章记录下我对RecyclerView回收复用机制的理解!

下面进入正题

正题

  • 核心类介绍
  • 回收和复用分析入口
  • 回收的机制
  • 复用的逻辑

核心类介绍

在分析回收复用机制之前,我们需要对回收复用机制的核心类有个了解,下面是RecyclerView回收复用中几个核心类的核心功能:

  • ViewHolder:对于Adapter来说,一个ViewHolder就对应一个data。它也是Recycler缓存池的基本单元。

    • itemView : 会被当做child viewaddRecyclerView中。
    • mPosition : 标记当前的ViewHolderAdapter中所处的位置。
    • mItemViewType : 这个ViewHolderType,在ViewHolder保存到RecyclerPool时,主要靠这个类型来对ViewHolder做复用。
    • mFlags : 标记ViewHolder的状态,比如 FLAG_BOUND(显示在屏幕上)FLAG_INVALID(无效,想要使用必须rebound)FLAG_REMOVED(已被移除)等。
  • Adapter:它的工作是把dataView绑定,即上面说的一个data对应一个ViewHolder。主要负责ViewHolder的创建以及数据变化时通知RecyclerView

  • LayoutManager:它是RecyclerView的布局管理者,RecyclerViewonLayout时,会利用它来layoutChildren,它决定了RecyclerView中的子View的摆放规则。但不止如此, 它做的工作还有:

    • 测量子View
    • 对子View进行布局
    • 对子View进行回收
    • 子View动画的调度
    • 负责RecyclerView滚动的实现
    • ...
  • Recycler:对于LayoutManager来说,它是ViewHolder的提供者。对于RecyclerView来说,它是ViewHolder的管理者,是RecyclerView最核心的实现。四级缓存机制,后面具体介绍

  • RecyclerView:继承ViewGroup,列表视图的容器,串联RecyclerLayoutManagerAdapter的核心类

小结

通过前面核心类的介绍,我们知道ViewHoldermFlags标记着当前View的状态,通过这些标志 我们可以知道哪些ViewHolder可以回收,哪些可以复用。

回收和复用分析入口

RecyclerView回收入口有很多, 但是RecyclerView 的回收或者复用必然涉及到add view 和 remove view 的操作,还有Adapter notifyData 等 刷新操作,都会触发 view layout 流程。 所以我将从onLayout的流程入手分析回收和复用的机制。

回收的机制

下面是整个onLayout视图回收的时序图

我们看下重要的几个方法

scrapOrRecycleView
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;
    }
  	//ViewHolder 失效 并且未被 移除
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
      	//将View remove
        removeViewAt(index);
      	//viewHolder存到缓存中
        recycler.recycleViewHolderInternal(viewHolder);
    } else {//ViewHolder 在屏幕内,有效
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

detachAndScrapAttachedViews 会遍历所有RecyclerView的child View 然后调用scrapOrRecycleView这个方法会将所有的viewHolder存放到对应的缓存中

缓存到mCachedViews和mRecyclerPool中
recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
    省略代码...
      
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    @SuppressWarnings("unchecked")
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;

    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();
          	//如果mCachedViews缓存已满,先移除最先缓存的holder
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            省略代码...
          
          	// 添加 holder 到mCachedViews缓存中
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
          	//如果前面未缓存,将holder缓存到RecycledViewPool中
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
    		省略代码...
    }
    省略代码...
}
recycleCachedViewAt
void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

取出mCachedViews中的缓存并缓存到RecycledViewPool中

addViewHolderToRecycledViewPool
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    if (mAccessibilityDelegate != null) {
        AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate();
        AccessibilityDelegateCompat originalDelegate = null;
        if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
            originalDelegate =
                    ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
                            .getAndRemoveOriginalDelegateForItem(itemView);
        }
        // Set the a11y delegate back to whatever the original delegate was.
        ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
    }
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    getRecycledViewPool().putRecycledView(holder);
}

最终调用getRecycledViewPool().putRecycledView()缓存holder

小结
  • mCachedViews缓存ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN的holder
  • RecycledViewPool缓存的holder,会调用holder.resetInternal()将holder内部数据重置
  • mCachedViewsRecycledViewPool 只会缓存不在屏幕中的ViewHolder
  • mCachedViews有缓存个数限制 默认2个,可以通过调用setViewCacheSize设置缓存个数
  • RecycledViewPool缓存各种ItemType的holder,每一种holder 最多默认缓存5个,可以通过setMaxRecycledViews设置对应ItemType的缓存个数,超过个数限制者不缓存后面新添的holder
缓存到mAttachedScrap和mChangedScrap中
void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
            throw new IllegalArgumentException("Called scrap view with an invalid view."
                    + " Invalid views cannot be reused from scrap, they should rebound from"
                    + " recycler pool." + exceptionLabel());
        }
        holder.setScrapContainer(this, false);
      	//缓存不需要更新的holder
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
      	//缓存需要更新的holder
        mChangedScrap.add(holder);
    }
}
  • mAttachedScrap 缓存不需要更新,等到用的时候直接拿来用的holder,也就是数据没有变化
  • mChangedScrap 用来缓存需要更新的holder
  • mAttachedScrapmChangedScrap缓存的holder的ItemView都是添加到RecyclerView的 childView

复用的逻辑

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    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() + exceptionLabel());
        }

        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"
                            + exceptionLabel());
                } else if (holder.shouldIgnore()) {
                    throw new IllegalArgumentException("getViewForPositionAndType returned"
                            + " a view that is ignored. You must call stopIgnoring before"
                            + " returning this view." + exceptionLabel());
                }
            }
        }
        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");
            }
        }
    }
		省略代码...
    return holder;
}

分几种情况去获取ViewHolder

  1. getChangedScrapViewForPosition -- mChangeScrap 与动画相关
  2. getScrapOrHiddenOrCachedHolderForPosition -- mAttachedScrap 、mCachedViews
  3. getScrapOrCachedViewForId -- mAttachedScrap 、mCachedViews (ViewType,itemid)
  4. mViewCacheExtension.getViewForPositionAndType -- 自定义缓存 -- 一般不会使用
  5. getRecycledViewPool().getRecycledView -- 从缓冲池里面获取