recyclerView缓存分析

2020-05-25  本文已影响0人  me_biubiu
1.png

mAttachedScrap:onlayout可能执行多次,remove add view性能损耗过大,因而在其中先加入scrap缓存,执行viewgroup的detach子view方法,然后在fill()方法中addView的时候再将scrap中的缓存移出。
mChangedView:应该只在动画时我们不做多余分析
mCatchedViews:缓存刚溢出屏幕的viewholder,只缓存两个,满了以后再加回移出旧的至mReyclerViewPool,然后再添加新的。
mReyclerViewPool:每种viewholder缓存5个

源码分析:

情况1:可视窗口区域直接展示,不做任何滑动或notify方法

RecyclerView.onMeasure->dispatchLayoutStep2
onLayout->dispatchLayout()

void dispatchLayout() {
        ...
        } 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();
        }
        ...

viewgroup多次执行onlayout 如果给定宽高有变动还会再执行一次。
dispatchLayoutStep2->layoutManager.onLayoutChildren

   @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //确定了布局方向,正着倒着
        ...
        //在此处scrap了view
        detachAndScrapAttachedViews(recycler);
        //倒着fill
        if (mAnchorInfo.mLayoutFromEnd) {
            ...
        } else {
            //确认锚点位置,从锚点出开始向上向下填充
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            fill(recycler, mLayoutState, state, false);
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            fill(recycler, mLayoutState, state, false);
        }
    }

linearLayoutManager.detachAndScrapAttachedViews->遍历viewgroup中的childview执行scrapOrRecycleView

 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 (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
            removeViewAt(index);
            recycler.recycleViewHolderInternal(viewHolder);
        } else {
            //detach并且scrap
            detachViewAt(index);
            recycler.scrapView(view);
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }

fill过程

int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
             RecyclerView.State state, boolean stopOnFocusable{
     
        //循环,只要计算出下面还有空白区域需要填满则持续layoutChunk
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            //如果进入这个判断,会将移出屏幕的view回收至mCatchedViews或者recyclerViewPool中,但在这种情况下不会走入改循环,
            // 因为updateLayoutStateToFillStart方法设了layoutState.mScrollingOffset =SCROLLING_OFFSET_NaN
            if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
        }
    }

layoutManager.fill->layoutManager.layoutChunk

  void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LayoutState layoutState, LayoutChunkResult result) {
        //从各级缓存中取view 描述a
        View view = layoutState.next(recycler);
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                //此处addView并且unscrapView 
                addView(view);
            } else {
                addView(view, 0);
            }
        }
    }

next()->recycler.getViewForPosition(mCurrentPosition)->tryGetViewHolderForPositionByDeadline()

RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                                  boolean dryRun, long deadlineNs) {
        ...
        //如果scrap有缓存此处取出缓存,不然此种情况无catchedViews缓存只能总recyclerViewPool中取重新bind或者createViewholder
        // 1) Find by position from scrap/hidden list/cache
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }
        ...
    }

情况2:窗口滑动情况

RecyclerView的在onTouchEvent的ACTION_MOVE事件,这里对canScrollVertically方法进行了判断,并最终将偏移量传给了scrollByInternal方法,而在scrollByInternal方法中,调用了LayoutManager的scrollVerticallyBy方法。而scrollVerticallyBy最后调用了scrollBy方法,从而调到fill方法。

int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
             RecyclerView.State state, boolean stopOnFocusable{
        ...
        //循环,会继续计算填满下方因为滑动空出来需要填充的区域
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            //分析b
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            //如果进入这个判断,会将移出屏幕的view回收至mCatchedViews中,可能会将之前回收到mCatched中的viewholder挤到recyclerViewPool中,此时有偏移量走进该方法了 分析c
            if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
        }
    }

分析b
还是从next取view,最终调到tryGetViewHolderForPositionByDeadline

  RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position,

          //此时从scrap中是取不到 ,如果从上向下滑一个从未展示过的position,mCachedView中是肯定取不到的,
       //如果该position展示过就看mCatchedViews缓存的那俩是不是这个position的view吧,否则走recyclerPool+bind或createViewHOlder                                                       boolean dryRun, long deadlineNs) {
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }
}

分析c

recycleByLayoutState
->recycleViewsFromEnd()
->找到哪些移出了屏幕要被回收并recycleChildren(recycler, 0, i);
->遍历removeAndRecycleViewAt()

  public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index);
//实际通过callback调用到了recyclerView即viewgroup的removeView
            removeViewAt(index);
//在这个方法中调到recycleViewHolderInternal方法 回收view到mCatchedView中慢了就挤走原来的至pool中
            recycler.recycleView(view);
        }

情况3:notifyDataChanged的情况

以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()→onLayout()→onDraw()
走到dispathLayoutStep2()->onLayoutChildren()

数据源如果改变会更新所有的子view和mCatchedView标记FLAG_UPDATE,FLAG_INVALID

onLayoutChildren中同上面过程
linearLayoutManager.detachAndScrapAttachedViews->遍历viewgroup中的childview执行scrapOrRecycleView

 private void scrapOrRecycleView(RecyclerView.Recycler recycler, int index, View view) {
        final RecyclerView.ViewHolder viewHolder = getChildViewHolderInt(view);
        //因为isInvalid不能加入scrap重用了 只能removeView并放入pool中等待bind重用了
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
            removeViewAt(index);
            recycler.recycleViewHolderInternal(viewHolder);
        } else {
            detachViewAt(index);
            recycler.scrapView(view);
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }

在fill->next时也因为invalid无法拿到scrap mCatchedViews缓存

类图[https://www.jianshu.com/p/ff2e1fb56070/]

参考资料:
https://mp.weixin.qq.com/s/-CzDkEur-iIX0lPMsIS0aA
https://www.jianshu.com/p/2b19e9bcda84!
(https://www.jianshu.com/p/ff2e1fb56070/)

上一篇 下一篇

猜你喜欢

热点阅读