RecyclerView源码分析二之绘制与复用

介绍

我们都知道在Android ViewGroup的绘制流程中,需要通过重写方法onMeasure(int width,int height) 和 onLayout(int l,int t,int r,int b)来实现自定定义ViewGroup的。
RecyclerView的测量、绘制依然是依靠这两个方法来实现的。

在这里插入图片描述

测量与绘制

测量
 @Override
 protected void onMeasure(int widthSpec, int heightSpec) {
     //LinearLayoutManager默认开启'自动测量'
     //onMeasure会执行两次
     if (mLayout.isAutoMeasureEnabled()) {
         final int widthMode = MeasureSpec.getMode(widthSpec);
         final int heightMode = MeasureSpec.getMode(heightSpec);

         //通过mLayout设置大小参数给recyclerview
         mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

//如果RecyclerView的宽高都是精确模式(Match_Parent或者精确的dp),则结束本方法的执行,接下来等待onLayout方法的执行
         final boolean measureSpecModeIsExactly =
                 widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
         if (measureSpecModeIsExactly || mAdapter == null) {
             return;
         }

//进行第一步绘制(后面会详细讲到)
         if (mState.mLayoutStep == State.STEP_START) {
             dispatchLayoutStep1();
         }
         //更新宽高信息到mLayout对象,mLayout是一个LayoutManager的实例
         mLayout.setMeasureSpecs(widthSpec, heightSpec);
         mState.mIsMeasuring = true;
         //进行真正的绘制布局操作
         dispatchLayoutStep2();

         //根据children的布局大小再做一次,宽高决策
         //如果是精确,则取精确对应的值
      	//如果是AT_MOST,则取 size和(disired,min中的最大值)的最小值
      	// 否则 disired,min中的最大值) 
         mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

       //如果ReyclerView没有精确的宽高,且它的children中至少有一个item宽和高不精确(针对LinearLayoutManager)
       //则需要进行二次测量
         if (mLayout.shouldMeasureTwice()) {
         	//RecyclerView先将自己的宽高设置为精确模式
             mLayout.setMeasureSpecs(
                MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
             mState.mIsMeasuring = true;
             //再进行二次布局
             dispatchLayoutStep2();
      	    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
         }
     } else {
        ...
     }
 }
/**
 * 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
 */
private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    //记录需要滚动的距离到State中
    fillRemainingScrollValues(mState);
    mState.mIsMeasuring = false;
    //优化requestLayout的调用
    startInterceptRequestLayout();
    //记录View动画信息的数据结果,清除缓存信息
    mViewInfoStore.clear();
    //标记滚动或者布局阶段,在此期间 某些方法不能被调用
    onEnterLayoutOrScroll();
    //处理Adapter更新内容和设置动画标记
    processAdapterUpdatesAndSetAnimationFlags();
    //保存焦点信息
    saveFocusInfo();
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    mState.mItemCount = mAdapter.getItemCount();
    //记录目前最大最小position
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

    //processAdapterUpdatesAndSetAnimationFlags方法中设置的标记
    //是否需要执行动画,该值在processAdapterUpdatesAndSetAnimationFlags()方法中进行设置
    if (mState.mRunSimpleAnimations) {
    	//下一篇介绍动画相关内容
    	....
    }

    //processAdapterUpdatesAndSetAnimationFlags方法中设置的标记
     //是否需要执行预测动画,该值在processAdapterUpdatesAndSetAnimationFlags()方法中进行设置
    if (mState.mRunPredictiveAnimations) {
        //下一篇介绍动画相关内容
        ...
        //进行原有位置信息的清除操作
        clearOldPositions();
    } else {
         //进行原有位置信息的清除操作
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    //设置状态为“布局”阶段
    mState.mLayoutStep = State.STEP_LAYOUT;
}
/**
* 真正的布局child的操作
*/
private void dispatchLayoutStep2() {
   	...
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    //将更新操作一次性 触发
    mAdapterHelper.consumeUpdatesInOnePass();
    //设置一下mState参数信息,便于在布局阶段使用
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
   	//进入布局阶段
    mState.mInPreLayout = false;
    //真正的布局操作,交由LayoutManager去实现,比如LinearLayoutManager
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
}
布局

咱们接着上面的内容,来看一下LinearLayoutManager的是如何布局RecyclerView的children的。

/**
* LinearLayoutManager.java
*/
@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.
 
    final View focused = getFocusedChild();
   	 //计算anchor的位置和座标信息
     updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
    mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
            ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    mReusableIntPair[0] = 0;
    mReusableIntPair[1] = 0;
    //已占用空间和布局方向
    calculateExtraLayoutSpace(state, mReusableIntPair);
    int extraForStart = Math.max(0, mReusableIntPair[0])
            + mOrientationHelper.getStartAfterPadding();
    int extraForEnd = Math.max(0, mReusableIntPair[1])
            + mOrientationHelper.getEndPadding();
  	//锚点位置确定
    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    //暂时回收attchedView
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mInfinite = resolveIsInfinite();
    mLayoutState.mIsPreLayout = state.isPreLayout();
   
    mLayoutState.mNoRecycleSpace = 0;
    if (mAnchorInfo.mLayoutFromEnd) {
        ....
    } else {
        //以mAnchorInfo为参照物 向后布局
        //将mAnchorInfo传递给mLayoutState对象,便于布局
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForEnd;
        //开始布局
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        final int lastElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        //以mAnchorInfo为参照物 向前布局
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtraFillSpace = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }
    ...
    //预布局动画(在下一篇会详细介绍)
    layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    if (!state.isPreLayout()) {
        mOrientationHelper.onLayoutComplete();
    } else {
        mAnchorInfo.reset();
    }
    mLastStackFromEnd = mStackFromEnd;
    if (DEBUG) {
        validateChildOrder();
    }
}
 /**
 * LinearLayoutManager.java
 */
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;
    //循环布局
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        //布局item
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            //重新计算剩余空间
            remainingSpace -= layoutChunkResult.mConsumed;
        }
    }
  
    return start - layoutState.mAvailable;
}
/**
* LinearLayoutManager.java
*/
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                  LayoutState layoutState, LayoutChunkResult result) {
     //获取一个ItemView
     //注意这里 实现了复用操作,具体的复用逻辑,再下一小节分析             
     View view = layoutState.next(recycler);
     RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
     //如果mScrapList为空,则正常即可
     if (layoutState.mScrapList == null) {
     	 ....
         addView(view, 0);
         ....
     } else {
         ...
         //否则 需要记录消失的动画信息
         //再下一篇将进行分析
         addDisappearingView(view);
         ...
     }
     //测量itemView的大小
     measureChildWithMargins(view, 0, 0);
     //每个item消耗的大小(横向或者纵向)
     result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
     //重新计算带有ItemDecoration的大小
     layoutDecoratedWithMargins(view, left, top, right, bottom);
   	 //如果当前的Item是被删除和更改的,则不消耗空间
     if (params.isItemRemoved() || params.isItemChanged()) {
         result.mIgnoreConsumed = true;
     }
     result.mFocusable = view.hasFocusable();
 }

接下来咱们分析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;
}
void dispatchLayout() {   
    //宽高都精确,执行该条件
    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 {
       	//将测量规格设置为“EXACTLY”
        mLayout.setExactMeasureSpecsFrom(this);
    }
    //触发必要的动画和必要的资源回收
    dispatchLayoutStep3();
}
 private void dispatchLayoutStep3() {
    //当前动画执行阶段
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    ...
    //重置阶段的状态
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) {
    	 //下一篇介绍动画相关内容
        ...
    }
	//如何确保回收地 不重复 不漏掉
    mLayout.removeAndRecycleScrapInt(mRecycler);
    ...
    if (mRecycler.mChangedScrap != null) {
        mRecycler.mChangedScrap.clear();
    }
	//布局完成,重置LayoutManager状态
    mLayout.onLayoutCompleted(mState);
 	...
    //恢复焦点信息
    recoverFocusFromState();
    resetFocusInfo();
}

缓存与复用机制

复用

ReyclerView中的复用机制是通过其内部类Recycler来实现

public final class Recycler {
		
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
               
        RecycledViewPool mRecyclerPool;
        private ViewCacheExtension mViewCacheExtension;
}

共有四级缓存:

  • mAttachedScrap 和 mChangedScrap
    • mAttachedScrap存储当前屏幕中的ViewHolder,并且可以根据id和position进行查找
    • mChangedScrap 用来缓存数据已经发生变化的,需要改变的ViewHolder。
  • mCachedViews
    • 用来缓存移除到屏幕中的ViewHolder,默认缓存容量为2.通过setViewCacheSize可以改变。缓存策略FIFO
  • ViewCacheExtension
    • 开发者用户自定义的缓存策略
  • ReycledViewPool
    • 如果mCachedViews中存储不下ViewHolder,则会将其放在RecycledPool中。可以更加Type查找ViewHolder。缓存策略根据type缓存ViewHolder。并且多个recyclerview之间共享RecycledViewPool

接下来看一下在LinearLayoutManager中,是如何调用Recycler复用ViewHolder的。

-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler) 
-> RecyclerView.Recycler.getViewForPosition(int) 
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView 
-> Recycler.tryGetViewHolderForPositionByDeadline(...)//真正获取ViewHolder的地方
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
        holder = getChangedScrapViewForPosition(position);
    }
    
    if (holder == null) {
        // 1) 分别从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
        //    这个 mHiddenViews 是用来做动画期间的复用
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    
    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) 如果 Adapter 的 hasStableIds 方法返回为 true
        //    优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }
      
        if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not know it.
            // 3) 从自定义缓存获取,别问,问就是别用
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }
    
    if (holder == null) {
        // 4) 从 RecycledViewPool 获取 ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }
  
    if (holder == null) {
        // 缓存全取过了,没有,那只好 create 一个咯
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
	 if (mState.isPreLayout() && holder.isBound()) {
      	...
     } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        ... 
        //如果有必要,进行数据绑定操作
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
     }
}
缓存

接下来,我们看一下,四级缓存之间是如何进行协调工作的。

mAttachedScrap和mChangedScrap
// 调用链 
   LinearLayoutManager.onLayoutChildren(...)
-> LayoutManager.detachAndScrapAttachedViews(recycler)
-> LayoutManager.scrapOrRecycleView(..., view)
-> Recycler.scrapView(view);  

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        // 后面会讲,缓存到 mCacheViews 和 RecyclerViewPool
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        // 缓存到 scrap
        recycler.scrapView(view); 
    }
}

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        // 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
        mAttachedScrap.add(holder);
    } else {
        mChangedScrap.add(holder);
    }
}

其实还有一种情况会调用到 scrapView , 从 mHiddenViews 获得一个 ViewHolder 的话(发生在支持动画的操作),会先将这个 ViewHolder 从 mHiddenViews 数组里面移除,然后调用:

   Recycler.tryGetViewHolderForPositionByDeadline(...)
-> Recycler.getScrapOrHiddenOrCachedHolderForPosition(...)
-> Recycler.scrapView(view)
mCacheViews和RecycledViewPool
void recycleViewHolderInternal(ViewHolder holder) {
    if (forceRecycle || holder.isRecyclable()) {
        if(mViewCacheMax > 0
             && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                 | ViewHolder.FLAG_REMOVED
                 | ViewHolder.FLAG_UPDATE
                 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
          int cachedViewSize = mCachedViews.size();
          if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
              // 1. mCacheViews 满了,最早加入的不要了放 RecyclerViewPool
              recycleCachedViewAt(0); 
          }
           mCachedViews.add(targetCacheIndex, holder);
           cached = true;
        }
        
        if (!cached) { 
            // 2. 不能放进 mCacheViews 的放 RecyclerViewPool
            addViewHolderToRecycledViewPool(holder, true);
        }
    }   
}

// Recycles a cached view and removes the view from the list
void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

在这我们知道 recycleViewHolderInternal 会把 ViewHolder 缓存到 mCacheViews ,而不满足加到 mCacheViews 的会缓存到 RecycledViewPool 。那又是什么时候调用的 recycleViewHolderInternal 呢?有以下三种情况:

  • 重新布局,主要是调用 Adapter.notifyDataSetChange 且 Adapter 的 hasStableIds 方法返回为 false 时调用。从这边也可以看出为什么一般情况下 notifyDataSetChange 效率比其它 notifyXXX 方法低(使用二级缓存及优先级更低的缓存 ),同时也知道了,如果我们设置 Adapter.setHasStableIds(ture) 以及其它相关需要的实现,则可以提高效率(使用一级缓存)
  • 在复用时,从一级缓存里面获取到 ViewHolder,但是此时这个 ViewHolder 已经不符合一级缓存的特点了(比如 Position 失效了,跟 ViewType 对不齐),就会从一级缓存里面移除这个 ViewHolder,添加到这两级缓存里面
  • 当调用 removeAnimatingView 方法时,如果当前 ViewHolder 被标记为 remove ,会调用 recycleViewHolderInternal 方法来回收对应的 ViewHolder。调用 removeAnimatingView 方法的时机表示当前的 ItemAnimator 已经做完了
总结

到这里,RecyclerView 的缓存复用机制应该分析完成了,总结一下:

  • RecyclerView 的缓存复用机制,主要是通过内部类 Recycler 来实现
  • Recycler 有 4 级缓存,每一级的缓存都有各自的作用,会按优先级使用。
  • ViewHolder 会从某一级缓存移至其它级别的缓存
  • mHideenViews 的存在是为了解决在动画期间进行复用的问题。
  • 缓存复用 ViewHolder 时会针对内部不同的状态 (mFlags) 进行相应的处理。

参考 RecyclerView 的缓存复用机制

上一篇RecyclerView源码分析一之简单介绍
下一篇RecyclerView源码分析三之动画分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章