美文网首页
recyclerView缓存分析

recyclerView缓存分析

作者: me_biubiu | 来源:发表于2020-05-25 20:10 被阅读0次
    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/)

    相关文章

      网友评论

          本文标题:recyclerView缓存分析

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