android进阶篇02RecyclerView回收复用机制源码解析

Posted datian1234

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android进阶篇02RecyclerView回收复用机制源码解析相关的知识,希望对你有一定的参考价值。

原文链接:https://juejin.cn/post/6960953337965445128

前言

首先我们明确一点,回收复用的是ViewHolder,并且回收复用机制一般包括四级缓存:

1.mAttachedScrap和mChangedScrap
2.mCachedViews
3.自定义缓存机制mViewCachedExtension
4.RecyclerViewPool缓存池

其中第三级自定义缓存一般不需要,这里不再讨论;滑动时主要是使用第二级和第四级缓存,这也是使用最多的情况,我们在下面会详细讨论这种情况;

第一级缓存主要用于屏幕内ViewHolder的缓存和复用,例如下拉刷新等需要重新布局的操作可以直接复用ViewHolder;

一、回收复用前的调用链分析

1、RecyclerView -> onTouchEvent

既然是滑动时进行回收复用,那自然可以想到从RecyclerView的onTouchEvent方法的MOVE分支中切入分析,注释1处会调用scrollByInternal方法;

public boolean onTouchEvent(MotionEvent e) {
            、、、
    case MotionEvent.ACTION_POINTER_DOWN: {
        、、、
    } break;
    case MotionEvent.ACTION_MOVE: {                                
        、、、
        //1
        if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)){
            getParent().requestDisallowInterceptTouchEvent(true);
        }                             
    } break;
    、、、
}

2、RecyclerView -> scrollByInternal

scrollByInternal中又会调用scrollStep;

boolean scrollByInternal(int x, int y, MotionEvent ev) {
    、、、
    scrollStep(x, y, mReusableIntPair);
    、、、
}

3、RecyclerView -> scrollStep

我们以注释2举例,又会调用mLayout的scrollVerticallyBy方法,我们以最常用的LinearLayoutManager举例;

void scrollStep(int dx, int dy, @Nullable int[] consumed) {
    、、、
    if (dx != 0) {
        consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); //1
    }
    if (dy != 0) {
        consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); //2
    }
}

4、LinearLayoutManager -> scrollVerticallyBy

如下所示,注释1表示scrollVerticallyBy又会调用scrollBy;

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
    if (mOrientation == HORIZONTAL) {
        return 0;
    }
    return scrollBy(dy, recycler, state); //1
}

5、LinearLayoutManager -> scrollBy

如下所示,注释1处表示又会调用fill方法,官方注释表示这是一个神奇的方法,回收与复用共同的入口;

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
    、、、
    final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false); //1
    、、、
}

6、LinearLayoutManager -> fill

fill方法如下所示,注释1处的recycleByLayoutState是回收部分入口;注释2处的layoutChunk是复用部分入口;我们在第二部分分析recycleByLayoutState,第三部分分析layoutChunk;

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
    、、、
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        、、、
        recycleByLayoutState(recycler, layoutState); //1
    }

    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        、、、
        layoutChunk(recycler, state, layoutState, layoutChunkResult); //2
        、、、
    }
}

二、回收部分

1、LinearLayoutManager -> recycleByLayoutState

注释1和注释2处分别表示从start和end回收,原理都是相同的,这里我们以注释1举例;

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    、、、
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace); //1
    } else {
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace); //2
    }
}

2、LinearLayoutManager -> recycleViewsFromEnd

如注释1所示,又会调用recycleChildren进行回收;

private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
    、、、
    if (mShouldReverseLayout) {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // stop here
                recycleChildren(recycler, 0, i); //1
                return;
            }
        }
    }
}

3、LinearLayoutManager -> recycleChildren

如下所示,注释1处表示又会调用removeAndRecycleViewAt方法;

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
    、、、
    if (endIndex > startIndex) {
        for (int i = endIndex - 1; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler); //1
        }
    }
    、、、
}

4、LayoutManager -> removeAndRecycleViewAt

如下所示,又会调用recycler的recycleView方法;

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    removeViewAt(index);
    recycler.recycleView(view); //1
}

5、Recycler -> recycleView

如下所示,注释1处表示又会调用recycleViewHolderInternal;

public void recycleView(@NonNull View view) {
    、、、
    recycleViewHolderInternal(holder); //1
}

6-1 RecyclerView # Recycler

在分析recycleViewHolderInternal方法之前,我们先简单分析一下RecyclerView的两个内部类Recycler类和RecyclerViewPool;

RecyclerView # Recycler

Recycler如下所示,注释1和注释2是一级缓存,其实就是泛型为ViewHolder的ArrayList;注释3就是二级缓存,注释4处的mViewCacheMax表示二级缓存的最大容量为2;注释5就是四级缓存RecycledViewPool;注释6就是第三级缓存,也就是自定义缓存;

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

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); //3

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

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; 
    int mViewCacheMax = DEFAULT_CACHE_SIZE; //4

    RecycledViewPool mRecyclerPool; //5

    private ViewCacheExtension mViewCacheExtension; //6

    static final int DEFAULT_CACHE_SIZE = 2; //7
    、、、
}

RecyclerView # RecycledViewPool

RecycledViewPool如下所示,先看注释2处的内部类ScrapData,注释3处的mScrapHeap就是缓存ViewHolder的ArrayList,注释4处表示最大容量为5,即可以缓存ViewHolder的个数为5;注释5处mScrap是一个SparseArray,其泛型为内部类ScrapData,这表示什么意思呢?就是说RecyclerViewPool对每种类型的ViewHolder最大缓存个数为5,然后可以缓存多种ViewHolder类型;

public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5; //1

    static class ScrapData { //2
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); //3
        int mMaxScrap = DEFAULT_MAX_SCRAP; //4
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
    SparseArray<ScrapData> mScrap = new SparseArray<>(); //5
    、、、
}

6-2 Recycler -> recycleViewHolderInternal

在recycleViewHolderInternal方法中才是执行真正的回收操作,主要是使用第二级缓存mCachedViews和第四级缓存RecyclerViewPool;注释1、2处表示mCachedViews容量满了之后会调用recycleCachedViewAt方法,然后将size–;注释3处表示往mCachedViews添加;注释4表示往缓存池添加;

 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)) {
            // Retire oldest cached view
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0); //1
                cachedViewSize--; //2
            }
            、、、
            mCachedViews.add(targetCacheIndex, holder); //3
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true); //4
            recycled = true;
        }
    }
}

7、Recycler -> recycleCachedViewAt

recycleCachedViewAt如下所示,注释1、2表示将mCachedViews的viewHolder移除,然后添加到RecycledViewPool;

void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    addViewHolderToRecycledViewPool(viewHolder, true); //1
    mCachedViews.remove(cachedViewIndex); //2
}

8、Recycler -> addViewHolderToRecycledViewPool

如下所示,注释1表示将ViewHolder添加到RecyclerViewPool;到此回收完毕;

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    、、、
    getRecycledViewPool().putRecycledView(holder); //1
}

三、复用部分

1、LinearLayoutManager -> layoutChunk

接着第一部分的layoutChunk方法分析复用部分,如下所示,注释1表示又会调用LayoutState的next方法;

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler); //1
    、、、
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    }
    、、、
}

2、LayoutState -> next

next方法如下所示,注释1又调用了getViewForPosition方法;

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

3、Recycler -> getViewForPosition

getViewForPosition方法如下所示,注释1又会调用到注释2;

public View getViewForPosition(int position) {
    return getViewForPosition(position, false); //1
}

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

4、Recycler -> tryGetViewHolderForPositionByDeadline

tryGetViewHolderForPositionByDeadline方法就是真正去寻找缓存进行复用了,这个方法比较长,这里只截取主要部分的代码,如下所示;

注释1处表示从mChangedScrap中寻找;

注释2和注释3表示从mAttachedScrap和mCachedView中去寻找;

注释4表示从自定义缓存中去寻找;

注释5表示从RecyclerViewPool中去寻找;

注释6表示如果上边所有的缓存都没有的话,就调用我们重写的Adapter的onCreateViewHolder去创建;

注释7处表示获取到ViewHolder之后再去调用Adapter的onBindViewHolder进行数据绑定,所以现在我们应该明白为什么要在我们重写的Adapter中重写这两个方法了;

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
            boolean dryRun, long deadlineNs) {
    、、、
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position); //1
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); //2
        、、、
    }
    if (holder == null) {
        、、、
        // 2) Find from scrap/cache via stable ids, if exists
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun); //3                
        }
        if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not
            // know it.
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type); //4
            、、、
        }
        if (holder == null) { // fallback to pool

            holder = getRecycledViewPool().getRecycledView(type); //5
            、、、
        }
        if (holder == null) {
            、、、
            holder = mAdapter.createViewHolder(RecyclerView.this, type); //6
            、、、
        }
    }
    、、、
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // do not update unless we absolutely have to.
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        、、、
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);//7
    }
    、、、
    return holder;
}

四、onLayout入口

1、RecyclerView -> onLayout

前面三个部分我们是从onTouchEvent方法的MOVE分支中切入讨论的,这一部分我们从RecyclerView的布局方法onLayout方法切入分析;

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout(); //1
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

2、RecyclerView -> dispatchLayout

如下所示,可以看到RecyclerView的dispatchLayout方法中主要分为三步;这里分析一下注释2的第二步;

void dispatchLayout() {
    、、、
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1(); //1
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2(); //2
    } 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 {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3(); //3
}

3、LinearLayoutManager -> onLayoutChildren

onLayoutChildren方法比较长,如下我们只看关键部分,注释1处又会调用removeAndRecycleViewAt;注释3、4处的fill方法正是我们前面重点讨论的方法;我们这里重点看一下注释2;

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    、、、
    if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler); //1
            return;
        }
    }
    、、、
    detachAndScrapAttachedViews(recycler); //2
    、、、
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForStart;
        fill(recycler, mLayoutState, state, false); //3
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false); //4
    }
    、、、
}

4、LayoutManager -> detachAndScrapAttachedViews

如下所示,又会调用scrapOrRecycleView

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); //1
    }
}

5、LayoutManager -> scrapOrRecycleView

scrapOrRecycleView如下所示,注释1表示又会调用Recycler的scrapView

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    、、、
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        recycler.scrapView(view); //1
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

6、Recycler -> scrapView

scrapView如下所示,注释1和注释2处正是第一级缓存mAttachedScrap和mChangedScrap;至此我们RecyclerView的四级缓存大体流程也就全部分析完毕;

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); //1
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder); //2
    }
}

以上是关于android进阶篇02RecyclerView回收复用机制源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Android高级UI进阶RecyclerView 刷新列表数据的 notifyDataSetChanged() 为什么是昂贵的?

Android 高级进阶(源码剖析篇)

Android进阶之通用RecyclerView适配器打造方法

Android进阶之通用RecyclerView适配器打造方法

我的Android进阶之旅强烈推荐 一种优雅的方式实现RecyclerView条目多类型

android studio中使用recyclerview小白篇