Android RecyclerView四级缓存源码层详细分析

RecyclerView是一个非常重要的控件,是任何一个研发都需要掌握的,这个控件的设计也是非常优秀的,值得我们去学习。RecyclerView的核心就是缓存机制,RecyclerView为了提升效率使用了4级缓存:

  • mChangeScrap与 mAttachedScrap:用来缓存还在屏幕内的 ViewHolder,是ViewHolder的ArrayList 集合。
  • mCacheView:缓存将要隐藏ViewHolder 下次将要显示的ViewHolder 先从这个缓存里边获取,也是ViewHolder的 ArrayList 集合。
  • mViewChcheExtension:需要用户自己实现的缓存,这一级系统会调用一个抽象方法,这个方法需要用户自己实现。
  • mRecyclerPool:缓存池 ,这个用户根据不同的ViewType保存缓存池 ,这个缓存池是一个二维数组 外部是ScrapData 的SparseArray数组,内部是ArrayList数组。

1.缓存的使用流程源码分析-滑动入口

当用户在滑动Item的时候会进行ViewHolder的复用,下面来看滑动方法:RecyclerView的onTouchEvent方法case MotionEvent.ACTION_MOVE

@Override
public boolean onTouchEvent(MotionEvent e) {
 if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
 return false;
 }
 if (dispatchOnItemTouch(e)) {
 cancelTouch();
 return true;
 }
 if (mLayout == null) {
 return false;
 }
 ...
 switch (action) {
 case MotionEvent.ACTION_DOWN: 
	...
 case MotionEvent.ACTION_MOVE: {
 ...
 if (mScrollState == SCROLL_STATE_DRAGGING) {
 mLastTouchX = x - mScrollOffset[0];
 mLastTouchY = y - mScrollOffset[1];
 //入口在这里 因为滑动的时候会发生缓存操作 所以一个入口在这里
 if (scrollByInternal(
 canScrollHorizontally ? dx : 0,
 canScrollVertically ? dy : 0,
 vtev)) {
 getParent().requestDisallowInterceptTouchEvent(true);
 }
 if (mGapWorker != null && (dx != 0 || dy != 0)) {
 mGapWorker.postFromTraversal(this, dx, dy);
 }
 }
 } break;
 case MotionEvent.ACTION_POINTER_UP: {
 onPointerUp(e);
 } break;
 case MotionEvent.ACTION_UP: 
 ...
 vtev.recycle();
 return true;
}

scrollByInternal 方法就是使用缓存的入口方法

下面来看scrollByInternal方法

boolean scrollByInternal(int x, int y, MotionEvent ev) {
 int unconsumedX = 0, unconsumedY = 0;
 int consumedX = 0, consumedY = 0;
 consumePendingUpdateOperations();
 if (mAdapter != null) {
 eatRequestLayout();
 onEnterLayoutOrScroll();
 Trace.beginSection(TRACE_SCROLL_TAG);
 if (x != 0) {
 consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
 unconsumedX = x - consumedX;
 }
 if (y != 0) {
 consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
 unconsumedY = y - consumedY;
 }
 Trace.endSection();
 repositionShadowingViews();
 onExitLayoutOrScroll();
 resumeRequestLayout(false);
 }
 ...
 return consumedX != 0 || consumedY != 0;
}

这里区分横向和纵向滑动:scrollHorizontallyBy与scrollVerticallyBy

下面分析纵向滑动的情况scrollVerticallyBy(横向类似):

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
 RecyclerView.State state) {
 if (mOrientation == HORIZONTAL) {
 return 0;
 }
 return scrollBy(dy, recycler, state);
}

这里调用了scrollBy方法,继续往下跟

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
 if (getChildCount() == 0 || dy == 0) {
 return 0;
 }
 mLayoutState.mRecycle = true;
 ensureLayoutState();
 final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
 final int absDy = Math.abs(dy);
 updateLayoutState(layoutDirection, absDy, true, state);
 final int consumed = mLayoutState.mScrollingOffset
 + fill(recycler, mLayoutState, state, false);
 if (consumed < 0) {
 if (DEBUG) {
 Log.d(TAG, "Don't have any more elements to scroll");
 }
 return 0;
 }
 final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
 mOrientationHelper.offsetChildren(-scrolled);
 if (DEBUG) {
 Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
 }
 mLayoutState.mLastScrollDelta = scrolled;
 return scrolled;
}

这里有个关键方法:fill,当布局或者上下滚动的时候会调用fill方法。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
 RecyclerView.State state, boolean stopOnFocusable) { //布局或者上下滚动的时候会调用
 // max offset we should set is mFastScroll + available
 final int start = layoutState.mAvailable;
 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
 // TODO ugly bug fix. should not happen
 if (layoutState.mAvailable < 0) {
 layoutState.mScrollingOffset += layoutState.mAvailable;
 }
 recycleByLayoutState(recycler, layoutState); //回收ViewHolder
 }
 int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
 LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
 layoutChunkResult.resetInternal();
 layoutChunk(recycler, state, layoutState, layoutChunkResult); //循环调用 这里是layout的核心
 if (layoutChunkResult.mFinished) {
 break;
 }
 ...
 }
 if (DEBUG) {
 validateChildOrder();
 }
 return start - layoutState.mAvailable;
}

layoutChunk这个方法是使用缓存的入口,recycleByLayoutState这个是进行ViewHolder缓存的入口。

下面来看layoutChunk:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
 LayoutState layoutState, LayoutChunkResult result) {
 View view = layoutState.next(recycler);
 if (view == null) {
 if (DEBUG && layoutState.mScrapList == null) {
 throw new RuntimeException("received null view when unexpected");
 }
 // if we are laying out views in scrap, this may return null which means there is
 // no more items to layout.
 result.mFinished = true;
 return;
 }
 	...
 result.mFocusable = view.isFocusable();
}

这个方法里边调用了layoutState的next方法得到一个View,那么关键就是next方法了

View next(RecyclerView.Recycler recycler) {
 if (mScrapList != null) {
 return nextViewFromScrapList();
 }
 final View view = recycler.getViewForPosition(mCurrentPosition);
 mCurrentPosition += mItemDirection;
 return view;
}
public View getViewForPosition(int position) {
 return getViewForPosition(position, false);
 }
View getViewForPosition(int position, boolean dryRun) {
 return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
 }

这个方法又调用了recycler.getViewForPosition方法,最终调到了tryGetViewHolderForPositionByDeadline这个方法。

下面来分析tryGetViewHolderForPositionByDeadline这个方法,整个ViewHolder的复用流程都在这里,这里是最核心的位置:

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
 boolean dryRun, long deadlineNs) {
 if (position < 0 || position >= mState.getItemCount()) {
 throw new IndexOutOfBoundsException("Invalid item position " + position
 + "(" + position + "). Item count:" + mState.getItemCount());
 }
 boolean fromScrapOrHiddenOrCache = false;
 ViewHolder holder = null;
 // 0) If there is a changed scrap, try to find from there
 if (mState.isPreLayout()) { //通过位置从mChangeScrap缓存中获取ViewHolder
 holder = getChangedScrapViewForPosition(position);
 fromScrapOrHiddenOrCache = holder != null;
 }
 // 1) Find by position from scrap/hidden list/cache
 if (holder == null) {//通过position的方式从mAttachScrap或者mCacheViews中获取ViewHolder
 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
 ...
 }
 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());
 }
 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); //通过id的方式从mAttachScrap或者mCacheViews中获取ViewHolder
 if (holder != null) {
 // update position
 holder.mPosition = offsetPosition;
 fromScrapOrHiddenOrCache = true;
 }
 }
 if (holder == null && mViewCacheExtension != null) { //从用户自定义缓存获取ViewHolder
 // We are NOT sending the offsetPosition because LayoutManager does not
 // know it.
 final View view = mViewCacheExtension
 .getViewForPositionAndType(this, position, type);
 ...
 }
 if (holder == null) { // 从缓存池获取ViewHolder
 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;
 } //如果还是获取不到ViewHolder,那么就需要通过createViewHolder创建了
 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");
 }
 }
 }
 ...
 boolean bound = false;
 if (mState.isPreLayout() && holder.isBound()) {
 // do not update unless we absolutely have to.
 holder.mPreLayoutPosition = position;
 } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
 if (DEBUG && holder.isRemoved()) {
 throw new IllegalStateException("Removed holder should be bound and it should"
 + " come here only in pre-layout. Holder: " + holder);
 }
 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
 //这里会调用到onBindViewHolder方法进行数据的绑定
 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
 }
 ...
 return holder;
}
  • getChangedScrapViewForPosition:通过位置从mChangeScrap缓存中获取ViewHolder。
  • getScrapOrHiddenOrCachedHolderForPosition:通过position的方式从mAttachScrap或者mCacheViews中获取ViewHolder。
  • getScrapOrCachedViewForId:通过id的方式从mAttachScrap或者mCacheViews中获取ViewHolder
  • mViewCacheExtension.getViewForPositionAndType:从用户自定义缓存获取ViewHolder(这里系统未做实现,需要用户自定义)
  • getRecycledViewPool().getRecycledView(type):从缓存池获取ViewHolder
  • mAdapter.createViewHolder:如果从各个缓存中获取不到ViewHolder,那么就需要通过createViewHolder创建了
  • tryBindViewHolderByDeadline:这里会调用到onBindViewHolder方法进行数据的绑定

以上就是整个ViewHolder获取过程,首先从缓存池获取,获取不到才会创建,然后进行数据绑定。

2.RecyclerView的缓存流程

在进行layout操作的时候就会进行ViewHolder的缓存操作,将创建好的ViewHolder缓存到缓存池,以便直接使用,下面分析一下ViewHolder是如何缓存到缓存池中的。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
 Trace.beginSection(TRACE_ON_LAYOUT_TAG);
 dispatchLayout(); //这里是摆放的入口
 Trace.endSection();
 mFirstLayoutComplete = true;
}

下面是dispatchLayout:

void dispatchLayout() {
 if (mAdapter == null) {
 Log.e(TAG, "No adapter attached; skipping layout");
 // leave the state in START
 return;
 }
 if (mLayout == null) {
 Log.e(TAG, "No layout manager attached; skipping layout");
 // leave the state in START
 return;
 }
 mState.mIsMeasuring = false;
 if (mState.mLayoutStep == State.STEP_START) {
 dispatchLayoutStep1();
 mLayout.setExactMeasureSpecsFrom(this);
 dispatchLayoutStep2();
 } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
 || mLayout.getHeight() != getHeight()) {
 // First 2 steps are done in onMeasure but looks like we have to run again due to
 // changed size.
 mLayout.setExactMeasureSpecsFrom(this);
 dispatchLayoutStep2();
 } else {
 // always make sure we sync them (to ensure mode is exact)
 mLayout.setExactMeasureSpecsFrom(this);
 }
 dispatchLayoutStep3();
}

下面来看dispatchLayoutStep2:

private void dispatchLayoutStep2() {
 eatRequestLayout();
 onEnterLayoutOrScroll();
 mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
 mAdapterHelper.consumeUpdatesInOnePass();
 mState.mItemCount = mAdapter.getItemCount();
 mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
 // Step 2: Run layout
 mState.mInPreLayout = false;
 mLayout.onLayoutChildren(mRecycler, mState);
 mState.mStructureChanged = false;
 mPendingSavedState = null;
 // onLayoutChildren may have caused client code to disable item animations; re-check
 mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
 mState.mLayoutStep = State.STEP_ANIMATIONS;
 onExitLayoutOrScroll();
 resumeRequestLayout(false);
}

这个方法中会调用onLayoutChildren方法,这个方法是缓存的核心所在。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
 ...
 onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
 detachAndScrapAttachedViews(recycler); //分离并废弃附加视图
 ...
}

这个方法内容较多,做了省略。detachAndScrapAttachedViews这个方法会将ViewHolder缓存到缓存池中。

public void detachAndScrapAttachedViews(Recycler recycler) {
 final int childCount = getChildCount();
 for (int i = childCount - 1; i >= 0; i--) {
 final View v = getChildAt(i);
 scrapOrRecycleView(recycler, i, v);
 }
}

调到了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;
 }
 if (viewHolder.isInvalid() && !viewHolder.isRemoved()
 && !mRecyclerView.mAdapter.hasStableIds()) {
 removeViewAt(index);
 recycler.recycleViewHolderInternal(viewHolder); //这是一个收集的情况
 } else {
 detachViewAt(index);
 recycler.scrapView(view); //这是一个情况
 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
 }
}
  • recycleViewHolderInternal :这个方法主要是缓存到mCacheViews或者RecyclerViewPool中
  • scrapView:这个情况会将ViewHolder缓存到mAttachScrap中或者mChangedScrap中

下面来分析recycleViewHolderInternal:

void recycleViewHolderInternal(ViewHolder holder) { //主要处理CacheViews 和RecyclerPool 的缓存
 ...
 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;
 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); //这里是加入到mCachedViews中
 cached = true;
 }
 if (!cached) { //这里是加入到RecycledViewPool缓存池中
 addViewHolderToRecycledViewPool(holder, true);
 recycled = true;
 }
 }
 ...
}
  • mCachedViews.add(targetCacheIndex, holder):将ViewHolder加入到mCachedViews中
  • addViewHolderToRecycledViewPool:加入到RecycledViewPool缓存池中

下面是scrapView部分:

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.");
 }
 holder.setScrapContainer(this, false);
 mAttachedScrap.add(holder);
 } else {
 if (mChangedScrap == null) {
 mChangedScrap = new ArrayList<ViewHolder>();
 }
 holder.setScrapContainer(this, true);
 mChangedScrap.add(holder);
 }
}

根据不同的情况会将ViewHolder缓存到mAttachedScrap或者mChangedScrap中

3.RecyclerView缓存总结

RecyclerView 缓存的是ViewHolder

RecyclerView采用了四级缓存:缓存的分类是根据功能区分

  • mAttachedScrap : 缓存可见的ViewHolder 用于 执行onLayout的时候 ArrayList 集合
  • mCacheView:缓存将要隐藏ViewHolder 下次将要显示的ViewHolder 先从这个缓存里边获取 ArrayList 集合
  • mViewChcheExtension:需要用户自己实现的缓存
  • mRecyclerPool:缓存池,这个用户根据不同的ViewType保存缓存池 , ScrapData包含一个ArrayList mScrap 是一个SparseArray数组,所以缓存池是一个二维数组。

ViewHolder的创建流程

  • 先从mAttachedScrap 缓存 查找ViewHolder
  • 然后从mCacheView 查找
  • 然后从mViewCacheExtension
  • 然后 从来mRecyclerPool查找
  • 如果还是没有 就需要调用onCreateViewHolder方法来新创建
作者:lpf_wei原文地址:https://blog.csdn.net/u014078003/article/details/127226922

%s 个评论

要回复文章请先登录注册