RecyclerView缓存分析

在之前渲染过程中,layoutChunk方法调用View view = layoutState.next(recycler)代码,该代码获取一个view视图,内部包含缓存逻辑。RecyclerView缓存共有四级,以下简单说下四级缓存。

层级
一级缓存 mAttachedScrap:数据没有变化的ViewHolder ; mChangedScrap:数据已经改变的 ViewHolder
二级缓存 mCachedViews:滑出屏幕之外的 ViewHolder
三级缓存 mViewCacheExtensions:自定义的缓存池
四级缓存 mRecyclerPool:mCachedViews无法存放时候放置的地方,需要重新onBindViewHolder()

其他:mHiddenViews 和Recycler无关。在子view被移出屏幕的动画执行期间暂时缓存viewHolder。

首先看下LayoutState里面的next方法。

View next(RecyclerView.Recycler recycler) {
    //执行layoutForPredictiveAnimations之后mScrapList不为null
    //mScrapList 实质为不可修改的mAttachedScrap
    //这边忽略,一般mScrapList==null
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //获取一个view
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

在mScrapList == null 的情况下getViewForPosition方法进一步获取View

@NonNull
public View getViewForPosition(int position) {
       return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
      return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                 boolean dryRun, long deadlineNs) {
    // ...省略
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        // 预加载下,通过mChangedScrap获取ViewHolder
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
   // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
        // 通过mAttachedScrap、HiddenView(隐藏的view) 、mCachedViews来获取ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        //. ..省略
   }
   if (holder == null) {
        //...省略
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        if (mAdapter.hasStableIds()) { 
            // stableId设置为true的时候,相当于调用了viewholder中的view的requestFocus()方法。这个方法的作用是给这个请求requestFocus()的方法的view的下面的那个view焦点。
            //根据ids来查找ViewHolder,设置了id,那么会忽略标志位强行复用viewHolder。
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            //...省略
        }
       if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not
            // know it.
            //自定义的mViewCacheExtensions缓存获取ViewHolder
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
                //...省略
            }
        }
        if (holder == null) { 
             holder = getRecycledViewPool().getRecycledView(type);
             //mRecyclerPool中获取ViewHolder
        }
         //..省略
        if (holder == null) {
            //..省略
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            //..省略
            }
            //..省略
        }
    }
     //..省略
     boolean bound = false;
     //bindViewHolder
    if (mState.isPreLayout() && holder.isBound()){
       bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    //..省略
    return holder;
}

tryGetViewHolderForPositionByDeadline 中的代码展示完,这边展示的是一级缓存mChangedScrap、mAttachedScrap和二级mCachedViews的逻辑。以下为在一级、二级缓存中获取ViewHolder常用的代码。

ViewHolder getChangedScrapViewForPosition(int position) {
        // If pre-layout, check the changed scrap for an exact match.
        final int changedScrapSize;
        if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
             return null;
        }
        // find by position
       for (int i = 0; i < changedScrapSize; i++) {
          //通过position查找viewholder
          final ViewHolder holder = mChangedScrap.get(i);
          //...省略
   
       if (mAdapter.hasStableIds()) {
            //再次校验id,获取viewhodler
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
                 final long id = mAdapter.getItemId(offsetPosition);
                 for (int i = 0; i < changedScrapSize; i++) {
                      final ViewHolder holder = mChangedScrap.get(i);
                      if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                           holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                         return holder;
                       }
                  }
             }
        }
       return null;
 }

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++) {
            //获取一个精确的,合法的scrap
            final ViewHolder holder = mAttachedScrap.get(i);
             //...省略
             return holder;
        }
    }
   if (!dryRun) {
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
            // This View is good to be used. We just need to unhide, detach and move to the
            // scrap list.
            //隐藏但未被移除的view
            final ViewHolder vh = getChildViewHolderInt(view);
            //...省略
            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
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            return holder;
        }
    }
    return null;
}

//Adapter的notifyDataSetChanged会令所有viewHolder为FLAG_UPDATE和FLAG_INVALID
//设置id,会忽略标志位置,强行获取ViewHolder。确保不多次创建view。
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);
        //根据id来查找ViewHolder
        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 && !holder.isAttachedToTransitionOverlay()) {
            if (type == holder.getItemViewType()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

以上完成了ViewHolder的创建,接下来查看各个缓存的生成。

  • mChangedScrap 和 mAttachedScrap
    以下代码会将变化的viewholder存入到mChangedScrap、不变的viewholder存入到mAttachedScrap
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //..省略
       detachAndScrapAttachedViews(recycler);
       //..省略
}

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.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);
    }
}
  • mCachedViews
    当item被滑出屏幕后会被保存在mCachedViews中,在dispatchLayoutStep1方法中调用
    processAdapterUpdatesAndSetAnimationFlags时候会坚持mCachedViews,如果mCachedViews有变化,则会mCachedViews中移除掉ViewHolder。
void recycleViewHolderInternal(ViewHolder holder) {
 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;
        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;
    }
}
  • mRecyclerPool
    当mCachedViews被填满溢出的viewholder,会被移动到RecyclerRool中。
  • mHiddenViews 和 mUnmodifiableAttachedScrap :mHiddenViews当view被移除屏幕又要执行动画,所有会暂存到mHiddenViews中。mUnmodifiableAttachedScrap实为不可修改的mAttachedScrap,在执行动画区间会暂时充当mAttachedScrap中的一项,只有动画只想完成后会删除mAttachedScrap中的viewholder。

你可能感兴趣的:(RecyclerView缓存分析)