文章目录

  • Recycler
  • 四级缓存
  • 屏幕内缓存 mAttachedScrap 和 mChangedScrap
  • 进入缓存
  • 屏幕外缓存 mCachedViews
  • 滚动
  • 缓存池 RecycledViewPool
  • 优化
  • notifyDataSetChanged
  • setHasFixedSize


Recycler承担了RecyclerView中的缓存功能,其中定义的5个集合代表了四个缓存层级。

Recycler

//一级缓存
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
//二级缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
///三级缓存
private ViewCacheExtension mViewCacheExtension;
//四级缓存
RecycledViewPool mRecyclerPool;
//默认mCachedViews的最大缓存数量
static final int DEFAULT_CACHE_SIZE = 2;

关于四级缓存的优先级,可以看Recycler中的以下方法:

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            
            //省略其他代码
            ....
            
            ViewHolder holder = null;

            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            }
            if (holder == null) {
                // 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) { // fallback to pool
                    holder = getRecycledViewPool().getRecycledView(type);
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                }
            }

            return holder;
        }

用图片的形式更加直观一些:

android recyclerview 缓存实现 recyclerview缓存机制_二级缓存


缓存有两个边界条件:进和出。

四级缓存

屏幕内缓存 mAttachedScrap 和 mChangedScrap

scrap仅仅在layout期间不为空。当LayoutManager开始 layout 的时候(pre-layout 或 post-layout),会将所有的viewHolder都放到scrap中。然后一个个再取回来,除非有些view发生了变化。

进入缓存

LinearLayoutManager.onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
-> LayoutManager.detachAndScrapAttachedViews(Recycler recycler)
-> LayoutManager.scrapOrRecycleView(Recycler recycler, int index, View view)
-> Recycler.scrapView(View view)
void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        // 标记为移除或失效的 || 没有改变 || item 无动画或动画不复用
        mAttachedScrap.add(holder);
    } else {
        // 放入 mChangedScrap
        mChangedScrap.add(holder);
    }
}

屏幕外缓存 mCachedViews

mCachedViews是一个 ArrayList 类型集合,不区分 viewHolder 的类型,大小限制为2,可以使用 setItemViewCacheSize()这个方法调整它的大小。

滚动

当发生滚动时,方法调用流程

RecyclerView
-> onTouchEvent(MotionEvent e)
-> scrollByInternal(int x, int y, MotionEvent ev)
-> scrollStep(int dx, int dy, @Nullable int[] consumed)
LinearLayoutManager
-> scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state)
-> scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state)
-> fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable)
-> recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState)
-> recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace)
-> recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex)
RecyclerView.LayoutManager
-> removeAndRecycleViewAt(int index, @NonNull Recycler recycler)
RecyclerView.Recycler
-> recycleView(@NonNull View view)
-> recycleViewHolderInternal(ViewHolder holder)
-> recycleCachedViewAt(int cachedViewIndex)
-> addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled)
RecyclerView.RecycledViewPool
-> putRecycledView(ViewHolder scrap)

在下面的方法中,判断了Holder是否需要回收

private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
        final int limit = scrollingOffset - noRecycleSpace;
        final int childCount = getChildCount();
        //依次判断childView是否已经离开屏幕可见区域,并进行回收
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                // stop here
                recycleChildren(recycler, 0, i);
                return;
            }
        }
}

回收时,如果mCachedViews已满,移除mCachedViews中的第一个元素,将其放入RecycledViewPool

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)) {
             // mCachedViews的默认缓存数量是2,如果缓存已满,移除掉最老的Holer,为新的Holder让出空间
             int cachedViewSize = mCachedViews.size();
             if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
             //移除mCachedViews中的第一个元素,将其放入RecycledViewPool
                 recycleCachedViewAt(0);
                 cachedViewSize--;
             }
             int targetCacheIndex = cachedViewSize;
             mCachedViews.add(targetCacheIndex, holder);
             cached = true;
         }
         if (!cached) {
         // 不能放进 mCacheViews 的放 RecyclerViewPool
             addViewHolderToRecycledViewPool(holder, true);
             recycled = true;
         }
     } 
}

void recycleCachedViewAt(int cachedViewIndex) {
     ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
     addViewHolderToRecycledViewPool(viewHolder, true);
     mCachedViews.remove(cachedViewIndex);
}

public void putRecycledView(ViewHolder scrap) {
      final int viewType = scrap.getItemViewType();
      final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
      //如果RecycledViewPool的该类型集合已满(默认最大值 = 5),不做任何处理
      if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
          return;
      }
      scrap.resetInternal();
      scrapHeap.add(scrap);
}

缓存池 RecycledViewPool

RecycledViewPool,它储存了各个类型的 viewHolder。最大数量为5,可以通过 setMaxRecycledViews() 方法来设置每个类型储存的容量。可以多个列表公用一个 RecycledViewPool,使用 setRecycledViewPool() 方法。

优化

notifyDataSetChanged

当调用这个方法时,所有的ViewHolder被标记为无效,意味着全部将进入RecycledViewPool缓存,其他缓存为空,因为RecycledViewPool的默认缓存最大值只有5,当RecyclerView展示的item数量超过5时,无法取得缓存,会选择重新创建,执行onCreateViewHolder。考虑到此,刷新数据尽量不要调用notifyDataSetChanged,使用其他方法代替,或者,调整RecycledViewPool的最大缓存数量

setHasFixedSize

如果RecyclerView的尺寸不受adapter的影响,RecyclerView可以做一些优化。
RecyclerView仍可以基于其他因素更改其尺寸(例如,父级的大小),但大小的计算不依赖于子项的大小或adapter的内容(adapter中的数量除外)。
设置为true,可避免adapter内容更改时整个布局无效。

简而言之,当设置为true的时候,adapter将不再影响recyclerView的大小,由此可以避免刷新recyclerView的时候,重新布局计算大小。

#RecyclerView
public void setHasFixedSize(boolean hasFixedSize) {
    mHasFixedSize = hasFixedSize;
}

当调用adapter.notifyDataSetChanged一类方法刷新界面时,会执行到下面的对应方法:

private class RecyclerViewDataObserver {

      @Override
      public void onChanged() {
          processDataSetCompletelyChanged(true);
          if (!mAdapterHelper.hasPendingUpdates()) {
              requestLayout();
          }
      }

      @Override
      public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
          if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
              triggerUpdateProcessor();
          }
      }

      @Override
      public void onItemRangeInserted(int positionStart, int itemCount) {
          if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
              triggerUpdateProcessor();
          }
      }

      @Override
      public void onItemRangeRemoved(int positionStart, int itemCount) {
          if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
              triggerUpdateProcessor();
          }
      }

      @Override
      public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
          if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
              triggerUpdateProcessor();
          }
      }

      void triggerUpdateProcessor() {
          if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
              ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
          } else {
              mAdapterUpdateDuringMeasure = true;
              requestLayout();
          }
      }
  }

onChanged和其他方法的区别在于,其他方法都调用了triggerUpdateProcessortriggerUpdateProcessor使用mHasFixedSize做了一些判断,适当的条件下才去调用requestLayoutonChanged则直接调用了requestLayout。因此想要跳过requestLayout,除了不调用onChanged外,mHasFixedSize必须设置为true。

mHasFixedSize为true的情况下,依然可以通过调用onChanged,也就是notifyDataSetChanged,调整recyclerView的大小。