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

手机APP/开发
322
0
0
2023-06-22
标签   Android
目录
  • 1.缓存的使用流程源码分析-滑动入口
  • 2.RecyclerView的缓存流程
  • 3.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[];
                mLastTouchY = y - mScrollOffset[];
                //入口在这里  因为滑动的时候会发生缓存操作  所以一个入口在这里
                if (scrollByInternal(
                        canScrollHorizontally ? dx :,
                        canScrollVertically ? dy :,
                        vtev)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                if (mGapWorker != null && (dx != || 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 =, unconsumedY = 0;
    int consumedX =, consumedY = 0;
    consumePendingUpdateOperations();
    if (mAdapter != null) {
        eatRequestLayout();
        onEnterLayoutOrScroll();
        Trace.beginSection(TRACE_SCROLL_TAG);
        if (x !=) {
            consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
            unconsumedX = x - consumedX;
        }
        if (y !=) {
            consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
            unconsumedY = y - consumedY;
        }
        Trace.endSection();
        repositionShadowingViews();
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
    }
    ...
    return consumedX != || consumedY != 0;
}

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

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

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

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

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == || dy == 0) {
        return;
    }
    mLayoutState.mRecycle = true;
    ensureLayoutState();
    final int layoutDirection = dy > ? 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 <) {
        if (DEBUG) {
            Log.d(TAG, "Don't have any more elements to scroll");
        }
        return;
    }
    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 <) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState); //回收ViewHolder
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace >) && 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 < || position >= mState.getItemCount()) {
        throw new IndexOutOfBoundsException("Invalid item position " + position
                + "(" + position + "). Item count:" + mState.getItemCount());
    }
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    //) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) { //通过位置从mChangeScrap缓存中获取ViewHolder
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    //) 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 < || offsetPosition >= mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                    + "position " + position + "(offset:" + offsetPosition + ")."
                    + "state:" + mState.getItemCount());
        }
        final int type = mAdapter.getItemViewType(offsetPosition);
        //) 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方法进行数据的绑定