RecyclerView在不同操作下的ViewHolder回收分析

起因

RV在几乎在所有的有界面的Android应用都会使用到,可以说日常开发中RV是老熟人天天见,可是仔细想想熟悉它的什么呢?熟悉的是怎么去使用,对于它的内部原理太过陌生。曾经多次点看它的源码,但都迷失在成千上万的代码与注释中。幸好网上还有很多关于RV优秀的文章,让我能抓住RV的一些重点,其中一个重点就是它对viewHolder的回收与复用机制,关于这一块网上很多文章已经分析过了,Recycler、四级缓存等等,但我还是有一个疑问,在什么操作下会将什么viewHolder放到哪一个缓存?由于RV源码太复杂了😭,下面就带着这个疑问片面地分析下它的缓存设计。

Recycler与RecyclerPool类

Recycler类是RV的一个内部类,正如它的名字一样它是负责回收的,在它的内部定义了好几种关于回收的成员变量,先熟悉一下

public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        RecycledViewPool mRecyclerPool;

        private ViewCacheExtension mViewCacheExtension;

        static final int DEFAULT_CACHE_SIZE = 2;
}

有三种类型为ViewHolder的ArrayList,显然它们是都是用来缓存不同时机的ViewHolder的,接下来是一个类成员RecycledViewPool,它的代码如下

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

      static class ScrapData {
          //回收list
          final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
          int mMaxScrap = DEFAULT_MAX_SCRAP;
          long mCreateRunningAverageNs = 0;
          long mBindRunningAverageNs = 0;
      }
      SparseArray<ScrapData> mScrap = new SparseArray<>();


      private ScrapData getScrapDataForType(int viewType) {
           ScrapData scrapData = mScrap.get(viewType);
           if (scrapData == null) {
               scrapData = new ScrapData();
               mScrap.put(viewType, scrapData);
           }
           return scrapData;
       }

      /**
       * 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;
              return scrapHeap.remove(scrapHeap.size() - 1);
          }
          return null;
      }

      /**
       * Add a scrap ViewHolder to the pool.
       * <p>
       * If the pool is already full for that ViewHolder's type, it will be immediately discarded.
       *
       * @param scrap ViewHolder to be added to the pool.
       */
      public void putRecycledView(ViewHolder scrap) {
          final int viewType = scrap.getItemViewType();
          final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
          if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
              return;
          }
          if (DEBUG && scrapHeap.contains(scrap)) {
              throw new IllegalArgumentException("this scrap item already exists");
          }
          scrap.resetInternal();
          scrapHeap.add(scrap);
      }
  }

在它内部还有一个静态内部类ScrapData,ScrapData类中出现了ArrayList<ViewHolder>,显然又是用来保存ViewHolder的,但为什么要单独设计一个类中呢,再看看下面一行,定义了一个SparseArray<ScrapData>,SparseArray是一个Android特有的类,类似于HashMap,但它的key只能是int,结合RecycledViewPool的put和get方法可以知道,SparseArray中的key就是RV中的itemType,并且每个itemType最多保存5个,所以,在RecycledViewPool中viewHolder是按照它们的类型来分别保存的,这一点也提醒了我:之前那三个缓存是不分类型的!

ViewHolder类

这是一个在开发中经常使用的类,日常使用中也就是在继承ViewHolder,在其中保存itemView的子View,但今天关注的点不一样,在ViewHolder中定义了很多关于ViewHolder状态的常量,主要的几个如下


        /**
         * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
         * are all valid.
         */
        static final int FLAG_BOUND = 1 << 0;

        /**
         * The data this ViewHolder's view reflects is stale and needs to be rebound
         * by the adapter. mPosition and mItemId are consistent.
         */
        static final int FLAG_UPDATE = 1 << 1;

        /**
         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
         * are not to be trusted and may no longer match the item view type.
         * This ViewHolder must be fully rebound to different data.
         */
        static final int FLAG_INVALID = 1 << 2;

        /**
         * This ViewHolder points at data that represents an item previously removed from the
         * data set. Its view may still be used for things like outgoing animations.
         */
        static final int FLAG_REMOVED = 1 << 3;

        /**
         * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
         * and is intended to keep views around during animations.
         */
        static final int FLAG_NOT_RECYCLABLE = 1 << 4;

  
        int mFlags;

        void addFlags(int flags) {
            mFlags |= flags;
        }

  • FLAG_BOUND:viewHolder与RV中POSITION绑定了,就是调用了onBindViewHolder方法吧
  • FLAG_INVALID:onBindViewHolder的数据失效了,需要重新绑定
  • FLAG_UPDATE:viewHolder的view需要重新更新数据

关于缓存的一些关键的类介绍完了,接下来开始分析具体的使用场景中这几个缓存是怎么发挥作用的

调用setAdapter方法

setAdapter这个方法可以多次调用,第一次调用显然没有任何缓存可言,这里只考虑第一次调用后再次调用的情况,如下

  //=====RecyclerView中
  public void setAdapter(@Nullable Adapter adapter) {
       // bail out if layout is frozen
       setLayoutFrozen(false);
       setAdapterInternal(adapter, false, true);
       processDataSetCompletelyChanged(false);
       requestLayout();
   }

   private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
           boolean removeAndRecycleViews) {
       if (mAdapter != null) {
           mAdapter.unregisterAdapterDataObserver(mObserver);
           mAdapter.onDetachedFromRecyclerView(this);
       }
       if (!compatibleWithPrevious || removeAndRecycleViews) {
           removeAndRecycleViews();
       }
       mAdapterHelper.reset();
       final Adapter oldAdapter = mAdapter;
       mAdapter = adapter;
       if (adapter != null) {
           adapter.registerAdapterDataObserver(mObserver);
           adapter.onAttachedToRecyclerView(this);
       }
       if (mLayout != null) {
           mLayout.onAdapterChanged(oldAdapter, mAdapter);
       }
       mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
       mState.mStructureChanged = true;
   }

   /**
     * Removes and recycles all views - both those currently attached, and those in the Recycler.
     */
    void removeAndRecycleViews() {
        ......
        mRecycler.clear();
    }

setAdapter会调用setAdapterInternal(adapter, false, true)方法,从中可以看到RV和Adapter之间是观察者模式,每一次的setAdapter都是RV反过来订阅了Adapter,Adapter通过各种notify**通知RV数据更新。这个方法后面传入的两个参数是字面意思都很好理解,第一个是否和上一个Adapter的类型兼容,第二个是否移除回收views,compatibleWithPrevious为false,所以会调用removeAndRecycleViews方法,在这个方法中又会调用Rcycler的clear方法,好了这就进入正题了,下面看看这个方法

//=====Recycler中

  public void clear() {
    //清空mAttachedScrap
      mAttachedScrap.clear();
      recycleAndClearCachedViews();
  }

  void recycleAndClearCachedViews() {
      final int count = mCachedViews.size();
      for (int i = count - 1; i >= 0; i--) {
          recycleCachedViewAt(i);
      }
      mCachedViews.clear();
      ......
  }

void recycleCachedViewAt(int cachedViewIndex) {

    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);

    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

 void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
      ......
     getRecycledViewPool().putRecycledView(holder);
 }

一系列的调用可以发现,首先会移除RV中所有的view(代码不贴了,这里只关注回收相关的),然后清空了mAttachedScrap,然后将mCachedViews中的viewholder移动到了RecycledViewPool,之后清空mCachedViews中的viewholder,此时只有RecyclerViewPool中有缓存,但感觉不对,已经换Adapter了所有数据都会变,万一旧的Adapter和新的Adapter的itemType有一样的但viewHolder不一样,那还是会从Pool中去取viewHolder,岂不是会崩?多虑了~回到setAdapterInternal方法中会发现这里最后还调用了mRecycler.onAdapterChanged方法,如下

      void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
            boolean compatibleWithPrevious) {
        if (oldAdapter != null) {
            detach(); //mAttachCount--
        }
        if (!compatibleWithPrevious && mAttachCount == 0) {
            clear();
        }
        if (newAdapter != null) {
            attach();//mAttachCount++
        }
      }


    public void clear() {
      for (int i = 0; i < mScrap.size(); i++) {
        ScrapData data = mScrap.valueAt(i);
        data.mScrapHeap.clear();
      }
}

由于一个RecyclerViewPool可以设置给多个RV,mAttachCount表示这个Pool关联的RV的数量,当mAttachCount为0时就清空Pool中的所有的viewHolder,所以最后三种缓存都给清空了,如图所示:


setAdapter.png

调用setAdapter方法缓存的更新就分析完了,可以总结为:

  • 不给RV设置RecyclerViewPool的情况下,如果重新setAdapter会清空所有的三级缓存
  • 如果单独设置RecyclerViewPool,并且多个RV同时在使用,会将viewHolder移动最多5个到RecyclerViewPool

调用notifyDataChanged方法

在日常使用中一旦给RV设置了Adapter,一般都不会去再次设置adapter,更多时候是调用Adapter的notify***方法更新数据,现在就来分析一下调用notifyDataChanged时缓存的情况。刚才提到过RV与Adapter之间是观察者模式,Adapter是被观察者、RV是观察者,当我们调用adapter的notifyDataChanged时最后会调用到RV内部的RecyclerViewDataObserver的onChanged方法,如下

//==RecyclerView中
    private class RecyclerViewDataObserver extends AdapterDataObserver {
          RecyclerViewDataObserver() {
          }

          @Override
          public void onChanged() {
              assertNotInLayoutOrScroll(null);
              mState.mStructureChanged = true;

              processDataSetCompletelyChanged(true);
              if (!mAdapterHelper.hasPendingUpdates()) {
                  requestLayout();
              }
          }
      }
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
  mDispatchItemsChangedEvent |= dispatchItemsChanged;
  mDataSetHasChangedAfterLayout = true;
  markKnownViewsInvalid();
}

void markKnownViewsInvalid() {
    //getUnfilteredChildCount(): Returns the total number of children.
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
            holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
        }
    }
    markItemDecorInsetsDirty();
    mRecycler.markKnownViewsInvalid();
}

//==Recycler中
void markKnownViewsInvalid() {
    final int cachedCount = mCachedViews.size();
    for (int i = 0; i < cachedCount; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        if (holder != null) {
            holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            holder.addChangePayload(null);
        }
    }

    if (mAdapter == null || !mAdapter.hasStableIds()) {
        // we cannot re-use cached views in this case. Recycle them all
        recycleAndClearCachedViews();
    }
}

最后会调用到RV的markKnownViewsInvalid方法,在这里会取出所有被添加到RV中viewHoder,然后给它们添加上ViewHolder.FLAG_UPDATE和ViewHolder.FLAG_INVALID的FLAG,这两个标记上面介绍了,标记这个viewHolder的数据、位置失效,接着又调用Recycler的同名方法将mCachedViews中的viewHolder标记为失效,然后再调用recycleAndClearCachedViews方法,这个方法上面也提到过,就是将mCachedViews中的viewHolder移动到了RecyclerViewPool中去。
此刻可以总结为:RV中添加的viewHoder仍然在,但被打了标记,mAttachedScrap为空,mCachedViews为空,RecyclerViewPool中可能有缓存。

回到onChanged方法中,最后会调用requestLayout重新布局,requestLayout会触发RV重新measure、layout、draw,在layout的时候将所有的布局都交给了LayoutManager,调用它的onLayoutChildren方法,以LinearLayoutManager为例,在真正进行布局之前会调用detachAndScrapAttachedViews方法,顾名思义就是要detach掉RV中所有的viewHolder

//==LinearLayoutManager中
public void detachAndScrapAttachedViews(@NonNull 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.isInvalid() && !viewHolder.isRemoved()
                   && !mRecyclerView.mAdapter.hasStableIds()) {
               removeViewAt(index);
               recycler.recycleViewHolderInternal(viewHolder);
           } else {
               detachViewAt(index);
               recycler.scrapView(view);
               mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
           }
       }

//==Recycler中
void recycleViewHolderInternal(ViewHolder 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();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }
            int targetCacheIndex = cachedViewSize;
            ......
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {//成立
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
      .....
    }
....
}

这一系列的调用中会根据viewHolder的flag将它放到不同的缓存中去,这时这些被标记的viewHolder的flags会导致它被RV移除并被移动到RecyclerViewPool中去,如图所示:


notifyDataChanged.png

到这里就明确了在调用notifyDataChanged的时候,RV中所有的viewHolder都会标记失效并被移除到RecyclerPool中去,mCachedView、mAttchedView都为空。

调用notifyItemChanged方法

和notifyDataChanged调用一样,调用链如下

//==RV中
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}
//==AdapterHelper中
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    if (itemCount < 1) {
        return false;
    }
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
    mExistingUpdateTypes |= UpdateOp.UPDATE;
    return mPendingUpdates.size() == 1;
}
//AdapterHelper中,mCallback是RV 
  private void postponeAndUpdateViewHolders(UpdateOp op) {
      if (DEBUG) {
          Log.d(TAG, "postponing " + op);
      }
      mPostponedList.add(op);
      switch (op.cmd) {
          case UpdateOp.ADD:
              mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
              break;
          case UpdateOp.MOVE:
              mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
              break;
          case UpdateOp.REMOVE:
              mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,  op.itemCount);
              break;
          case UpdateOp.UPDATE:
              mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
              break;
          default:
              throw new IllegalArgumentException("Unknown update op type for " + op);
      }
  }

//==RV中
  @Override
  public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
      viewRangeUpdate(positionStart, itemCount, payload);
      mItemsChanged = true;
  }

  void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
      final int childCount = mChildHelper.getUnfilteredChildCount();
      final int positionEnd = positionStart + itemCount;

      for (int i = 0; i < childCount; i++) {
          final View child = mChildHelper.getUnfilteredChildAt(i);
          final ViewHolder holder = getChildViewHolderInt(child);
          if (holder == null || holder.shouldIgnore()) {
              continue;
          }
          if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
              // We re-bind these view holders after pre-processing is complete so that
              // ViewHolders have their final positions assigned.
              holder.addFlags(ViewHolder.FLAG_UPDATE);
              holder.addChangePayload(payload);
              // lp cannot be null since we get ViewHolder from it.
              ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
          }
      }
      mRecycler.viewRangeUpdate(positionStart, itemCount);
  }

AdapterHelper的onItemRangeChanged将这个更新事件添加到一个排队执行的List中,triggerUpdateProcessor或者其他事件会触发List的中的事件被执行,不管怎么调用最后都会调用到AdapterHelper的postponeAndUpdateViewHolders,在这里会将需要更新的viewHolder标记为ViewHolder.FLAG_UPDATE,这些都是在RV真正开始布局之前调用的,之后会走正常的layout流程,又以LienarLayoutManager为例,还是会调用detachAndScrapAttachedViews,但这次只有需要更新的viewHolder被标记为FLAG_UPDATE了,其他都没有格外标记,回收的逻辑如下

//==RV中
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);
           }
       }
//==Recycler中
       void scrapView(View view) {
           final ViewHolder holder = getChildViewHolderInt(view);
           //对不需要更新的viewHolder成立
           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);
               mAttachedScrap.add(holder);
           } else {  //对需要更新的viewHolder成立
               if (mChangedScrap == null) {
                   mChangedScrap = new ArrayList<ViewHolder>();
               }
               holder.setScrapContainer(this, true);
               mChangedScrap.add(holder);
           }
       }

RV中所有的viewHolder都会被detach,对不需要更新的viewHolder放到了mAttachedScrap中,对需要更新的viewHolder是放到了mChangedScrap中,其他的缓存没有变,如图所示:


notifyItemChanged.png

调用notifyItemInserted方法

和notifyItemChanged一样,在layout之前会执行AdapterHelper中的postponeAndUpdateViewHolders,这次是UpdateOp.ADD了,所以会调用RV的offsetPositionsForAdd,如下:

@Override
public void offsetPositionsForAdd(int positionStart, int itemCount) {
    offsetPositionRecordsForInsert(positionStart, itemCount);
    mItemsAddedOrRemoved = true;
}

void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
            if (DEBUG) {
                Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
                        + holder + " now at position " + (holder.mPosition + itemCount));
            }
            //改变ViewHolder的位置
            holder.offsetPosition(itemCount, false);
            mState.mStructureChanged = true;
        }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
}

//==ViewHolder中
void offsetPosition(int offset, boolean applyToPreLayout) {
    if (mOldPosition == NO_POSITION) {
        mOldPosition = mPosition;
    }
    if (mPreLayoutPosition == NO_POSITION) {
        mPreLayoutPosition = mPosition;
    }
    if (applyToPreLayout) {
        mPreLayoutPosition += offset;
    }
    mPosition += offset;
    if (itemView.getLayoutParams() != null) {
        ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
    }
}

这个过程只是改变了ViewHolder的位置,没有改变它的状态,参考刚才对notifyItemChanged的分析,RV重新layout时也只是将所有的viewHolder detach了,然后放进mAttachedScrap中,只是这一次viewHolder中代表位置的mPosition值发生了变化。

滑动

具体的分析参见RecyclerView缓存机制(回收些啥?)
可以概括为:在滑动的时候 ,触发RV的onTouchEvent,在它的ACTION_MOVE中,如果这个滑动被认为是一次SCROLL_STATE_DRAGGING就会将这个滑动事件转发给LayoutMamager,以LinearLayoutManager为例,会调用其中的fill方法去添加新的viewHolder到RV中,同时也会detach和回收掉滑出屏幕的viewHolder,fill方法中会依次调用新的item的measure和layout方法,最后在scrollBy方法中会移动所有的item,导致item的invalidate被调用,绘制view。
对滑出屏幕的viewHoler会调用recycleChildren回收,如下:

//==RV中
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);
        }
    }
}

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    //detach
    removeViewAt(index);
    recycler.recycleView(view);
}

//==Recycler中
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);
}

移出屏幕的viewHolder会被detach,然后调用Recycler的recycleView方法回收,这里又调用到了
recycleViewHolderInternal方法,这次会被放到mCachedView中去,如果mCachedView满了就把以前的发到Pool中去,再把这个viewHolder放进mCachedView中。

复用

前面提到过在LinearLayoutManager的fill方法会去添加新的viewHolder到RV中,那这个viewHolder从哪里来呢?显然是要从缓存中获取的,Recycler提供了一个叫tryGetViewHolderForPositionByDeadline的方法获取缓存的viewHolder,具体分析请看
RecyclerView缓存机制(咋复用?)
可以概括为: RV在每一次需要为某个位置添加viewHolder的时候都会通过这个方法去获取viewHolder,首先是根据position在mAttchScrap中去寻找,没有再根据position去mCachedViews中寻找,如果找到了直接返回使用,没有再去RecyclerViewPool中根据itemType寻找。

参考:
Android ListView与RecyclerView对比浅析--缓存机制

RecyclerView缓存机制

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容