Android RecyclerView源码完全解析

抽出了几天的时间,看了一下RecyclerView和ListView的源码,对这两个控件有了更深的理解。RecyclerView是平时经常用的,一直以为已经完全替代了ListView。但是深层次的学习了这两个View之后,才明白是各有所长吧。ListView的源码和原理,相对来说更加简单些,这里推荐郭霖的文章《Android ListView工作原理完全解析,带你从源码的角度彻底理解》,郭神出品,必是精品!2015年的文章了,但还没有过时。在不同的SDK中,ListView源码会有一些变化,View的绘制也有略微差别,但是原理上没有变化。郭霖的这边文章虽然贴了大量的源码,但是每一步都很细,所以本篇文章我也打算沿用这样的风格。

目录

一、Recycler

二、绘制流程

三、滑动

四、RecyclerView和ListView的缓存比较

五、注意


一、Recycler

Recycler这个类是缓存的核心类,是RecyclerView的内部类,和ListView的RecycleBin作用一样,这里我就不过多的带入ListView的内容了。简单说一下,Recycler类中比较关键的几个属性和方法:

mAttachedScrap和mChangedScrap(一级缓存):这两个集合是RecyclerView的一级缓存,但是我测试过程中发现正常情况下,并没有向这两个集合中加入ViewHolder,其他情况有没有把ViewHolder加入到集合中我不太清楚,即便有,这两个集合也不是一级缓存了。网上好多博客都这么说的,我也很奇怪,有知道的朋友希望在留言区告诉我~

mCachedViews(二级缓存):从屏幕上完全移除后的ViewHolder都会加入到mCachedViews

mViewCacheExtension(三级缓存):自定义缓存,这个缓存由我们自己定义。这是一个抽象类,内部的方法需要覆写,存取的规则由我们自己定义。我没有使用过这级的缓存,现有的缓存完全支持我所了解的需求

mRecyclerPool(四级缓存):如果mCachedViews的数量达到阈值,那么就会移除mCachedViews中第一个ViewHolder(也就是最早加入的ViewHolder),加入到mRecyclerPool中。mRecyclerPool并不是数组,使用的是SpareseArray,以viewType为key,ViewHolder的数组为value进行存储的。也就是说,mRecyclerPool是根据viewType进行存储的。

对于缓存的获取过程,我画了一张图

我们暂时先了解到这里,之后在绘制和滑动流程中,再细致的了解,这里我们先混一个眼熟

二、绘制流程

View的绘制流程,大家应该都知道,measure--->layout--->draw。RecyclerView不例外,也是这个流程,我们先看onMeasure方法

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode ==MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            if (mState.mLayoutStep == RecyclerView.State.STEP_START) {
                dispatchLayoutStep1();
            }
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            ....
        }
    }

因为篇幅的原因,我省略了部分代码和一些英文注释。mLayout其实就是我们所设置LayoutManager。

第7行中的mLayout.isAutoMeasureEnabled方法,在LayoutManager的子类中都有覆写,返回值都为true。

我们看到第11行的代码mLayout.onMeasure,也就是说RecyclerView的测量过程,委托给了LayoutManager方法。

第13行代码中判断了RecyclerView的宽高模式,如果给RecyclerView指定了高度或者充满了父布局,那么onMeasure方法到这里就结束了,后续子条目的摆放不在执行,而是在onLayout方法中进行,这是为什么呢?其实我们可以思考一下,如果RecyclerView的宽或高是wrap_content,那么RecyclerView的宽高值会随子条目的摆放而变化;如果RecyclerView的高度确定了,那么直接摆放子条目即可。无论是在onMeasure中,还是onLayout方法中,子条目摆放的方法没有区别,分别是dispatchLayoutStep1和dispatchLayoutStep2。dispatchLayoutStep1方法中做了和动画相关及一些字段的赋值,和真正的layout关系不大,核心操作在dispatchLayoutStep2方法中,我们进入查看:

    private void dispatchLayoutStep2() {
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(RecyclerView.State.STEP_LAYOUT | RecyclerView.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 = RecyclerView.State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }

第11行代码mLayout.onLayoutChildren是子条目的摆放过程,我们进入查看

        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
        }

这个方法是空,可以推测出,这个方法一定在LayoutManager的子类中有实现,我们以LinearLayoutManager为例,进入查看

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        ...
        if (mAnchorInfo.mLayoutFromEnd) {
            ...
        } else {
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                extraForEnd = mLayoutState.mAvailable;
                // start could not consume all it should. add more items towards end
                updateLayoutStateToFillEnd(lastElement, endOffset);
                mLayoutState.mExtra = extraForEnd;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
            }
        }
}

这个方法的代码非常多,也做了很多的工作,主要做了两个工作:确定锚点和绘制。什么是锚点呢?也就是说RecyclerView开始绘制的点。确定锚点后,再根据绘制方向进行绘制。可以根据下图理解:

红色点为锚点,第一个图和第二个图,都是从锚点开始向箭头方向绘制;第三个图片,锚点在中间位置,或先向上再向下,或反之,具体怎么方向由代码确定。

看第4行的mAnchorInfo.mLayoutFromEnd,这个值表示绘制过程的方向。我认为我们没有必要去关心绘制的方向,这个对我们来说不重要。假设我们锚点在屏幕顶端(第二个图),第10行和第20行的fill只会执行一个,如果锚点在中间的话,这两个方法都会执行,这应该是很好理解,我们进入fill方法

    int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
             RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LinearLayoutManager.LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            /**
             * Consume the available space if:
             * * layoutChunk did not request to be ignored
             * * OR we are laying out scrap children
             * * OR we are not doing pre-layout
             */
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }

第12行代码中的remainingSpace,从命名上我们基本上知道了具体意义,剩余的空间。也就是RecyclerView在平铺子条目时,剩余的空间,如果没有放子条目,那么剩余空间等于RecyclerView的高度(假设是LinearLayoutManager,纵向滑动),每摆放一个子条目,剩余空间则少一个子条目的高度。14行是一个while循环,所以这里会不断的摆放子条目,直到RecyclerView被完全冲慢,或者条目全都摆放完毕。每次摆放是,调用的方法是layoutChunk,我们看一看:

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.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;
        }
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LinearLayoutManager.LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LinearLayoutManager.LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

第4行的代码很重要,获取缓存的核心代码,但是我们暂时先跳过,因为此时缓存中还没有任何数据,在滑动分析的时候,我们再回到这里。第18行和第20行代码,相信大家都明白它们的含义,就是把子条目放到RecyclerView中。绘制流程,到此完毕。onLayoutChildren方法中,fill执行了3次,我认为这里无伤大雅,只不过是准确性的判断。

三、滑动

RecyclerView的滑动,肯定会执行onTouchEvent方法,看一下这个方法

@Override
    public boolean onTouchEvent(MotionEvent e) {
        .....
        switch (action) {
            .....
            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;
            .....
        }

        .....

        return true;
    }

滑动时,事件类型为MotionEvent.ACTION_MOVE,所以我们只看这部分代码。第11行代码是关键的入口,我们进入方法内部:

    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            scrollStep(x, y, mScrollStepConsumed);
            consumedX = mScrollStepConsumed[0];
            consumedY = mScrollStepConsumed[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

        if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                TYPE_TOUCH)) {
            // Update the last touch co-ords, taking any scroll offset into account
            mLastTouchX -= mScrollOffset[0];
            mLastTouchY -= mScrollOffset[1];
            if (ev != null) {
                ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
            }
            mNestedOffsets[0] += mScrollOffset[0];
            mNestedOffsets[1] += mScrollOffset[1];
        } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
            if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
                pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
            }
            considerReleasingGlowsOnScroll(x, y);
        }
        if (consumedX != 0 || consumedY != 0) {
            dispatchOnScrolled(consumedX, consumedY);
        }
        if (!awakenScrollBars()) {
            invalidate();
        }
        return consumedX != 0 || consumedY != 0;
    }

进入到第7行scrollStep方法中

    void scrollStep(int dx, int dy, @Nullable int[] consumed) {

        startInterceptRequestLayout();
        onEnterLayoutOrScroll();

        TraceCompat.beginSection(TRACE_SCROLL_TAG);
        fillRemainingScrollValues(mState);

        int consumedX = 0;
        int consumedY = 0;
        if (dx != 0) {
            consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
        }
        if (dy != 0) {
            consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
        }

        TraceCompat.endSection();
        repositionShadowingViews();

        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);

        if (consumed != null) {
            consumed[0] = consumedX;
            consumed[1] = consumedY;
        }
    }

再强调一下,mLayout我们假设为LinearLayoutManager,所以我们看15行代码mLayout.scrollVerticallyBy方法

        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
            return 0;
        }

这个方法是空的,所以应该是在LinearLayoutManager中覆写了,查看LinearLayoutManager中的此方法

    @Override
    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 ? LinearLayoutManager.LayoutState.LAYOUT_END : LinearLayoutManager.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;
    }

在第12行中,我们又看见了fill方法,在绘制的时候我们说过这个方法,根据剩余的距离来摆放子条目。从这里进入的fill是一个方法,所以作用也是如此。我们想象一下,RecyclerView中最后一个条目一定是展示部分或正好展示全部,当我们向上慢慢滑动时,最后一个条目的底部会逐渐的进入到屏幕内,当底部进入到屏幕中时,fill方法中的remainingSpace的值会大于0,因此进入到while循环中,进入到layoutChunk方法,我们再看一下layoutChunk方法

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result) {

        View view = layoutState.next(recycler);
        ......
    }

layoutState.next方法我们简单说过,是获取缓存的ViewHolder,这次我们进入看看

        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }

进入第5行的getViewForPosition方法中

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

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

getViewForPosition有两个重载方法,最后会执行到第7行的tryGetViewHolderForPositionByDeadline方法,我们进入看一下

        @Nullable
        RecyclerView.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()
                        + exceptionLabel());
            }
            boolean fromScrapOrHiddenOrCache = false;
            RecyclerView.ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(RecyclerView.ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            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() + exceptionLabel());
                }

                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);
                    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) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view." + exceptionLabel());
                        }
                    }
                }
                if (holder == null) { // fallback to pool
                    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;
                    }
                    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");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(RecyclerView.ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, RecyclerView.ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = RecyclerView.ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= RecyclerView.ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemAnimator.ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            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
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final RecyclerView.LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (RecyclerView.LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (RecyclerView.LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (RecyclerView.LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

这个方法我没有做任何的删减,因为都比较重要,执行的操作基本上和我所画的缓存流程图一样。我们继续说滑动时的流程,进入到这个方法后,会执行第18行代码,我们进入看看

        RecyclerView.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++) {
                final RecyclerView.ViewHolder holder = mAttachedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                        && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                    holder.addFlags(RecyclerView.ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    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.
                    final RecyclerView.ViewHolder vh = getChildViewHolderInt(view);
                    mChildHelper.unhide(view);
                    int layoutIndex = mChildHelper.indexOfChild(view);
                    if (layoutIndex == RecyclerView.NO_POSITION) {
                        throw new IllegalStateException("layout index should not be -1 after "
                                + "unhiding a view:" + vh + exceptionLabel());
                    }
                    mChildHelper.detachViewFromParent(layoutIndex);
                    scrapView(view);
                    vh.addFlags(RecyclerView.ViewHolder.FLAG_RETURNED_FROM_SCRAP
                            | RecyclerView.ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    return vh;
                }
            }

            // Search in our first-level recycled view cache.
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final RecyclerView.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) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    if (DEBUG) {
                        Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                                + ") found match in cache: " + holder);
                    }
                    return holder;
                }
            }
            return null;
        }

我们在这里看到了两个眼熟的变量,mAttachedScrap和mCachedViews。mAttachedScrap此时是空的,所以略过。而mCachedViews是有值的,所以获取到了ViewHolder。等等等等,mCachedViews怎么突然就有值了,之前也没有说过什么时候向其中存过值啊,什么时候的事啊?!是的,因为还没有说到,关于向mCachedViews存值的过程,我们还要回到onToucheEvent方法中

@Override
    public boolean onTouchEvent(MotionEvent e) {
        .....
        switch (action) {
            .....
            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;
            .....
        }

        .....

        return true;
    }

我们看到第18行代码,进入到mGapWorker的postFromTraversal中查看

    void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {

        if (recyclerView.isAttachedToWindow()) {
            if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
                throw new IllegalStateException("attempting to post unregistered view!");
            }
            if (mPostTimeNs == 0) {
                mPostTimeNs = recyclerView.getNanoTime();
                recyclerView.post(this);
            }
        }

        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
    }

第9行代码 recycler.post(this),那么mGapWorker类中一定有一个run方法,我们看一下

@Override
    public void run() {
        try {
            TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);

            if (mRecyclerViews.isEmpty()) {
                // abort - no work to do
                return;
            }

            // Query most recent vsync so we can predict next one. Note that drawing time not yet
            // valid in animation/input callbacks, so query it here to be safe.
            final int size = mRecyclerViews.size();
            long latestFrameVsyncMs = 0;
            for (int i = 0; i < size; i++) {
                RecyclerView view = mRecyclerViews.get(i);
                if (view.getWindowVisibility() == View.VISIBLE) {
                    latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
                }
            }

            if (latestFrameVsyncMs == 0) {
                // abort - either no views visible, or couldn't get last vsync for estimating next
                return;
            }

            long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;

            prefetch(nextFrameNs);

            // TODO: consider rescheduling self, if there's more work to do
        } finally {
            mPostTimeNs = 0;
            TraceCompat.endSection();
        }
    }

第29行代码,我们进入perfetch中

    void prefetch(long deadlineNs) {
        buildTaskList();
        flushTasksWithDeadline(deadlineNs);
    }

里面有两个方法,都很重要,我们一个一个看,先看buildTaskList方法

    private void buildTaskList() {
        .....
        for (int i = 0; i < viewCount; i++) {
            ......
            for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
                final GapWorker.Task task;
                if (totalTaskIndex >= mTasks.size()) {
                    task = new GapWorker.Task();
                    mTasks.add(task);
                } else {
                    task = mTasks.get(totalTaskIndex);
                }
                final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
                task.immediate = distanceToItem <= viewVelocity;
                task.viewVelocity = viewVelocity;
                task.distanceToItem = distanceToItem;
                task.view = view;
                task.position = prefetchRegistry.mPrefetchArray[j];

                totalTaskIndex++;
            }
        }
        .....
    }

篇幅原因,我贴部分代码,其中第14行代码是关键,distanceToItem是屏幕中最后一个条目的底部距离屏幕底部的距离,值为正数;viewVelocity是偏移量x的绝对值加上偏移量y的绝对值。简单来说,滑动的距离大于distanceToItem时,表示新的item要展示了,所以要创建新的,此时task.immediate为true。我们再看另一个方法flushTasksWithDeadline方法

    private void flushTasksWithDeadline(long deadlineNs) {
        for (int i = 0; i < mTasks.size(); i++) {
            final GapWorker.Task task = mTasks.get(i);
            if (task.view == null) {
                break; // done with populated tasks
            }
            flushTaskWithDeadline(task, deadlineNs);
            task.clear();
        }
    }

再进入flushTaskWithDealine方法中

    private void flushTaskWithDeadline(GapWorker.Task task, long deadlineNs) {
        long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
                task.position, taskDeadlineNs);
        if (holder != null
                && holder.mNestedRecyclerView != null
                && holder.isBound()
                && !holder.isInvalid()) {
            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
        }
    }

第2行用到了task.immediate这个值,如果这个值为true的话,taskDeadlinedNs为RecyclerView.FOREVER_NS,然后传入到了第3行代码的方法里,第3行代码返回了一个ViewHolder,我们进入查看

    private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view, int position, long deadlineNs) {
        if (isPrefetchPositionAttached(view, position)) {
            // don't attempt to prefetch attached views
            return null;
        }

        RecyclerView.Recycler recycler = view.mRecycler;
        RecyclerView.ViewHolder holder;
        try {
            view.onEnterLayoutOrScroll();
            holder = recycler.tryGetViewHolderForPositionByDeadline(
                    position, false, deadlineNs);

            if (holder != null) {
                if (holder.isBound() && !holder.isInvalid()) {
                    // Only give the view a chance to go into the cache if binding succeeded
                    // Note that we must use public method, since item may need cleanup
                    recycler.recycleView(holder.itemView);
                } else {
                    // Didn't bind, so we can't cache the view, but it will stay in the pool until
                    // next prefetch/traversal. If a View fails to bind, it means we didn't have
                    // enough time prior to the deadline (and won't for other instances of this
                    // type, during this GapWorker prefetch pass).
                    recycler.addViewHolderToRecycledViewPool(holder, false);
                }
            }
        } finally {
            view.onExitLayoutOrScroll(false);
        }
        return holder;
    }

我们在第11行看见到了熟悉的方法tryGetViewHolderForPositionByDeadline,这个方法不就是RecyclerView从缓存中获取新条目的方法嘛,从这里进入的话,会如何执行呢?我们再进入看看

@Nullable
        RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                                                                   boolean dryRun, long deadlineNs) {
           .....
            if (holder == null) {
                .....
                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;
                    }
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    ....
                }
            }

            .....
            return holder;
        }

代码太多了,我进行了阉割。这个方法的代码就是从缓存中获取ViewHolder,现在缓存中都是空的。我们看第9行代码,deadlineNs这个变量,刚刚不就是被赋值为FOREVER_NS吗,所以执行了第14行的代码mAdapter.createViewHolder方法,这段代码很眼熟吧,就是我们自定义Adapter中覆写的方法,这时候ViewHolder就被创建出来了,那是什么时候被放入到缓存的呢?我们需要回到上一个方法prefetchPositionWithDeadline中

    private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view, int position, long deadlineNs) {
        if (isPrefetchPositionAttached(view, position)) {
            // don't attempt to prefetch attached views
            return null;
        }

        RecyclerView.Recycler recycler = view.mRecycler;
        RecyclerView.ViewHolder holder;
        try {
            view.onEnterLayoutOrScroll();
            holder = recycler.tryGetViewHolderForPositionByDeadline(
                    position, false, deadlineNs);

            if (holder != null) {
                if (holder.isBound() && !holder.isInvalid()) {
                    // Only give the view a chance to go into the cache if binding succeeded
                    // Note that we must use public method, since item may need cleanup
                    recycler.recycleView(holder.itemView);
                } else {
                    // Didn't bind, so we can't cache the view, but it will stay in the pool until
                    // next prefetch/traversal. If a View fails to bind, it means we didn't have
                    // enough time prior to the deadline (and won't for other instances of this
                    // type, during this GapWorker prefetch pass).
                    recycler.addViewHolderToRecycledViewPool(holder, false);
                }
            }
        } finally {
            view.onExitLayoutOrScroll(false);
        }
        return holder;
    }

我们看第18行和第24行,第18行是把ViewHolder加入到了mCacheViews,而24行是把ViewHolder加入到RecycledViewPool中,当然这里是加入到mCacheViews中。这样从onTouchEvent的scrollByInternal进入获取的新ViewHolder不就获取到了吗。稍等稍等!!有同学可能发现了,在onToucheEvent中scrollByInternal是先执行的,而mGapWorker.postFromTraversal是后执行的,后执行的代码创建,先执行的获取?这不是错了吗?而且mGapWorker还是子线程,执行结果的时间都不确定,更不对了啊。从scrollByInternal进入,有一个fill方法,大家还记得吗,fill方法中有一个remainingSpace变量,在滑动前,如果最后一个条目没有完全展示,那么这个值就是负数,向上滑动时,这个值逐渐向0靠近;执行mGapWorker.postFromTraversal方法时,有一个buildTaskList方法,其中有一个变量为distanceToItem,这个变量和remainingSpace含义相似,但是这个值确永远大于0,相当于是remainingSpace的绝对值。我举一个例子:比如滑动RecyclerView是,最后一个条目即将完全露出,此时remainingSpace等于-5,而remainingSpace则等于5,假设滑动的偏移量为10,那么创建ViewHolder的流程,肯定要比从缓存获取的流程前置。滑动流程,至此结束了

四、RecyclerView和ListView的缓存比较

(1)RecyclerView是四级缓存,而ListView是两级缓存,缓存策略上RecyclerView更有优势

(2)RecyclerView中RecycledViewPool可以供多个RecyclerView公用,这是ListView所没有的

(3)从RecyclerView的mCachedViews获取的View不需要重新bindView,因为缓存存储的是ViewHolder;而ListView中的缓存存储的是View,需要重新绑定;如果是从RecyclerView的RecycledViewPool中获取的话,也需要重新bindView一次

第3点我当时有些不太明白,我记得我是用ListView的时候没有重新绑定啊,如下

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if(convertView==null){
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_layout,parent,false);
            viewHolder = new ViewHolder();
            viewHolder.titleView = view.findViewById(R.id.title_view);
            view.setTag(viewHolder);
        }else{
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.titleView.setText(dataList.get(position));
        return view;
    }

其实,这应该是算是一种优化的写法(我一直都不知道。。。),而非优化的写法如下

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	Fruit fruit = getItem(position);
	View view;
	if (convertView == null) {
		view = LayoutInflater.from(getContext()).inflate(resourceId, null);
	} else {
		view = convertView;
	}
	ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
	TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
	fruitImage.setImageResource(fruit.getImageId());
	fruitName.setText(fruit.getName());
	return view;
}

五、注意

(1)在不同的SDK版本中,onMeasure,onLayout,onDraw执行的次数不一样,我使用的是SDK29,onLayout只执行了一次

(2)RecyclerView源码调试方法有很多种,我使用了最笨的一种,把RecyclerView的源码复制了一份,这样更方便打log,大概有20个左右文件吧,还能接受

(3)如果直接在RecyclerView中打断点的话,一定要注意:最好使用模拟器,因为不同品牌的手机源码可能有变动;SDK版本最好和模拟器的版本相同

有问题的同学,欢迎在留言区讨论

参考文章:

《Android ListView工作原理完全解析,带你从源码的角度彻底理解》

《【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制》

《【进阶】RecyclerView源码解析(一)——绘制流程》

《【进阶】RecyclerView源码解析(二)——缓存机制》

《RecyclerView与ListView 对比浅析:缓存机制》

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Android 源码中,适配器模式被广泛应用于 UI 层,特别是 ListView 和 RecyclerView 这样的列表控件中。 举例来说,ListView 和 RecyclerView 的数据源都是一个 Adapter 对象,该对象负责将数据绑定到列表中的视图上。Adapter 接口定义了一些方法,例如 getCount()、getItem() 和 getView() 等,用于获取数据总数、单个数据项和显示数据项对应的 View 视图。 具体来说,ListView 和 RecyclerView 会调用 Adapter 的 getCount() 方法获取数据总数,在绘制列表时会调用 getView() 方法获取每个数据项对应的 View 视图,并将数据绑定到视图上。如果数据源发生变化(例如添加或删除数据项),则需要调用 Adapter 的 notifyDataSetChanged() 方法通知列表控件重新绘制。 另外,RecyclerView 还引入了更高级的适配器模式,即 ViewHolder 模式。ViewHolder 模式可以减少 findViewById() 方法的调用次数,从而提高列表绘制的性能。在 ViewHolder 模式中,Adapter 会持有一个 ViewHolder 对象,该对象用于存储数据项的视图和相关信息。RecyclerView 在绘制列表时,会先检查 ViewHolder 是否已创建,如果已创建则直接使用该 ViewHolder 对象,否则会创建一个新的 ViewHolder 对象并绑定到数据项上。 因此,适配器模式在 Android 源码中起到了非常重要的作用,它使得列表控件的数据源和视图分离,提高了代码的可维护性和可重用性。同时,ViewHolder 模式更是提高了列表绘制的性能,优化了用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值