结合源码了解RecyclerView工作机制详解

RecyclerView 众所周知有四级缓存是目前性能最好的ListView控件,官方也是推荐使用这个控件,同时支持LayoutManager 以及 ItemDecoration 自定义元素的摆放以及分线线。接下来我们结合RecyclerView的源码来了解下它的工作机制, 只有了解了工作机制以后我们才更容易方便我们来使用它。
首先我们来一个简单使用

RecyclerView recycler_view = findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);

recycler_view.setLayoutManager(layoutManager);

List<String> stringList = new ArrayList<>();
for (int i = 0; i < 200; i++) {
    int count = (int) (Math.random() * 10);
    StringBuilder builder = new StringBuilder();
    for (int i1 = 0; i1 < count; i1++) {
        builder.append("a");
    }
    stringList.add(builder.toString());
}
RecyclerAdapter testAdapter = new RecyclerAdapter(stringList);
recycler_view.setAdapter(testAdapter);

从上面的代码我可以看到最基本的设置需要上面的一些元素,必须有一个LayoutManager 因为他是控制元素的摆放的,还有需要一个适配器Adapter它是提供数据绑定和创建itemView对象的。

    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.
        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();
        //将LayoutManager赋值给了mLayout
        mLayout = layout;
        if (layout != null) {
			。。。。
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        mRecycler.updateViewCacheSize();
        requestLayout();
    }

注释可以看出mLayout就是LayoutManager,同时做了一些初始化操作。

    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
       // ....  移除和取消一些东西  
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        //同样的是adapter 给了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;
    }

从上面的注释可以最基础的结论就是mAdapter 就是我们的AdaptermLayout 就是我们的LayoutManager。他们都调用了requestLayout方法,去执行RecyclerView的测量 布局以及绘制方法。


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

        /**
         * 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.
         */
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

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

        if (mState.mLayoutStep == State.STEP_START) {
        	// 摆放item前的操作
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        //完成 控件的摆放, 自定义LayoutManager 的核心方法在这个api中被调用
        dispatchLayoutStep2();

        // now we can get the width and height from the children.
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

        // 如果RecyclView 宽和高 有一个是不确定的 warp_content 
        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 {
    	//通常我们自定义LayoutManager 不会设置setAutoMeasureEnabled 所以测量方法会走这个else
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // 只有adapter 调用 removenotify 这种更新api时 mAdapterUpdateDuringMeasure = true
        // custom onMeasure
        if (mAdapterUpdateDuringMeasure) {
            startInterceptRequestLayout();
            onEnterLayoutOrScroll();
            processAdapterUpdatesAndSetAnimationFlags();
            onExitLayoutOrScroll();

            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            stopInterceptRequestLayout(false);
        } else if (mState.mRunPredictiveAnimations) {
        	// mRunPredictiveAnimations 只有执行了dispatchLayoutStep1 才会赋值 true
            // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
            // this means there is already an onMeasure() call performed to handle the pending
            // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
            // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
            // because getViewForPosition() will crash when LM uses a child to measure.
            setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
            return;
        }

        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        startInterceptRequestLayout();
        // 测量  mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        stopInterceptRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

在测量方法中我们可以看到dispatchLayoutStep1dispatchLayoutStep2 dispatchLayoutStep3 意思很明显步骤。但是通常我们不会设置setAutoMeasureEnabled(true) 所以上面三个方法并不会执行(LinearLayoutManager会)。
紧接着即使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() {
        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()) {
                //如果adapter有更新 或者 大小发生改变都会运行第二步。
            // 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();
    }

我们依然可以看到 dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3 三个布局步骤。
dispatchLayoutStep1 : 更新适配器 、决定运行那个动画、保存视图相关信息,如果有必要还会进行一些预测布局。
dispatchLayoutStep2 : 实际布局的执行, 可能会运行多次 。
dispatchLayoutStep3 : 保存动画信息,触发动画并执行清理操作。
根据上面解释可以清楚知道,我们布局的核心就在第二步。在代码中的注释也可以清楚执行这一点。

    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的核心方法,onLayoutChildren
        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);
    }

在上面我们找到了onLayoutChildren 这个自定义LayoutManager的核心方法。 代码看了这么多依然不见四级缓存, 其实RecyclerView有个专门处理类Recycler。

public final class Recycler {
	//一级缓存 存放的是当前显示的View
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
	//二级缓存 大小是2 存放的是 经常需要使用的View 比如 来回滑动RecyclerView 一小段距离,让其频繁复用最边界的两个Item。此时的view就存放在这个缓存容器
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;
	//第四级缓存 当缓存的内容大小超过2 就会储存在mRecyclerPool 中,它是根据 ItemViewType 去分类存储的
    RecycledViewPool mRecyclerPool;
	//第三级缓存, 默认留给用户自己实现
    private ViewCacheExtension mViewCacheExtension;
	//第二级缓存的默认大小
    static final int DEFAULT_CACHE_SIZE = 2;
  
    //省略代码。。。。。
}  

上面RecyclerView默认的四个缓存容器分别是mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool
接下来我们看看 这个四个缓存是怎样被使用的。首先我们从这个mAttachedScrap开始,它是屏幕内的View集合。看看他是什么时候被添加的东西的。
Recycler.java (RecycleView的内部类)

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
            throw new IllegalArgumentException("Called scrap view with an invalid view."
                    + " Invalid views cannot be reused from scrap, they should rebound from"
                    + " recycler pool." + exceptionLabel());
        }
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

这里我们完成了mAttachedScrap 集合添加数据。但是我们在这里看不出什么,继续跟踪。
LayoutManager.java(RecycleView的内部类)

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
      final ViewHolder viewHolder = getChildViewHolderInt(view);
      if (viewHolder.shouldIgnore()) {
          if (DEBUG) {
              Log.d(TAG, "ignoring view " + viewHolder);
          }
          return;
      }
      // 这里的if 暂时不知道怎么解释
      if (viewHolder.isInvalid() && !viewHolder.isRemoved()
              && !mRecyclerView.mAdapter.hasStableIds()) {
          removeViewAt(index);
          recycler.recycleViewHolderInternal(viewHolder);
      } else {
          detachViewAt(index);
          //但是这里调用了 scrapView 方法
          recycler.scrapView(view);
          mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
      }
  }

在scrapOrRecycleView 中我们找到scrapView 方法。接着看这个方法,什么时候被调用。
LayoutManager.java (RecycleView的内部类)

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

搜索这个方法招不到这个方法 在RecycleView.java 中, 说明他可能在LayoutManager的实现类 中被调用。查看LinearLayoutManager搜索在 onLayoutChildren 中使用到了这个方法。

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

   if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
       if (state.getItemCount() == 0) {
           removeAndRecycleAllViews(recycler);
           return;
       }
   }
	// 省略。。。。。
   onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
   detachAndScrapAttachedViews(recycler);
   //  省略。。。。。
}

调用摆放的方法的时候我们就会向 一级缓存mAttachedScrap 中添加holder。
这里由回到了当初的三个步骤方法,dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3

接下来看看 二级缓存
recycleViewHolderInternal

void recycleViewHolderInternal(ViewHolder holder) {

    //noinspection unchecked
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;

    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // Retire oldest cached view
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            // 存放在二级缓存中
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
        	//二级缓存中存不了,就存四级缓存中
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {

    }
  
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

这个方法很明显就是如果能存 二级缓存 mCachedViews 不能存就存addViewHolderToRecycledViewPool

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
     clearNestedRecyclerViewIfNotNested(holder);
     if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
         holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
         ViewCompat.setAccessibilityDelegate(holder.itemView, null);
     }
     if (dispatchRecycled) {
         dispatchViewRecycled(holder);
     }
     holder.mOwnerRecyclerView = null;
     //添加到RecycledViewPool中
     getRecycledViewPool().putRecycledView(holder);
 }

三级缓存 mViewCacheExtension 是一个自定义缓存

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
        // 分别从四个缓存中取数据
        if (holder == null && mViewCacheExtension != null) {
       	//我们只需要实现这个getViewForPositionAndType 完成自己的获取holder 的方式就行
        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());
            }
        }
    }
}

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

在LinearLayoutManager 中有个next方法,我们每次需要数据就会调用这个方法

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

而我们在自定义LayoutManager时通常都是直接使用recycler.getViewForPosition(mCurrentPosition) 取条目的holder。

发布了58 篇原创文章 · 获赞 16 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/dingshuhong_/article/details/103483197