RecyclerView缓存机制总结
基本概念
scrapped:
即 dettach 和 attach 。对一个view做dettach操作,会把这个view从 ViewGroup 的 children 数组里移除,但是不会重绘UI。
在 LayoutManager 布局时,会 dettach 掉所有的子view,再从 Recycler 中获取view(缓存或新创建)后再 addView。 因为做过 dettach ,已经从 children 数组里移除了。所以再addView不会造成问题。 
RecyclerView中涉及到缓存的集合
- mAttachedScrap
- 显示在屏幕中,未与RecyclerView分离但被标记移除的Holder。 LayoutManager 布局时的临时缓存,布局完成后为空
 
 
- mChangedScrap
- 显示在屏幕中,和 mAttachedScrap 类似,在预布局的时候会用到
 
 
- mCachedViews 
 
- mRecyclerPool
- 在屏幕外的Holder。当mCachedViews满时,存储至此。按照ViewType进行分类存储。默认大小为5。从中取出的Holder需要调用onBindViewHolder方法
 
 
mCachedViews中取出的Holder是直接可用的,不需要调用onCreatedViewHolder和onBindViewHolder方法。
mAttachedScrap 和 mChangedScrap 的插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | 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 ..         }         holder.setScrapContainer(this, false);         mAttachedScrap.add(holder);     } else {         if (mChangedScrap == null) {             mChangedScrap = new ArrayList<ViewHolder>();         }         holder.setScrapContainer(this, true);         mChangedScrap.add(holder);     } }
  | 
 
LayoutManager 在布局之前会scrap所有的view。 会根据不同的情况放入到 mAttachedScrap 或者 mChangedScrap
试了下,一般都是放在 mAttachedScrap 里,即使 holder.isUpdated() 为true。 所以 mAttachedScrap 里的ViewHolder也是有可能调用 onBind 的。
mChangedScrap 试了下只在有一些动画需要 preLayout 的时候有用到
ViewHolder 只有在满足下面情况才会被添加到 mChangedScrap:当它关联的 item 发生了变化(notifyItemChanged 或者 notifyItemRangeChanged 被调用),并且 ItemAnimator 调用 ViewHolder#canReuseUpdatedViewHolder 方法时,返回了 false。否则,ViewHolder 会被添加到AttachedScrap 中。
canReuseUpdatedViewHolder 返回 “false” 表示我们要执行用一个 view 替换另一个 view 的动画,例如淡入淡出动画。 “true”表示动画在 view 内部发生。
mAttachedScrap 在 整个布局过程中都能使用,但是 changed scrap — 只能在预布局阶段使用。
mCachedViews 和 mRecyclerPool 的插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
   | 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)) {                          int cachedViewSize = mCachedViews.size();             if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {                 recycleCachedViewAt(0);                 cachedViewSize--;             }
              int targetCacheIndex = cachedViewSize;             if (ALLOW_THREAD_GAP_WORK                     && cachedViewSize > 0                     && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {                 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;         }     }     ... } void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {     ...     holder.mBindingAdapter = null;     holder.mOwnerRecyclerView = null;      getRecycledViewPool().putRecycledView(holder); }
  | 
 
先尝试加入到 mCachedViews 集合,如果满了,就删除第一个
为加入到 mCachedViews 的会加入到 recyclerPool 里
mCacheViews 缓存是区分 viewType 的, recyclerPool 会区分存储,单个 viewType 容量是5
判定 !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN 为true,才会加入到 mCachedViews 。 所以从 mCachedViews 获取是不需要重新 onBind 的
加入到 recyclerPool 时会设置 holder.mBindingAdapter = null 下面会说到这个
RecyclerView获取Holder的顺序(sdk 28)
LayoutManager 在布局的时候会调用 getViewForPosition(int position) 方法获取 VH 和 View
后续会调用到tryGetViewHolderForPositionByDeadline 中获取viewholder缓存,如果不存在会创建。
- getChangedScrapViewForPosition
 
- getScrapOrHiddenOrCachedHolderForPosition
 
- getScrapOrCachedViewForId
 
- getChildViewHolder
 
- mViewCacheExtension.getViewForPositionAndType
 
- getRecycledViewPool().getRecycledView
 
- mAdapter.createViewHolder
 
从各种缓存集合中获取 ViewHolder 。 
获取的 ViewHolder 是否需要重新 bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | boolean bound = false; if (mState.isPreLayout() && holder.isBound()) {          holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {     final int offsetPosition = mAdapterHelper.findPositionOffset(position);     bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }
  private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,        int position, long deadlineNs) {    holder.mBindingAdapter = null;    holder.mOwnerRecyclerView = RecyclerView.this;    final int viewType = holder.getItemViewType();        mAdapter.bindViewHolder(holder, offsetPosition);    return true; }
  public final void bindViewHolder(@NonNull VH holder, int position) {    boolean rootBind = holder.mBindingAdapter == null;    if (rootBind) {        holder.mPosition = position;        if (hasStableIds()) {            holder.mItemId = getItemId(position);        }        holder.setFlags(ViewHolder.FLAG_BOUND,                ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);        TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);    }    holder.mBindingAdapter = this;    onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());    ... }
   | 
 
!holder.isBound() ViewHolder 是否调用过 onBind 。在bind时,如果 holder.mBindingAdapter == null ,会设置这个 bound 标记位。
holder.needsUpdate() 调用 notifyXxxxx 方法时vh会为true
holder.isInvalid() vh非法
- 是否调用 vh.onBind 只和标记位有关系,和从哪个缓存集合中获取到的无关。 
 
- 加入到 mCacheView 时会判断一定没有 needUpdate 和 Invalide 的标记位,所以mCacheView一定不需要重新bind
 
- recyclerPool 加入元素时设置了 
holder.mBindingAdapter = null ,所以一定需要重新 bind 
- mAttachedScrap 是否需要重新bind是不一定的
 
ListView的缓存机制
缓存的集合
与RecyclerView的不同
- 缓存不同: RecyclerView缓存的是ViewHolder,避免了每次的findViewByid,ListView缓存的是View。
 
- RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView。
而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView) 
- RecyclerView可以实现局部刷新, ListView不行
 
参考:
RecyclerView源码分析缓存机制
RecyclerView的缓存机制
Android ListView 与 RecyclerView 对比浅析–缓存机制
深入理解 RecyclerView 的缓存机制