整体流程、Measure、Layout 详解——RecyclerView源码详解(一)

刚才有个朋友问我,博主发生什么事了,给我发了几张截图,我一看,哦,原来是有个大帅哔看了文章,说是,博主,我能白嫖你的文章,我说年轻人,点个赞再走,他说不点,我说点一个,他说不点,我说点一个,他说不点,我说我这文章对你有用,他不服气,说要先看看。我说可以,很快啊,看完后,就是一个复制,一个粘贴,一个网页关闭,我大意了啊,没有删除文章。按传统博客的有用为止,他说已经输了啊。 后来他说他是乱点的,这可不是乱点的啊,训练有素。我劝年轻人好好点赞,耗子尾汁,谢谢朋友们

前言

整体流程、measure、layout 详解 ——深入分析RecyclerView源码(一)
缓存 ——深入分析RecyclerView源码(二)
滑动和动画 ——深入分析RecyclerView源码(二)

本篇文章分析主体流程,先来整体看一下RecycleView的 结构
在这里插入图片描述

在这里插入图片描述
图片来自

  • RecycleView 是一个ViewGroup,想要显示数据集Datas,需要通过适配器Adapter,把数据转为对应的View,这样就可以添加到RecycleView中了。(适配器模式)
  • 由于屏幕能显示View的个数,往往是小于所有数据的个数。如果为每一个数据都创建一个View,效率肯定不够,所有使用Recycler来管理这些View,实现对View的创建,缓存,数据绑定(在代码实现中,缓存的是ViewHolder)
  • 不同的业务场景,需要不同的布局样式,有线性、网格、瀑布等,这里抽象出LayoutManager 来方便拓展,例如LinearLayoutManager等(策略模式)

这篇文章认为此处是桥接模式,桥接模式的核心是为了解决 如果有多个变化维度,每个维度有多种实现,若抽象类使用继承实现每个维度的每种实现,会导致的类爆炸。应该使用组合,抽象类和维度接口的组合(抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化)。桥接模式是一种结构型模式,不会在运行时改变。而策略模式,是一种行为模式,主要目的就是在运行时,可动态改变。这两种模式的类图很相似,但是要解决的问题完全不同。

  • 每当数据集发生变化的时候,需要调用notifyDataSetChanged 等函数,通知RecycleView更新界面。所以需要在Adapter(被观察者)和RecycleView(观察者) 建立监听关系(观察者模式)

源码分析

文章的源码版本是androidx recycleView 1.0.0

先来看看RecyclerView的构造函数

代码一:
RecyclerView.java
   public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //获取自定义属性值
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
            mClipToPadding = a.getBoolean(0, true);
            a.recycle();
        } else {
            mClipToPadding = true;
        }
        //设置当前View 是否可以滑动
        setScrollContainer(true);
        setFocusableInTouchMode(true);

        final ViewConfiguration vc = ViewConfiguration.get(context);
        //获取最小滑动的阈值
        mTouchSlop = vc.getScaledTouchSlop();
        //获取横竖方向的 滑动缩放因子,用在手势操作中
        mScaledHorizontalScrollFactor =
                ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);
        mScaledVerticalScrollFactor =
                ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);
        //获取最大最小 的滑动速率
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        //如果不可滑动,就不需要绘制自己了。如果设置了Item Decoration,就会设置为false
        setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);

        mItemAnimator.setListener(mItemAnimatorListener);
        //初始化AdapterManager,用于处理Adapter的数据发生变化,例如新增、删除item等
        //之所以需要它,如果有多个连续这样的操作,把这些操作放入队列,逐一处理(类似于fragment的处理方式)
        initAdapterManager();
        //初始化ChildHelper,用于管理子View,添加、删除item的View等
        initChildrenHelper();
        initAutofill();
        // If not explicitly specified this view is important for accessibility.
        if (ViewCompat.getImportantForAccessibility(this)
                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            ViewCompat.setImportantForAccessibility(this,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }
        mAccessibilityManager = (AccessibilityManager) getContext()
                .getSystemService(Context.ACCESSIBILITY_SERVICE);
        setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
        // Create the layoutManager if specified.
		//默认是支持嵌套滑动的
        boolean nestedScrollingEnabled = true;
        //从xml中 获取属性值
        if (attrs != null) {
            int defStyleRes = 0;
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
                    defStyle, defStyleRes);
            String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
            int descendantFocusability = a.getInt(
                    R.styleable.RecyclerView_android_descendantFocusability, -1);
            if (descendantFocusability == -1) {
                setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            }
            mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false);
            if (mEnableFastScroller) {
                StateListDrawable verticalThumbDrawable = (StateListDrawable) a
                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable);
                Drawable verticalTrackDrawable = a
                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable);
                StateListDrawable horizontalThumbDrawable = (StateListDrawable) a
                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable);
                Drawable horizontalTrackDrawable = a
                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable);
                initFastScroller(verticalThumbDrawable, verticalTrackDrawable,
                        horizontalThumbDrawable, horizontalTrackDrawable);
            }
            a.recycle();
            //创建LayoutManager,如果在xml中设置了LayoutManager ,这里会通过反射成功创建对应的LayoutManager
            
            createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);

            if (Build.VERSION.SDK_INT >= 21) {
                a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
                        defStyle, defStyleRes);
                nestedScrollingEnabled = a.getBoolean(0, true);
                a.recycle();
            }
        } else {
            setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        }
        //设置是否支持嵌套滑动
        // Re-set whether nested scrolling is enabled so that it is set on all API levels
        setNestedScrollingEnabled(nestedScrollingEnabled);
    }

下面我们来到View绘制流程的,RecyclerView把子View的measure和layout的交给LayoutManager,这样就可以方便定制布局。

在measure阶段,可能会调用LayoutManager的dispatchLayoutStep1() dispatchLayoutStep2(),这事为了计算出wrap_content的recycleView的大小

在layout阶段 ,分三个步骤 dispatchLayoutStep1() dispatchLayoutStep2() dispatchLayoutStep3() 对应的状态 STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS

  1. STEP_START 即将开始布局,此时会调用dispatchLayoutStep1,完成后 ,布局状态变为STEP_LAYOUT
  2. STEP_LAYOUT 真正开始布局,此时会调用dispatchLayoutStep2,会调用到layoutManager 中的layout,完成后,状态变为STEP_ANIMATIONS
  3. STEP_ANIMATIONS 执行动画,此时会调用dispatchLayoutStep3,

这三个函数会在onLayout 的时候分析,下面先来看看onMeasure

onMeasure

代码二
    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        //是否开启自动测量,如果true,就交给RecyclerView 去测量,如果false ,就由LayoutManager去测量。
        //现在自带的LayoutManager,都是返回true,例如 LinearLayoutManager
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            /**
             * This specific call should be considered deprecated and replaced with
             * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
             * break existing third party code but all documentation directs developers to not
             * override {@link LayoutManager#onMeasure(int, int)} when
             * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
             */
             //调用RecycleView 中的defaultOnMeasure,此处主要是设置measure的宽高模式,代码三分析
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
			//宽高的测量模式是否都是精确的
            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                //如果宽高的测量模式都是精确的,宽高不受子View 的影响,所以就直接返回
                return;
            }
            //代码执行到这里说明RecycleView的宽高 至少一个是wrap_content,
            //那么就需要measure、layout 子View后才能确定RecycleView的MeasureSpec
            //
            // 调用第一步layout,准备测量子View
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            //子View的measure,layout 是在layoutManager中进行的,所以需要把RecycleView的MeasureSpec 传递过去
            //在最终完成测量布局后,layoutManager中保存的RecycleView 的测量规则值可能会失效(详见下面疑问1)
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //调用第二步layout,测量子View
            dispatchLayoutStep2();
            //此时子View measure,layout完成,把可容纳这些View的宽高值 设置到RecycleView。代码四分析
            //这里有一个疑问,见下面的疑问2 
            // 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 {
            
        }
    }

看下代码 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); 做了些什么事情

代码三
   // 该函数是在LayoutManager 中
   //widthSpec、heightSpec 是RecyclerView的测量规格
   public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
                int heightSpec) {
       mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
  }
  // 该函数是在RecyclerView 中
  //根据测量规则计算 宽高值,并设置到RecyclerView的宽高中
   void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
   } 
   // 该函数是在LayoutManager 中
   //由上面的调用可知,desired 是padding值
   public static int chooseSize(int spec, int desired, int min) {
         final int mode = View.MeasureSpec.getMode(spec);
         final int size = View.MeasureSpec.getSize(spec);
         switch (mode) {
              case View.MeasureSpec.EXACTLY:
                    return size;
              case View.MeasureSpec.AT_MOST:
                    //注意,如果是AT_MOST,RecyclerView的还需要测量布局其子View,才能最终确定
                    //所以这里返回最小值,,并不能决定最终的RecyclerView的尺寸
                    return Math.min(size, Math.max(desired, min));
              case View.MeasureSpec.UNSPECIFIED:
              default:
                    return Math.max(desired, min);
         }
  }

继续来看一下onMeasure中调用的setMeasuredDimensionFromChildren

代码四
        void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
            final int count = getChildCount();
            if (count == 0) {
                //如果子View 测量完了,数量是0,设置RecyclerView的测量规格,就不用考虑子View的位置,使用测量规格
                mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
            //top,取最小值,bottom 取最大值
            //left 取最小值,right 取最大值
            //这样才能最大限度把所有子View都显示出来
            int minX = Integer.MAX_VALUE;
            int minY = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;
            int maxY = Integer.MIN_VALUE;
			
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                final Rect bounds = mRecyclerView.mTempRect;
                //需要考虑item 直接的装饰线
                getDecoratedBoundsWithMargins(child, bounds);
                if (bounds.left < minX) {
                    minX = bounds.left;
                }
                if (bounds.right > maxX) {
                    maxX = bounds.right;
                }
                if (bounds.top < minY) {
                    minY = bounds.top;
                }
                if (bounds.bottom > maxY) {
                    maxY = bounds.bottom;
                }
            }
            //得到容纳当前所有子View的尺寸,也就是RecyclerView的最大尺寸
            mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
            setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
        }
        
        public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
            int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
            int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
            //根据最大的尺寸和测量模式计算出 最终的测量规格,
            int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
            int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
            setMeasuredDimension(width, height);
        }

疑问1:在dispatchLayoutStep2() 中measure,layout子View 的时候,会使用到RecycleView的MeasureSpec,而该值是在dispatchLayoutStep2()之前就已经传入layoutManager了,dispatchLayoutStep2() 之后又要重新设置RecycleView的MeasureSpec, 那么是不是对子View UI的确定 就会有问题呢?因为确定子View 的时候,可能使用了不正确的RecycleView的MeasureSpec

要理清这个问题,需要先知道,在此时的代码RecycleView的宽高至少有一个是wrap_content,也就是不确定的。假设这样的情况,屏幕上的布局RecycleView 最大可容纳10个item,此时只有2个item,RecycleView的高度设置wrap_content

分4步简单介绍一下

1、在执行到RecycleView的onMeasure(), 父View会给出尽可能大的size,
2、RecycleView 把子View measure,layout完成后,再去设置自己具体的宽高值,
3、父View(这里假设为RelativeLayout) 在根据具体的RecycleView宽高值,设置RecycleView的LayoutParams
4、到layout阶段,就根据RecycleView的LayoutParams,去设置top,bottom,left,right 的值

这个问题大概分析到这里,下面贴一下主要的代码

  • 对应步骤1
    下面是RelativeLayout的onMeasure 函数,其中调用了measureChild 会调用到RecycleView的onMeasure ()
    在这里插入图片描述

  • 对应步骤3
    进入到RelativeLayout的positionChildVertical 函数,把RecycleView的LayoutParams 进行调整,注意图中断点处的child.getMeasureHeight 获取到的是最新的RecycleView的尺寸(就是步骤2 执行完后,能容纳子View的宽高)

在这里插入图片描述

  • 对应步骤4
    这里执行到RelativeLayout的onLayout 函数,看到在设置RecycleView的top,bottom,left,right 的值,使用的是LayoutParams的值,也就是新得到的RecycleView尺寸

在这里插入图片描述

疑问2:为什么需要子View measure,layout都完成后,才能确定RecycleView的宽高呢?
想想网格布局和瀑布布局

注意,此时LayoutManager中的保存RecycleView的宽高值 和 当前RecycleView 的宽高值 ,就不一样了。这个很关键,正因为这两个值不一样,所以下面的dispatchLayout 阶段,需要重新执行一次dispatchLayoutStep2() 。下面来分析绘制流程的onLayout

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

    void dispatchLayout() {
        .... 省略判空代码...
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            //STEP_START,需要从第一步layout 执行
            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.
            //如果是wrap_content 会进入到这个分支,
            //此时把LayoutManager中的保存RecycleView的测量模式设为精确,因为在LayoutManager布局的时候,无法改变RecycleView的大小
           
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

在上面介绍过,Layout被分为三个阶段, dispatchLayoutStep1() dispatchLayoutStep2() dispatchLayoutStep3(),下面来逐一分析这几个函数

dispatchLayoutStep1()

代码六
    private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        fillRemainingScrollValues(mState);
        mState.mIsMeasuring = false;
        //开启拦截 RequestLayout,避免多余的RequestLayout
        startInterceptRequestLayout();
        //记录View的布局前后状态,第三阶段根据状态变化,来最后执行动画。这里先清除 
        mViewInfoStore.clear();
        onEnterLayoutOrScroll();
        //执行队列任务,notifyItemXXXXX 这系列函数,产生的任务。例如:增加了一个item等
        //mRunSimpleAnimations、mRunPredictiveAnimations 这两个值 也是由此计算出来的
        processAdapterUpdatesAndSetAnimationFlags();
        // 查找焦点view并设置mState焦点相关变量
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        //获取Adapter 中item的数量
        mState.mItemCount = mAdapter.getItemCount();
        //获取View最小和最大的布局索引位置 ,保存在mMinMaxLayoutPositions 数组中
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
		
			
        if (mState.mRunSimpleAnimations) {
            //第一次layout 肯定不会到这个分支,在完整布局过一次后,该变量才可能为true。
            //该分支主要是记录view 布局前的状态,为后续动画做准备
            // Step 0: Find out where all non-removed items are, pre-layout
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                ....省略 代码...
                //使用mItemAnimator 获取一个ItemHolderInfo 变量,该变量记录了Item在layout之前的边界
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                //以holder为键,把animationInfo 保存起来
                //mViewInfoStore 主要是保存动画View 的前后状态
                //在dispatchLayoutStep3(),根据最终的状态,来执行动画。
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                ....省略 代码...
            }
        }
        if (mState.mRunPredictiveAnimations) {
            ....省略 代码...
        } else {
            // 第一次布局(还没有一次完整的布局) ,会执行到这里
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        //停止 拦截RequestLayout
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

dispatchLayoutStep2()

这里是真正的布局,会调用到LayoutManager中

代码七
    private void dispatchLayoutStep2() {
        startInterceptRequestLayout();
        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;
        //调用到layoutManager中,实现真正的布局
        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();
        stopInterceptRequestLayout(false);
    }

关于这部分的源码分析,网上虽然也有,不仅没有分析透彻,而且还是错误的(这篇文章 关于锚点的不够全面,但我相信该作者理解原理,可能是大意了。这篇文章,关于锚点的描述,是直接把英文文档翻译了一下,fill towards end 翻译成 结束填充,不知道要误导多少人了。)

下面以LinearLayoutManager 分析一下onLayoutChildren 函数的作用,首先找到一个锚点(数据集的索引mPosition,屏幕的像素位置mCoordinate),以这个点为基础,按照指定方向(start 或end)来填充item

那么这个点是如何找到的呢?分为三种情况:

  1. 初始的时候,这个点mPosition = 0,mCoordinate = 0,也就是说初始就是按照一个方向,LinearLayoutManager 默认为end,从上到下填充item。
  2. 滚动到指定索引位置(例如,调用scrollToPosition),这里又分为两种情况
  • 2.1. 目标位置mPosition 已经显示在当前屏幕 ,那么计算出该位置的屏幕位置mCoordinate,因为该位置已经在屏幕上显示了,所以需要分别填充它的上面和下面的item,也就是每个分支中 fill 会被调用两次的原因
  • 2.2. 目标位置mPosition 没有显示在当前屏幕,那么计算出该位置的屏幕位置mCoordinate,因为没有显示到屏幕,所以就设置它的mCoordinate为最上(需要向下滚动)或者最下(需要向上滚动),此时只要一次fill填充,就能完成。在执行下一个fill的时候,不符合条件 会直接在退出
  1. 在已有的item中选择,如果有item 正在获得焦点,就优先考虑它是否可以成为锚点,否则根据mLayoutFromEnd, 选择一个最靠近start 或end的item,

mLayoutFromEnd 的意识就是 布局是否从End方向开始。在选择锚点的时候,该变量是这样赋值的mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;

mShouldReverseLayout 表示 列表是否翻转,例如 原来是顺序 ,改为倒序

mStackFromEnd 表示 item的堆叠方向是否从End->Start,改变该值不影响列表的顺序,原来item 在列表中是第几个,该值改变后还是第几个。该值决定了,在屏幕上最终看到的是前几个item ,还是后几个item。

这两个值互相组合,有四种情况,上图,图中粉色的线,为锚点 所在的值。锚点不应该是一个点吗,因为本例是垂直方向的LinearLayoutManger,锚点是Y轴上的值,所以图中使用了横向的线来表示
在这里插入图片描述

下面就在代码中来一探究竟

代码八
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state

        ... 省略代码...
        //从保存的待定状态中 设置待定的滚动位置 mPendingScrollPosition。如果有值,那么在获取锚点的时候,就会以此为准
        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
        }

        ... 省略代码...

        final View focused = getFocusedChild();
        //mValid  在初次或者一次完整的绘制完成,该值等于false
        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            //上面分析的三点,就是个函数里
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                        >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
          ... 省略代码...
        }
         ... 省略代码...
         //这两个extra 的值,代表了可是额外使用的空间,虽然可以绘制,但是用户是看不到的。 这部分看不到的子View,为什么还需要绘制呢? 例如,为了即将到来的平滑滚动
        int extraForStart;
        int extraForEnd;
        //getExtraLayoutSpace这个函数会判断是否有平滑滚动,如果有,那么额外空间,就给当前RecycleView的可绘制空间。否则为0
        //这个额外空间是否给大了?下面可能还会根据即将滚动的位置进行调整
        final int extra = getExtraLayoutSpace(state);
        // If the previous scroll delta was less than zero, the extra space should be laid out
        // at the start. Otherwise, it should be at the end.
        if (mLayoutState.mLastScrollDelta >= 0) {
            extraForEnd = extra;
            extraForStart = 0;
        } else {
            extraForStart = extra;
            extraForEnd = 0;
        }
        extraForStart += mOrientationHelper.getStartAfterPadding();
        extraForEnd += mOrientationHelper.getEndPadding();
        //下面这部分代码,根据是否预布局和即将要滚动到的位置,来调整,这个需要绘制的额外空间(也就是屏幕可用绘制以外的空间)
        if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
                && mPendingScrollPositionOffset != INVALID_OFFSET) {
            // if the child is visible and we are going to move it around, we should layout
            // extra items in the opposite direction to make sure new items animate nicely
            // instead of just fading in
            final View existing = findViewByPosition(mPendingScrollPosition);
            if (existing != null) {
                final int current;
                final int upcomingOffset;
                if (mShouldReverseLayout) {
                    current = mOrientationHelper.getEndAfterPadding()
                            - mOrientationHelper.getDecoratedEnd(existing);
                    upcomingOffset = current - mPendingScrollPositionOffset;
                } else {
                    current = mOrientationHelper.getDecoratedStart(existing)
                            - mOrientationHelper.getStartAfterPadding();
                    upcomingOffset = mPendingScrollPositionOffset - current;
                }
                if (upcomingOffset > 0) {
                    extraForStart += upcomingOffset;
                } else {
                    extraForEnd -= upcomingOffset;
                }
            }
        }
        ... 省略代码...
        //回收当前可见的view到mAttachedScrap中,这样等下就可以直接复用,不需要重建了
        detachAndScrapAttachedViews(recycler);

        if (mAnchorInfo.mLayoutFromEnd) {
            //这里的逻辑和else分支的逻辑大体相同,只是调用顺序有所调整,因为一个 是从底向顶,一个是从顶向底
            ... 省略代码...
        } else {
            // fill towards end
            //设置布局的一些信息,例如 方向,可用空间等。下面会详细分析
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            //获取或创建 子View,然后进行addview,就添加到RecycleView中了
            fill(recycler, mLayoutState, state, false);
            //这个偏移量,就是锚点的值(例如:垂直方向的LinearLayoutManager ,就是Y 轴方向的值)
            endOffset = mLayoutState.mOffset;
            //锚点 的 数据集对应的索引
            final int lastElement = mLayoutState.mCurrentPosition;
            //一次填充后,屏幕的可用空间 还有剩余。那就加入到extraForStart,
            // 见下面 疑惑1
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            //比较常见的调用到这里的,就是上面关于锚点寻找2.1 的情况
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            //这里的mItemDirection 会根据绘制方向,为 1 或 -1 
            //绘制完 第n个及以下的view,如果需要向上绘制,就需要从第n-1开始。此时mItemDirection 就应该是-1 。
            //该值是在updateLayoutStateToFillxxx中 赋值的
            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;
            }
        }

        // changes may cause gaps on the UI, try to fix them.
        // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
        // changed
        if (getChildCount() > 0) {
            // because layout from end may be changed by scroll to position
            // we re-calculate it.
            // find which side we should check for gaps.
            if (mShouldReverseLayout ^ mStackFromEnd) {
                int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
                startOffset += fixOffset;
                endOffset += fixOffset;
                fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
                startOffset += fixOffset;
                endOffset += fixOffset;
            } else {
                int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
                startOffset += fixOffset;
                endOffset += fixOffset;
                fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
                startOffset += fixOffset;
                endOffset += fixOffset;
            }
        }
        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
        if (!state.isPreLayout()) {
            mOrientationHelper.onLayoutComplete();
        } else {
            mAnchorInfo.reset();
        }
        mLastStackFromEnd = mStackFromEnd;
    }

updateLayoutStateToFillEnd 和 updateLayoutStateToFillStart 内部的逻辑基本是对称的

代码九
    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
        updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
    }

    private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
         //获取可用的空间,例如垂直方向的linearLayoutManager,就是y轴上的可用像素大小
        mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
        //根据是否翻转列表,来设置该值 1,或-1,这样在获取下一个item的时候,都只要 加上该值就可以了。
        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
                LayoutState.ITEM_DIRECTION_TAIL;
        mLayoutState.mCurrentPosition = itemPosition;
        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
        //当前item的像素位置,例如垂直方向的linearLayoutManager,就是y轴上像素的值
        mLayoutState.mOffset = offset;
        mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
    }

fill 函数是实现view的填充的,根据屏幕的大小 ,来判断填充多少,它的返回是填充了多少像素(在LinearLayoutManager中指在垂直或横向 填充了多少像素),该数值可能会大于屏幕的像素。例如最后一个item,只显示了一部分

代码十
    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;
            }
            //回收view,那些滚动后 出界的子view
            recycleByLayoutState(recycler, layoutState);
        }
        //得到可用空间
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        //循环填充子View,三个判断条件,每添加一个子View,remainingSpace会减去相应的像素值。
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
			//这里是真正获取子View,Measure,layout的地方,下面会分析
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
			
            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;
            }

            ... 省略代码...
        }
        //返回总共填充了多少像素,在期望的方向上
        return start - layoutState.mAvailable;
    }

接着看一下,添加子View的函数 layoutChunk 中看看,每执行一次就添加了一个子View

代码十一
   void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        // 从recycler 中获取一个View,这个view 可以是新建的,也可能是从CacheView或 RecycleViewPool中获取的,
        //其中会执行createViewHolder,binderViewHolder
        //关于View 缓存的源码,会在下一篇分析
        View view = layoutState.next(recycler);
        ... 省略代码...
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        //添加子View
        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);
            }
        }
        //测量子view的大小
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        //根据绘制的方向,来确定子view的位置
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == 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 == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        //最终进行layout,
        // 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);

        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

dispatchLayoutStep2() 就算分析完了,下面来看看dispatchLayoutStep3()

dispatchLayoutStep3()

代码十二
    private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
            // Step 3: Find out where things are now, and process change animations.
            // traverse list in reverse because we may call animateChange in the loop which may
            // remove the target view holder.
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore()) {
                    continue;
                }
                long key = getChangedHolderKey(holder);
                //还记得,在dispatchLayoutStep1()中调用类似的函数,来获得ItemHolderInfo
                // 获取holder的边界信息,这里是布局完成后的
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                
                //以holder为键,把animationInfo 保存到 mLayoutHolderMap,这样就有了该View布局前和布局后的状态
                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                    // run a change animation

                    ... 省略代码...
                     mViewInfoStore.addToPostLayout(holder, animationInfo);
                     ... 省略代码...
                } else {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }
            //有了View的前后状态,就开始执行动画
            // Step 4: Process view info lists and trigger animations
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
        //还记得在onLayoutChildren中把可见的view 都缓存到mAttachedScrap
        //现在布局完成了,该复用的都复用了,就不需要这一级的缓存了,这里清除掉
        mLayout.removeAndRecycleScrapInt(mRecycler);
        mState.mPreviousLayoutItemCount = mState.mItemCount;
        mDataSetHasChangedAfterLayout = false;
        mDispatchItemsChangedEvent = false;
        mState.mRunSimpleAnimations = false;

        mState.mRunPredictiveAnimations = false;
        mLayout.mRequestedSimpleAnimations = false;
        if (mRecycler.mChangedScrap != null) {
            mRecycler.mChangedScrap.clear();
        }
        //在滑动屏幕的时候,触发GapWorker 任务,其中会设置到这个值。在那里把缓存的数量了提升了,
        //这里就根据该值,把缓存数量降下来
        if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
            // Initial prefetch has expanded cache, so reset until next prefetch.
            // This prevents initial prefetches from expanding the cache permanently.
            mLayout.mPrefetchMaxCountObserved = 0;
            mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
            mRecycler.updateViewCacheSize();
        }
		//一次完整layout 执行完成
        mLayout.onLayoutCompleted(mState);
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mViewInfoStore.clear();
        if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
            dispatchOnScrolled(0, 0);
        }
        recoverFocusFromState();
        resetFocusInfo();
    }

上面涉及到的几个函数,就不展开了,内容都很简单,对照源码 看一下,就明白了

只能说分析了大体流程,虽然我觉得比网上的文章要深入一些,但是距离100%解析,还是有点距离,不过整体轮廓已经出来了,有兴趣的朋友可以继续把细节钻一钻。

setLayoutManager

如果没有在xml中指定LayoutManager,那么需要在代码中设置recyclerView.setLayoutManager(new LinearLayoutManager(this));
下面来看一下 setLayoutManager

代码十三
RecyclerView.java
    public void setLayoutManager(@Nullable 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.
        //之前是否已经设置过LayoutManager
        if (mLayout != null) {
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            //mRecycler 是用来管理子View的,缓存、删除、获取都会调用它
            //删除所有的view 
            mLayout.removeAndRecycleAllViews(mRecycler);
            //删除缓存的Scrap view
            mLayout.removeAndRecycleScrapInt(mRecycler);
            mRecycler.clear();

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            //删除缓存AttachedSrap、CachedView 缓存,把CachedView 的缓存加入到RecycledViewPool中
            //关于缓存的会在下一篇文章详细分析
            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.exceptionLabel());
            }
            //把RecyclerView、及宽高、mChildHelper 设置到mLayout 中
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        //设置CacheView 缓存的数量,默认为2。可调用setViewCacheSize来设置
        mRecycler.updateViewCacheSize();
        //重新布局,就会走onMeasure,onLayout流程
        requestLayout();
    }

setAdapter

代码十四
    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        //设置adapter
        setAdapterInternal(adapter, false, true);
        //adapter 发生变化,需要通过Recycler 把所有的ViewHolder回收。并给这些ViewHolder设置无效标志位
        processDataSetCompletelyChanged(false);
        
        requestLayout();
    }

下面来分析一个setAdapterInternal的逻辑

代码十五
    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            //mAdapter 不为null ,说明之前已经设置过了,需要取消注册的观察者
            mAdapter.unregisterAdapterDataObserver(mObserver);
            //Adapter 和 Recycleview分离,这是一个生命周期性的函数,看自定义覆盖,源码中该函数为空
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            //根据上面传入的参数,这里一定会执行,删除和回收ViewHolder
            removeAndRecycleViews();
        }
        //情况任务队列,这里任务,例如 增加,删除View 等
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            //代码十六会分析
            adapter.registerAdapterDataObserver(mObserver);
            //这是一个生命周期性的函数,看自定义覆盖,源码中该函数为空
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }
代码十六
在Adapter 类中
        //每个adapter中都有的变量,可被观察者,把他注册到观察者
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        //observer 是AdapterDataObserver类型,它的实现类是RecyclerViewDataObserver
        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
            //把观察者observe  注册到 被观察者mObservable中的mObservers列表中,
            //也就是说一个被观察者,可以有多个观察者,这样用户就可以自定义这些事件的处理方式
            mObservable.registerObserver(observer)
        }

下面来看一下这个观察者RecyclerViewDataObserver 和 被观察者 AdapterDataObservable

代码十七
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }
         ... 省略代码...
        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            //可以看到遍历所有的观察者,调用它们的函数
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
        ... 省略代码...
    }   
代码十八
    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }


        ... 省略代码...
        //触发观察者的事件
        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            // mAdapterHelper 中的Pending Updates 队列 == 1,就立即执行,
            // 如果大于1,说明有任务正在执行,直接加入到队列
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }
         ... 省略代码...
         //触发布局,执行动画
         //下面的详细步骤,会在后面的文章分析
        void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
    }
代码十九
在AdapterHelper.java 中

    boolean onItemRangeInserted(int positionStart, int itemCount) {
        if (itemCount < 1) {
            return false;
        }
        //在待更新任务列表中 ,加入该类型的操作
        mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
        mExistingUpdateTypes |= UpdateOp.ADD;
        //如果任务列表数量 为1 ,返回true。需要立即执行的,该判断为了避免重复触发处理操作。
        return mPendingUpdates.size() == 1;
    }

熟悉并发编程的朋友,一定能马上意识到,这里mPendingUpdates.size() == 1;是否有问题,如果mPendingUpdates 里的任务已经处理完成,在要清理的时候,切换了线程,而此时加入一个新的任务后,改语句返回false,也就是无法触发执行操作。以后再继续增加,岂不是一直不能触发执行了。 我翻了半天源码没有找到线程同步的逻辑,后来看到triggerUpdateProcessor()的else分支的调用了requestLayout();,说明mPendingUpdates的增加,是在主线程。而是mPendingUpdates的任务执行 ,也是在主线程。所以这里不会有线程安全问题

参考:
转-桥接模式和策略模式的区别
经典设计模式UML类图汇总

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页