RecyclerView 源码分析

作者: MeloDev | 来源:发表于2017-08-01 21:08 被阅读1939次

    RecyclerView 源码分析

    本文原创,转载请注明出处。
    欢迎关注我的 简书 ,关注我的专题 Android Class 我会长期坚持为大家收录简书上高质量的 Android 相关博文。

    写在前面:
    RecyclerView 是一个越用越优雅的控件,相信大家对于 RecyclerView 的使用也已经比较熟悉了。其功能的高度解耦化,规范 ViewHolder 的写法,以及对动画友好支持,都是它与传统控件 ListView 的区别。而无论 ListView 还是 RecyclerView,本质上都是在有限的屏幕之上,展示大量的内容。所以复用的逻辑,就成了它们最最重要的核心原理,本文主要目的就是探究 RecyclerView 的复用原理。

    RecyclerView 的几大模块

    • LayoutManager
      负责 RecyclerView 中,控制 item 的布局方向

    • RecyclerView.Adapter
      为 RecyclerView 承载数据

    • ItemDecoration
      为 RecyclerView 添加分割线

    • ItemAnimator
      控制 RecyclerView 中 item 的动画

    刚刚我们提到 RecyclerView 的高度解耦,就是通过以上对象各司其职,来实现 RecyclerView 的基本功能。RecyclerView 无论多么复杂,本质上也是一个自定义 View,本文的重点就是缓存原理分析,不过在此之前,我们还是简单地分别介绍下以上中各个模块的源码。

    ItemDecoration

        public void addItemDecoration(ItemDecoration decor, int index) {
            if (mLayout != null) {
                mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                        + " layout");
            }
            if (mItemDecorations.isEmpty()) {
                setWillNotDraw(false);
            }
            if (index < 0) {
                mItemDecorations.add(decor);
            } else {
                // 指定添加分割线在集合中的索引
                mItemDecorations.add(index, decor);
            }
            markItemDecorInsetsDirty();
            // 重新请求 View 的测量、布局、绘制
            requestLayout();
        }
    

    mItemDecorations 是一个 ArrayList,我们将 ItemDecoration 也就是分割线对象,添加到其中。接着我们看下 markItemDecorInsetsDirty 这个方法做了些什么。

        void markItemDecorInsetsDirty() {
            final int childCount = mChildHelper.getUnfilteredChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = mChildHelper.getUnfilteredChildAt(i);
                ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
            }
            mRecycler.markItemDecorInsetsDirty();
        }
    

    这个方法首先遍历了 RecyclerView 和 LayoutManager 的所有子 View,将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true。接着调用了 mRecycler.markItemDecorInsetsDirty(),Recycler 是 RecyclerView 的一个内部类,就是它管理着 RecyclerView 的复用逻辑。这个我们一会再细谈。

            void markItemDecorInsetsDirty() {
                final int cachedCount = mCachedViews.size();
                for (int i = 0; i < cachedCount; i++) {
                    final ViewHolder holder = mCachedViews.get(i);
                    LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
                    if (layoutParams != null) {
                        layoutParams.mInsetsDirty = true;
                    }
                }
            }
    

    mCachedViews 见名知意,也就是 RecyclerView 缓存的集合,相信你也看到了,RecyclerView 的缓存单位是 ViewHolder。我们在 ViewHolder 中取出 itemView,然后获得 LayoutParams,将其 mInsetsDirty 字段一样置为 true。

    mInsetsDirty 字段的作用其实是一种优化性能的缓存策略,添加分割线对象时,无论是 RecyclerView 的子 view,还是缓存的 view,都将其置为 true,接着就调用了 requestLayout 方法。

    这里简单说一下 requestLayout 方法用一种责任链的方式,层层向上传递,最后传递到 ViewRootImpl,然后重新调用 view 的 measure、layout、draw 方法来展示布局。

    我们在 RecyclerView 中搜索 mItemDecorations 集合,看看他是在什么时刻操作 ItemDecoration 这个分割线对象的。

    onDraw 中:

        @Override
        public void onDraw(Canvas c) {
            super.onDraw(c);
    
            final int count = mItemDecorations.size();
            for (int i = 0; i < count; i++) {
                mItemDecorations.get(i).onDraw(c, this, mState);
            }
        }
    

    draw 方法中:

        @Override
        public void draw(Canvas c) {
            super.draw(c);
    
            final int count = mItemDecorations.size();
            for (int i = 0; i < count; i++) {
                mItemDecorations.get(i).onDrawOver(c, this, mState);
            }
        }
    

    可以看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。

    这两个抽象方法,由我们继承 ItemDecoration 来自己实现,他们区别就是 onDraw 在 item view 绘制之前调用,onDrawOver 在 item view 绘制之后调用。

    所以绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。

    (好像越写越多...收不住了...)

    我们在 onDrawonDrawOver 方法中就可以绘制 drawable 对象了。此时分割线就展现出来了。还记得刚才的 mInsetsDirty 字段吗?在添加分割线的时候,无论是 RecyclerView 子 View,还是缓存中的 View,其 LayoutParams 中的 mInsetsDirty 属性,都被置为 true。 我们来解释一下这个字段的作用:

        Rect getItemDecorInsetsForChild(View child) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (!lp.mInsetsDirty) {
                // 当 mInsetsDirty 为 false,说明 mDecorInsets 缓存可用
                return lp.mDecorInsets;
            }
    
            if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
                // changed/invalid items should not be updated until they are rebound.
                return lp.mDecorInsets;
            }
            final Rect insets = lp.mDecorInsets;
            insets.set(0, 0, 0, 0);
            final int decorCount = mItemDecorations.size();
            for (int i = 0; i < decorCount; i++) {
                mTempRect.set(0, 0, 0, 0);
                mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
                insets.left += mTempRect.left;
                insets.top += mTempRect.top;
                insets.right += mTempRect.right;
                insets.bottom += mTempRect.bottom;
            }
            lp.mInsetsDirty = false;
            return insets;
        }
    

    来解释一下这段代码,首先 getItemDecorInsetsForChild 方法是在 RecyclerView 进行 measureChild 时调用的。目的就是为了取出 RecyclerView 的 ChildView 中的分割线属性 --- 在 LayoutParams 中缓存的 mDecorInsets 。而 mDecorInsets 就是 Rect 对象, 其保存记录的是所有添加分割线需要的空间累加的总和,由分割线的 getItemOffsets 方法影响。

    最后在 measureChild 方法里,将分割线 ItemDecoration 的尺寸加入到 itemView 的 padding 中。

    但是大家都知道缓存并不是总是可用的,mInsetsDirty 这个 boolean 字段来记录它的时效性,当 mInsetsDirty 为 false 时,说明缓存可用,直接取出可以,当 mInsetsDirty 为 true 时,说明缓存的分割线属性就需要重新计算了。

    到此,关于 RecyclerView 添加分割线 ItemDecoration 的源码分析,也就基本结束了。

    ItemAnimator

    如果有人问我,在什么情况下你绝对会选择 RecyclerView,而不是 ListView?如果需求对 Item 的动画有一定要求,这绝对是我选择 RecyclerView 的重要原因之一。ListView 如果要做 Item 的增删动画,那可要费很大劲儿,而 RecyclerView 自身对动画就有很好的支持。

        public void setItemAnimator(ItemAnimator animator) {
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
                mItemAnimator.setListener(null);
            }
            mItemAnimator = animator;
            if (mItemAnimator != null) {
                mItemAnimator.setListener(mItemAnimatorListener);
            }
        }
    

    这个方法就是为 RecyclerView 设置动画的入口,逻辑就是清除旧的 Listener,设置新的 Listener。这个没什么可多说的,我们这次直接来看看 ItemAnimator 这个类。

    当我第一次粗略的看 RecyclerView 的源码之时,感觉这也太多了吧,一万两千多行,这得多复杂啊。后来再看看,才知道,RecyclerView 是把

    ItemAnimator 、LayoutManager 等等这些模块都当内部类写在了一块。。。我不知道这样设计的目的是啥,除了写着方便之外可能找不到其他理由了,so... 可能是 google 的程序员偷个懒吧 - -

    来看 ItemAnimator 类:

            public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
                    @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
                    @NonNull List<Object> payloads) {
                return obtainHolderInfo().setFrom(viewHolder);
            }
            public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
                    @NonNull ViewHolder viewHolder) {
                return obtainHolderInfo().setFrom(viewHolder);
            }
    

    这两个函数看命名也能猜一个大概,其目的就是为了记录在 RecyclerView 布局之前/之后,必要的一些 layout 信息保存在 ItemHolderInfo 中,ItemHolderInfo 这个类就是用来记录当前 ItemView 的位置信息

    recordPreLayoutInformation 来记录 layout 之前的状态信息,这个方法在 dispatchLayoutStep1 之中调用。

    dispatchLayoutStep1这个方法做了什么呢,来看看注释就一目了然:

        /**
         * The first step of a layout where we;
         * - process adapter updates
         * - decide which animation should run
         * - save information about current views
         * - If necessary, run predictive layout and save its information
         */
    

    这是布局的第一步:
    进行 adapter 布局的更新,决定执行哪个动画,保存当前 view 的信息,如果有必要,运行 predictive layout。

    相同的 recordPostLayoutInformation 方法来记录 layout 过程完成时,ItemView 的信息。

    它在 dispatchLayoutStep3 方法中调用,dispatchLayoutStep3 方法作用:

        /**
         * The final step of the layout where we save the information about views for animations,
         * trigger animations and do any necessary cleanup.
         */
    

    layout 的最后一个步骤,保存 view 动画的信息,执行动画,状态清理。当然也有 dispatchLayoutStep2 方法,他们三个方法依次在 onLayout 方法的 dispatchLayout方法调用。

    dispatchLayout 方法目的是 layout RecyclerView 的 childview,并且记录动画执行的过程、变更。

    现在知道了这两个 API 的作用就是在 layout 前后记录 itemview 动画的状态,保存在 ItemHolderInfo 中,我们继续寻找执行动画的 API。

    • animateDisappearance
      当 ViewHolder 从 RecyclerView 的 layout 中移除时,调用
    • animateAppearance
      当 ViewHolder 添加进 RecyclerView 时,调用
    • animatePersistence
      当 ViewHolder 已经添加进 layout 还未移除时,调用
    • animateChange
      当 ViewHolder 已经添加进 layout 还未移除,并且调用了 notifyDataSetChanged 时,调用。

    以上四个 api 就是为 RecyclerView 执行动画的。

    调用的时机和方式是如何的呢?

        /**
         * The callback to convert view info diffs into animations.
         */
        private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
                new ViewInfoStore.ProcessCallback() {
                    @Override
                    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                            @Nullable ItemHolderInfo postInfo) {
                        mRecycler.unscrapView(viewHolder);
                        animateDisappearance(viewHolder, info, postInfo);
                    }
                    @Override
                    public void processAppeared(ViewHolder viewHolder,
                            ItemHolderInfo preInfo, ItemHolderInfo info) {
                        animateAppearance(viewHolder, preInfo, info);
                    }
    
                    @Override
                    public void processPersistent(ViewHolder viewHolder,
                            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
                        viewHolder.setIsRecyclable(false);
                        if (mDataSetHasChangedAfterLayout) {
                            // since it was rebound, use change instead as we'll be mapping them from
                            // stable ids. If stable ids were false, we would not be running any
                            // animations
                            if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                                    postInfo)) {
                                postAnimationRunner();
                            }
                        } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
                            postAnimationRunner();
                        }
                    }
                    @Override
                    public void unused(ViewHolder viewHolder) {
                        mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
                    }
                };
    

    mViewInfoProcessCallback 是一个匿名内部类,在其回调方法中,分别执行了以上四个关于动画的 api。

    继续跟进,看看mViewInfoProcessCallback 这个接口是什么时候被调用执行的:

     // Step 4: Process view info lists and trigger animations
                mViewInfoStore.process(mViewInfoProcessCallback);
    

    执行触发方法的位置就在我们刚才提到的 dispatchLayoutStep3方法中,去根据一些保存的 flag 状态去触发动画。

    LayoutManager

    与其他绑定 adapter 展示数据的控件,比如 ListView、GrideView 相比,RecyclerView 允许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 如果想展示内容,就必须设置一个 LayoutManager。

    我们按照惯例来看设置 LayoutManager 的入口:

        public void setLayoutManager(LayoutManager layout) {
            if (layout == mLayout) {
                return;
            }
            // 停止滑动
            stopScroll();
            // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
            // chance that LayoutManagers will re-use views.
            if (mLayout != null) {
                // end all running animations
                if (mItemAnimator != null) {
                    mItemAnimator.endAnimations();
                }
                // 移除并回收视图
                mLayout.removeAndRecycleAllViews(mRecycler);
                // 回收废弃视图
                mLayout.removeAndRecycleScrapInt(mRecycler);
                mRecycler.clear();
    
                if (mIsAttached) {
                    mLayout.dispatchDetachedFromWindow(this, mRecycler);
                }
                mLayout.setRecyclerView(null);
                mLayout = null;
            } else {
                mRecycler.clear();
            }
            // this is just a defensive measure for faulty item animators.
            mChildHelper.removeAllViewsUnfiltered();
            mLayout = layout;
            if (layout != null) {
                if (layout.mRecyclerView != null) {
                    throw new IllegalArgumentException("LayoutManager " + layout +
                            " is already attached to a RecyclerView: " + layout.mRecyclerView);
                }
                mLayout.setRecyclerView(this);
                if (mIsAttached) {
                    mLayout.dispatchAttachedToWindow(this);
                }
            }
            mRecycler.updateViewCacheSize();
            requestLayout();
        }
    

    这段代码主要做了一下几件事:
    当之前设置过 LayoutManager 时,移除之前的视图,并缓存视图在 Recycler 中,将新的 mLayout 对象与 RecyclerView 绑定,更新缓存 View 的数量。最后去调用 requestLayout ,重新请求 measure、layout、draw。

    关于 RecyclerView 的缓存我们一会再研究,先看看设置的 LayoutManager 是在何时何处发挥作用的吧:

    LayoutManager 作为 RecyclerView 的一个抽象内部类,大概有3000行代码,方法数还很多,看着头都大了。用有限的时间去了解每一个方法的作用显然不现实。LayoutManager 的作用就是为 RecyclerView 放置子 view,所以我直接去定位 RecyclerView 的 onLayout 和 onMeasure 方法,研究一下 LayoutManager 的一些关键函数的作用。

        @Override
        protected void onMeasure(int widthSpec, int heightSpec) {
            if (mLayout == null) {
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
            if (mLayout.mAutoMeasure) {
                final int widthMode = MeasureSpec.getMode(widthSpec);
                final int heightMode = MeasureSpec.getMode(heightSpec);
                final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                        && heightMode == MeasureSpec.EXACTLY;
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                if (skipMeasure || mAdapter == null) {
                    return;
                }
                if (mState.mLayoutStep == State.STEP_START) {
                    dispatchLayoutStep1();
                }
                // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
                // consistency
                mLayout.setMeasureSpecs(widthSpec, heightSpec);
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
    
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    
                // if RecyclerView has non-exact width and height and if there is at least one child
                // which also has non-exact width & height, we have to re-measure.
                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 {
                // 省略非自动测量的情况
            }
        }
    

    来分析一下 onMeasure 方法,mAutoMeasure 字段用来标记是否使用 RecyclerView 的默认规则进行自动测量,否则就必须在 LayoutManager 中自己实现 onMeasure 来进行测量。LinearLayoutManager 的 mAutoMeasure 字段属性就被设置成为了 true。

    所以我们重点来看mAutoMeasure为 true 时,测量的规则。

    当 RecyclerView 的 MeasureSpecMeasureSpec.EXACTLY时,这个时候可以直接确定 RecyclerView 的宽高,所以 return 退出测量。当 RecyclerView 的宽高为不为 EXACTLY 时,首先进行的测量步骤就是 dispatchLayoutStep1,这个我们在分析动画源码的时候提到过。dispatchLayoutStep1 的作用总结起来就是记录 layout 之前,view 的信息。

    接着继续调用了 dispatchLayoutStep2方法:

        /**
         * The second layout step where we do the actual layout of the views for the final state.
         * This step might be run multiple times if necessary (e.g. measure).
         */
        private void dispatchLayoutStep2() {
            ...
            // Step 2: Run layout
            ...
            mLayout.onLayoutChildren(mRecycler, mState);
            ...
            mState.mLayoutStep = State.STEP_ANIMATIONS;
        }
    

    dispatchLayoutStep2 比较关键的就是以上代码展示的这两步,onLayoutChildren 这个函数由 LayoutManager 实现,来规定放置子 view 的算法,寻找锚点填充 view,锚点的寻找和填充 view 的方式,这里就不细说了。因为篇幅实在是太长。具体可以直接去看 LinearLayoutManager 的实现方式。

    第二步就是将 mState.mLayoutStep 置为 State.STEP_ANIMATIONS,刚才我们忘记说 mLayoutStep 这个属性了,从它的命名就知道它是来标记 layout 这个过程进行到哪一步了。在 dispatchLayoutStep1 中 mState.mLayoutStep 被置为 State.STEP_LAYOUT,记录 layout 的步骤是什么原因呢?来看看 RecyclerView 的 onLayout 方法:

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
            dispatchLayout();
            TraceCompat.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();
        }
    

    看到 mState.mLayoutStep 的作用了吧,当我们在 onMeasure 方法中已经调用过 dispatchLayoutStep1dispatchLayoutStep2 时,在 onLayout 方法中只会调用 dispatchLayoutStep3dispatchLayoutStep3方法我们在动画部分的源码讲解过。

    回想一下,什么情况下会在 onMeasure 方法中直接调用 dispatchLayoutStep1dispatchLayoutStep2 ? 就是 RecyclerView 的 MeasureSpec 不为 EXACTLY 时,这个情况下 RecyclerView 不能自己确定自身的宽高,只能在测量、布局了子 view 才能确定自己的宽高。所以在 onMeasure 的时候就调用了 dispatchLayoutStep1dispatchLayoutStep2 ,在 onLayout 仅仅调用 dispatchLayoutStep3 方法就可以了。

    如果文字表述的不够清晰,这里来一张图:

    onMeasure/onLayout

    最后在这个章节中,总结一下 LayoutManager 的作用:

    • 协助 RecyclerView 完成 onMeasure 过程
    • 通过 onLayoutChildren 完成对子 view 的布局
    • 滚动子视图
    • 滚动过程中判断何时添加 view ,何时回收 view,也就是对缓存时机的判断。

    Recycler

    这篇文章我在工作之余写写停停大概花了一周,终于到了本文的最后一个模块 -- recycler
    在上文中反复提到,Recycler 就是控制 RecyclerView 缓存的核心类。理解了它的工作过程,就可以弄明白 RecyclerView 如何回收 view,如何复用 view 的。

    在上一个章节,我们说过了 LayoutManager 其实就是 Recycler 的控制者,由 LayoutManager 来决定调用 Recycler 关键方法的时机,口说无凭,就直接来看看 LinearLayoutManager 的代码吧:

    首先来看最关键的部分 onLayoutChildren 方法,这个方法是在 RecyclerView 的 dispatchLayoutStep2 阶段调用的。

    onLayoutChildren 方法比较长,它核心的作用就是寻找一个锚点,为 RecyclerView 填充子 View。在 onLayoutChildren 调用的 fill 方法就是真正开始填充 layout 的方法。

        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;
            int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
            LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunkResult.resetInternal();
    
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
    
                if (layoutChunkResult.mFinished) {
                    break;
                }
    
                if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                    if (layoutState.mAvailable < 0) {
                        layoutState.mScrollingOffset += layoutState.mAvailable;
                    }
                    recycleByLayoutState(recycler, layoutState);
                }
            }
            return start - layoutState.mAvailable;
        }
    

    这就是省略了部分代码的 fill 方法,其中的 while 循环中,通过判断 LaytouState 中保存的状态来不断的通过 LayoutChunk 方法填充 view。所以接着再看 LayoutChunk 方法。

        void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
            View view = layoutState.next(recycler);
            if (view == null) {
                // 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;
            }
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addDisappearingView(view);
                } else {
                    addDisappearingView(view, 0);
                }
            }
        }
    

    以上就是 layoutChunk 的部分代码,可以看到通过 next 方法取出来 view ,并且通过 addView 添加到 RecyclerView 里面去,继续跟进next 方法,看看它是用什么方式和规则取出来 View 的。

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

    一路看到这里,我们的 Recycler 终于出现了...跟进 getViewForPosition 方法吧。

    在此之前可以先看看 Recycler 几个重要的成员变量,便于我们更好的认识 RecyclerView 的缓存结构:

            final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
            ArrayList<ViewHolder> mChangedScrap = null;
    
            final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    
            private final List<ViewHolder>
                    mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    
            RecycledViewPool mRecyclerPool;
    
            private ViewCacheExtension mViewCacheExtension;
    

    提前说一下,RecyclerView 其实可以算作是四级缓存、mAttachedScrap 、mCachedViews 、mViewCacheExtension、mRecyclerPool 这四个对象就是作为每一级缓存的结构的。

    getViewForPosition 方法:

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

    跟进 tryGetViewHolderForPositionByDeadline方法,一段一段的阅读这个方法代码。
    在文章之初我们就说过,RecyclerView 的缓存单元是 ViewHolder,这个tryGetViewHolderForPositionByDeadline 方法就完整的展示了如何在每个层级的缓存中,取出来 ViewHolder,下面我们一步步的分析一下:

                // 0) If there is a changed scrap, try to find from there
                if (mState.isPreLayout()) {
                    holder = getChangedScrapViewForPosition(position);
                    fromScrapOrHiddenOrCache = holder != null;
                }
    

    第一步,从 mChangedScrap 中尝试取出缓存的 ViewHolder ,若不存在,返回空。

                // 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(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;
                        }
                    }
                }
    

    如果在第一步发现没有缓存的 ViewHolder,则去 mAttachedScrap 中取,当然 mAttachedScrap 中没有怎么办呢,接着去 mHiddenViews 里面去找,如果还没有,继续从 mCachedViews 中取缓存的 ViewHolder ,这一系列操作是缓存层级的第二步。

                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);
                        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");
                            } else if (holder.shouldIgnore()) {
                                throw new IllegalArgumentException("getViewForPositionAndType returned"
                                        + " a view that is ignored. You must call stopIgnoring before"
                                        + " returning this view.");
                            }
                        }
                    }
                    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");
                        }
                    }
                }
    

    getScrapOrCachedViewForId 方法中,根据 id 依次在 mAttachedScrap 、mCachedViews 集合中寻找缓存的 ViewHolder,如果都不存在,则在 ViewCacheExtension 对象中寻找缓存,ViewCacheExtension 这个类需要使用者通过 setViewCacheExtension 方法传入,RecyclerView 自身并不会实现它,一般正常的使用也用不到。缓存层级的第三步,我们就分析完毕了,来看最后一步。

                    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);
                            }
                        }
                    }
    

    这段代码是从 RecycledViewPool 中取根据 type 取 ViewHolder,对于 RecycledViewPool 下面多说几句:

            static class ScrapData {
                ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
                int mMaxScrap = DEFAULT_MAX_SCRAP;
                long mCreateRunningAverageNs = 0;
                long mBindRunningAverageNs = 0;
            }
            SparseArray<ScrapData> mScrap = new SparseArray<>();
    

    从这里我们知道,RecycledViewPool 其实是一个 SparseArray 保存 ScrapData 对象的结构。根据 type 缓存 ViewHolder,每个 type,默认最多保存5个 ViewHolder。上面提到的 mCachedViews 这个集合默认最大值是 2 。

    RecycledViewPool 可以由多个 ReyclerView 共用。

    RecycledViewPool 就是缓存结构中的第四级缓存了,如果 RecycledViewPool 中依然没有缓存的 ViewHolder 怎么办呢?

                    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");
                        }
                    }
    

    这个时候没有办法,就只能调用 mAdapter.createViewHolder(RecyclerView.this, type),来创建一个 ViewHolder 了。

    到此我们就知道一个 ViewHolder 是如何层层从缓存中取出的了。

    写在后面:
    本文从源码的角度研究了 RecyclerView 的主要模块和功能,但是 RecyclerView 本身是很复杂的,要考虑到非常多的情况,光是各种状态的记录就让人看得很迷,这种复杂度的控件真不是一般程序员可以搞定的。不过我们不必深究每一块细节,将大致的流程梳理在心,也肯定会有收获和启发。未来打算总结下 RecyclerView 可能发生的一些复用问题。有疑问可以在下方交流。

    相关文章

      网友评论

      • 96fc63c5907a:曝光骗子宋文举: 简书昵称: wenju_song电话 13522410381 https://github.com/shengxinjing/programmer-job-blacklist/issues/136
      • 冉桓彬:写的挺好的, 说来惭愧, 工作三年的要学习你的文章; 不过现在你写文章的话, 会不会再抽个时间对写的某个知识点来一个一栏众山小? 不然只见树木不见森林
        MeloDev:@冉桓彬 刚换完工作 文章半年没写了 不过以后肯定会继续写下去~
      • 446fce01c97a:为什么垂直RecycletView 嵌套垂直RecyclerView(设置它不能滑动)的时候,不能正常的回收item,父RecyclerView只能回收一个一个的子RecyclerView,不能回收子RecyclerView里面的item,然而子RecycletView由于不能滑动,它的item不能正常的回收。那么最后当数据多了滑动就会OOM。怎么解决嵌套回收的问题呢?
        MeloDev:你嵌套的 RecyclerView 不能滑动,当然不能复用了,既然不能滑动,没有必要用 RecyclerView

      本文标题:RecyclerView 源码分析

      本文链接:https://www.haomeiwen.com/subject/cksslxtx.html