RecyclerView源碼分析之二

寫在前面:今天2021年2月12日,農(nóng)歷正月初一,2021年春節(jié)。一個人在上海沒有回家。從家里下了幾個水餃吃了,然后來公司了,學會習,下午再跑個5公里。拍張照紀念一下。

我的樣子.jpg

進入正題

上篇文章:RecyclerView源碼分析之一

源碼版本:androidx1.1.0

本文要旨:RecyclerView的回收和復用機制。

我們就以RecyclerView最簡單的使用方式為例進行分析。使用線性布局,方向為豎直方向,布局從上到下。

先引用這篇文章中RecyclerView 源碼分析(三) - RecyclerView的緩存機制對RecyclerView緩存的總結(jié),感覺非常清晰非常好。

緩存級別 實際變量 含義
一級緩存 mAttachedScrapmChangedScrap 優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了Adapter的notifyItemChanged方法。
二級緩存 mCachedViews 默認大小為2,在滾動的時候會存儲一些ViewHolder。
三級緩存 ViewCacheExtension 這個是自定義緩存,一般用不到。
四級緩存 RecyclerViewPool 根據(jù)ViewType來緩存ViewHolder,每個ViewType的數(shù)組大小為5,可以動態(tài)的改變。

public final class Recycler {

    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    //根據(jù)postion來尋找可復用的ViewHolder
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    //自定義的緩存擴展,一般不會定義吧。
    private ViewCacheExtension mViewCacheExtension;
    //根據(jù)type來尋找來尋找可復用的ViewHolder
    RecycledViewPool mRecyclerPool;
    

    static final int DEFAULT_CACHE_SIZE = 2;

}

public static class RecycledViewPool {

    private static final int DEFAULT_MAX_SCRAP = 5;

}

還有一篇文章中,說的感覺更清晰易懂。

1、AttachedScrap: 存放可見、不需要重新綁定的ViewHolder
2、CachedViews: 存放不可見、不需要重新綁定的ViewHoler
3、ViewCacheExtension: 自定義緩存(存放不可見、不需要重新綁定)
4、RecyclerPool: 存放不可見、需要重新綁定的ViewHolder

【Android】自定義無限循環(huán)的LayoutManager

RecyclerView的回收

在布局的時候的回收

在RecyclerView的布局的第二步dispatchLayoutStep2中,會調(diào)用LinearLayoutManager的onLayoutChildren方法。

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    //...
    //注釋1處,如果當前存在attach到RecyclerView的View,則臨時detach,后面再復用。
    detachAndScrapAttachedViews(recycler);
    //...
}

注釋1處,如果當前存在attach到RecyclerView的View,則臨時detach,后面再復用。

RecyclerView.LayoutManager的detachAndScrapAttachedViews方法。

/**
  * 臨時detach和廢棄當前attached的子Views。Views會被加入指定的Recycler。Recycler可能會在其他先前被回收的Views之前優(yōu)先使用`scrap views`。
  *
  * @param recycler Recycler to scrap views into
  */
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
    final int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View v = getChildAt(i);
        //調(diào)用scrapOrRecycleView方法
        scrapOrRecycleView(recycler, i, v);
    }
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        return;
    }
    //如果ViewHolder的數(shù)據(jù)無效了 && ViewHolder對應的數(shù)據(jù)沒有被從data set中移除 && 并且沒有固定的id
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        //注釋1處,移除View
        removeViewAt(index);
        //注釋2處,回收ViewHolder
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        //注釋3處,detachView
        detachViewAt(index);
        //注釋4處,回收View
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

注釋1處,移除View,這里會真正把子View從RecyclerView中移除。
注釋2處,回收ViewHolder,正常滾動的時候也會調(diào)用Recycler的recycleViewHolderInternal方法,后面分析滾動回收的時候再看。

注釋3處,臨時將一個View從當前窗口detach,然后復用的時候重新attach一下就行,效率非常高。

Recycler的scrapView方法。

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    /*
     * 1. 如果ViewHolder對應的數(shù)據(jù)從data set中移除了,或者ViewHolder對應的數(shù)據(jù)無效了,需要重新綁定新的數(shù)據(jù)。
     * 2. 或者ViewHolder對應的數(shù)據(jù)沒有更新
     * 3. 或者canReuseUpdatedViewHolder(holder)方法為true,這方法默認是true
     */
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        //如果ViewHolder的數(shù)據(jù)無效了 && ViewHolder對應的數(shù)據(jù)沒有被從data set中移除 && 并且沒有固定的id
        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());
        }
        //調(diào)用setScrapContainer方法,第二個參數(shù)是isChangeScrap的意思,這里為false
        holder.setScrapContainer(this, false);
        //將ViewHolder回收到mAttachedScrap
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        //調(diào)用setScrapContainer方法,第二個參數(shù)是isChangeScrap的意思,這里為true
        holder.setScrapContainer(this, true);
        //將ViewHolder回收到mChangedScrap
        mChangedScrap.add(holder);
    }
}

這里會根據(jù)ViewHolder的狀態(tài),決定將其加入到mAttachedScrap還是mChangedScrap。mAttachedScrap還是mChangedScrap的區(qū)別引用這篇文章中的敘述RecyclerView 源碼分析(三) - RecyclerView的緩存機制

這是優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了Adapter的notifyItemChanged方法。

在滾動時候的回收

在RecyclerView滾動的時候,會調(diào)用LinearLayoutManager的fill方法。fill方法內(nèi)部會發(fā)生View的回收和復用。

/**
 * @param recycler        當前關聯(lián)到RecyclerView的recycler。
 * @param layoutState     該如何填充可用空間的配置信息。
 * @param state           Context passed by the RecyclerView to control scroll steps.
 * @param stopOnFocusable 如果為true的話,遇到第一個可獲取焦點的View則停止填充。
 * @return 返回添加的像素。
 */
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;
    //...
    //注釋0處,如果layoutState.mScrollingOffset不為SCROLLING_OFFSET_NaN的話,調(diào)用recycleByLayoutState方法,從這個方法名,我們可以看出來,這是一個回收View的方法。
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        //...
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        //注釋1處,獲取并添加子View,然后測量、布局子View并將分割線考慮在內(nèi)。
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        //條件滿足的話,跳出循環(huán)
        if (layoutChunkResult.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        //注釋2處,這里也會判斷是否要調(diào)用recycleByLayoutState方法。
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    
    return start - layoutState.mAvailable;
}

注釋0處和注釋2處,如果layoutState.mScrollingOffset不為SCROLLING_OFFSET_NaN的話,調(diào)用recycleByLayoutState方法,從這個方法名可以看出來,這是一個回收View的方法。接下來我們看看其中的細節(jié)。

LinearLayoutManager的recycleByLayoutState方法。

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    if (!layoutState.mRecycle || layoutState.mInfinite) {
        return;
    }
    int scrollingOffset = layoutState.mScrollingOffset;
    int noRecycleSpace = layoutState.mNoRecycleSpace;
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        //注釋1處
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        //注釋2處
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}

注釋1處,以默認豎直方向的LinearLayoutManager來說,就是手指從上向下滑動的時候,回收從下面滑出屏幕的View。

注釋2處,以默認豎直方向的LinearLayoutManager來說,就是手指從下向上滑動的時候,回收從上面滑出屏幕的View。

LinearLayoutManager的recycleViewsFromEnd方法。

private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
    final int childCount = getChildCount();
    if (scrollingOffset < 0) {
        return;
    }
    final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
    if (mShouldReverseLayout) {
        //...
    } else {
        //從后向前遍歷
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                //注釋1處,遇到第一個View的top坐標小于limit就停止。然后調(diào)用recycleChildren方法,回收從childCount - 1到i之間的所有View。
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    }
}

注釋1處,遇到第一個View的top坐標小于limit就停止。然后調(diào)用recycleChildren方法,回收從childCount - 1i之間的所有View。

LinearLayoutManager的recycleViewsFromStart方法。

private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
    if (scrollingOffset < 0) {
        return;
    }
    // ignore padding, ViewGroup may not clip children.
    final int limit = scrollingOffset - noRecycleSpace;
    final int childCount = getChildCount();
    if (mShouldReverseLayout) {
        //...
    } else {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                //注釋1處,遇到第一個View的bottom坐標大于limit就停止。然后調(diào)用recycleChildren方法,回收從0到i之間的所有View。
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    }
}

注釋1處,遇到第一個View的bottom坐標大于limit就停止。然后調(diào)用recycleChildren方法,回收從0i之間的所有View。

接下來我們看看recycleChildren方法。

LinearLayoutManager的recycleChildren方法。

/**
 * 回收指定索引之間的子View。
 *
 * @param startIndex 包括
 * @param endIndex   不包括
 */
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
    if (startIndex == endIndex) {
        return;
    }
    if (endIndex > startIndex) {
        for (int i = endIndex - 1; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    } else {
        for (int i = startIndex; i > endIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    }
}

調(diào)用父類RecyclerView.LayoutManager的removeAndRecycleViewAt方法。

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    //注釋1處,調(diào)用RecyclerView.LayoutManager的removeViewAt方法移除View。
    removeViewAt(index);
    //注釋2處,回收View。
    recycler.recycleView(view);
}

RecyclerView.LayoutManager的removeAndRecycleViewAt方法。

public void removeViewAt(int index) {
    final View child = getChildAt(index);
    if (child != null) {
        //這里調(diào)用RecyclerView的mChildHelper進行移除。
        mChildHelper.removeViewAt(index);
    }
}

在RecyclerView的initChildrenHelper方法中,初始化了mChildHelper,并傳入了一個ChildHelper.Callback對象。最終就是調(diào)用這個ChildHelper.Callback對象的removeViewAt方法來移除的。

@Override
public void removeViewAt(int index) {
    final View child = RecyclerView.this.getChildAt(index);
    if (child != null) {
        dispatchChildDetached(child);

        // Clear any android.view.animation.Animation that may prevent the item from
        // detaching when being removed. If a child is re-added before the
        // lazy detach occurs, it will receive invalid attach/detach sequencing.
        child.clearAnimation();
    }
    //注釋1處,RecyclerView自身調(diào)用ViewGroup的removeViewAt方法來移除子View。
    RecyclerView.this.removeViewAt(index);
}
                

注釋1處,RecyclerView自身調(diào)用ViewGroup的removeViewAt方法來移除子View。

然后我們回到RecyclerView.LayoutManager的removeAndRecycleViewAt方法的注釋2處,回收移除掉的View。

recycler.recycleView(view);

Recycler的recycleView方法。

public void recycleView(@NonNull View view) {
    // This public recycle method tries to make view recycle-able since layout manager
    // intended to recycle this view (e.g. even if it is in scrap or change cache)
   //獲取ViewHolder
    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
        //清除FLAG_TMP_DETACHED臨時分離的標記
        removeDetachedView(view, false);
    }
    //注釋1處,將ViewHolder從mChangedScrap或者mAttachedScrap中移除。
    if (holder.isScrap()) {
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
    //注釋2處,調(diào)用recycleViewHolderInternal方法。
    recycleViewHolderInternal(holder);
           
    if (mItemAnimator != null && !holder.isRecyclable()) {
        mItemAnimator.endAnimation(holder);
    }
}

注釋1處,如果ViewHolder在mChangedScrap中或者mAttachedScrap中,則將ViewHolder從mChangedScrap或者mAttachedScrap中移除。也就是說滑出屏幕的ViewHolder不會緩存在mChangedScrap中或者mAttachedScrap中。

注釋2處,調(diào)用recycleViewHolderInternal方法。

Recycler的recycleViewHolderInternal方法。

void recycleViewHolderInternal(ViewHolder holder) {
    //這里判斷了在mChangedScrap中或者mAttachedScrap中的ViewHolder不會被回收,沒有被移除的子View對應的ViewHolder也不能被回收。
    if (holder.isScrap() || holder.itemView.getParent() != null) {
        throw new IllegalArgumentException(
                "Scrapped or attached views may not be recycled. isScrap:"
                    + holder.isScrap() + " isAttached:"
                    + (holder.itemView.getParent() != null) + exceptionLabel());
    }
    //臨時從屏幕上detach的ViewHolder也不能被回收。
    if (holder.isTmpDetached()) {
        throw new IllegalArgumentException("Tmp detached view should be removed "
                + "from RecyclerView before it can be recycled: " + holder
                + exceptionLabel());
    }
    //不需要回收的ViewHolder
    if (holder.shouldIgnore()) {
        throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                + " should first call stopIgnoringView(view) before calling recycle."
                + exceptionLabel());
    }
    //是否阻止回收
    final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
    //是否強制回收
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;
    if (DEBUG && mCachedViews.contains(holder)) {
        throw new IllegalArgumentException("cached view received recycle internal? "
                + holder + exceptionLabel());
    }
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            //注釋1處,如果mCachedView緩存已達上限,從mCachedViews中移除最老的ViewHolder到RecyclerViewPool中
            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;
            }
            //注釋2處,將要回收的ViewHolder加入mCachedViews
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            //注釋3處,沒有成功緩存到mCachedViews,則加入到RecycledViewPool中。
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        // NOTE: A view can fail to be recycled when it is scrolled off while an animation
        // runs. In this case, the item is eventually recycled by
        // ItemAnimatorRestoreListener#onAnimationFinished.

        // TODO: consider cancelling an animation when an item is removed scrollBy,
        // to return it to the pool faster
        if (DEBUG) {
            Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                    + "re-visit here. We are still removing it from animation lists"
                    + exceptionLabel());
        }
    }
    // even if the holder is not removed, we still call this method so that it is removed
    // from view holder lists.
    //跟動畫相關的ViewHolder也從mViewInfoStore移除。
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

注釋1處,如果mCachedView緩存已達上限,從mCachedViews中移除最老的ViewHolder到RecyclerViewPool中。

Recycler的recycleCachedViewAt方法。

void recycleCachedViewAt(int cachedViewIndex) {
    //從mCachedViews中獲取ViewHolder
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    //加入到RecycledViewPool
    addViewHolderToRecycledViewPool(viewHolder, true);
    //mCachedViews移除對應位置上的ViewHolder
    mCachedViews.remove(cachedViewIndex);
}

注釋2處,先將要回收的ViewHolder加入mCachedViews中。

注釋3處,沒有成功緩存到mCachedViews,則加入到RecycledViewPool中。

我們接下來看看Recycler的addViewHolderToRecycledViewPool方法。

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    //...
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    //注釋1處,調(diào)用RecycledViewPool的putRecycledView方法。
    getRecycledViewPool().putRecycledView(holder);
}

RecycledViewPool的putRecycledView方法。

/**
 * 將廢棄的ViewHolder加入到緩存池。
 * <p>
 * 如果ViewHolder對應的ViewType類型的緩存池已經(jīng)滿了,就直接將ViewHolder丟棄。
 *
 * @param scrap ViewHolder to be added to the pool.
 */
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    //根據(jù)viewType獲取緩存池,就是一個ArrayList<ViewHolder>
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    //緩存池已經(jīng)滿了,不回收,直接return。
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        return;
    }
    //ViewHolder做一下清除操作。
    scrap.resetInternal();
    //緩存ViewHolder
    scrapHeap.add(scrap);
}

RecyclerView的復用

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    //注釋1處,獲取子View,可能是從緩存中或者新創(chuàng)建的View。后面分析緩存相關的點的時候再看。
    View view = layoutState.next(recycler);
    if (view == null) {
        //注釋2處,如果獲取到的子View為null,將LayoutChunkResult的mFinished置為true,用于跳出循環(huán)然后直接return。
        result.mFinished = true;
        return;
    }
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            //注釋3處
            addView(view);  
        } else {
            //注釋4處
            addView(view, 0);
        }
    } 
    //...
   
}

上篇文章我們分析了layoutChunk方法。在注釋1處,獲取子View,可能是從緩存中或者新創(chuàng)建的View。我們現(xiàn)在就看一看其中細節(jié)。

View view = layoutState.next(recycler);

LayoutState的next方法。

View next(RecyclerView.Recycler recycler) {
    //如果需要布局特殊的View是,可以將mScrapList賦值為不為null的值,然后就會從mScrapList獲取View,這里可以忽略
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //注釋1處,調(diào)用Recycler的getViewForPosition方法獲取View
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

注釋1處,正常調(diào)用Recycler的getViewForPosition方法獲取View。RecyclerView.Recycler就是處理RecyclerView的緩存復用相關邏輯的類。

@NonNull
public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

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

tryGetViewHolderForPositionByDeadline方法里面的邏輯就是先從緩存里面取ViewHolder進行復用,如果沒有可復用的ViewHolder,則進行創(chuàng)建。

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    if (position < 0 || position >= mState.getItemCount()) {
        throw new IndexOutOfBoundsException("Invalid item position " + position
                + "(" + position + "). Item count:" + mState.getItemCount()
                + exceptionLabel());
    }
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    //注釋0處,從mChangedScrap中獲取ViewHolder,注意只有LinearLayoutManager沒有開啟自動測量的情況下,mState.isPreLayout()才可能是是true,我們這里就忽略mState.isPreLayout()為true的情況。
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    //注釋1處,從mAttachedScrap、mHiddenViews、mCachedViews中獲取ViewHolder
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            //是否可以在該位置上使用holder,
            if (!validateViewHolderForOffsetPosition(holder)) {
                // 如果holder不能被使用,回收之。
                if (!dryRun) {
                    // we would like to recycle this but need to make sure it is not used by
                    // animation logic etc.
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    //回收
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    //holder為null,繼續(xù)進行查找
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                    + "position " + position + "(offset:" + offsetPosition + ")."
                    + "state:" + mState.getItemCount() + exceptionLabel());
        }
       
        //獲取ViewType
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        //注釋2處,通過stable ids 從mAttachedScrap、mCachedViews中獲取ViewHolder。
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        //注釋3處,從我們自定義的緩存擴展mViewCacheExtension中獲取ViewHolder
        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);
            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());
                }
            }
        }
        if (holder == null) { // fallback to pool
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                        + position + ") fetching from shared pool");
            }
            //注釋4處,從RecycledViewPool中獲取ViewHolder
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        if (holder == null) {
            long start = getNanoTime();
            if (deadlineNs != FOREVER_NS
                    && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                // abort - we have a deadline we can't meet
                return null;
            }
            //注釋5處,從適配器中創(chuàng)建一個新的ViewHolder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            if (ALLOW_THREAD_GAP_WORK) {
                // only bother finding nested RV if prefetching
                RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                if (innerView != null) {
                    holder.mNestedRecyclerView = new WeakReference<>(innerView);
                }
            }

            long end = getNanoTime();
            mRecyclerPool.factorInCreateTime(type, end - start);
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
            }
        }
    }

    // This is very ugly but the only place we can grab this information
    // before the View is rebound and returned to the LayoutManager for post layout ops.
    // We don't need this in pre-layout since the VH is not updated by the LM.
    if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
        holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
        if (mState.mRunSimpleAnimations) {
            int changeFlags = ItemAnimator
                    .buildAdapterChangeFlagsForAnimations(holder);
            changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
            final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                    holder, changeFlags, holder.getUnmodifiedPayloads());
            recordAnimationInfoIfBouncedHiddenView(holder, info);
        }
    }

    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()) {
        if (DEBUG && holder.isRemoved()) {
            throw new IllegalStateException("Removed holder should be bound and it should"
                    + " come here only in pre-layout. Holder: " + holder
                    + exceptionLabel());
        }
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //注釋6處,綁定ViewHolder
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
    //最終返回ViewHolder對象。
    return holder;
}

注釋0處,從mChangedScrap中獲取ViewHolder。

if (mState.isPreLayout()) {//開啟了預布局
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}

注意只有LinearLayoutManager沒有開啟自動測量的情況下,mState.isPreLayout()才可能是是true,并不是常規(guī)行為,我們這里就忽略這種情況。

注釋1處,從mAttachedScrap、mHiddenViews、mCachedViews中獲取ViewHolder。傳入的dryRun為false。這個是用來標記找到一個ViewHolder以后,是否從緩存它的數(shù)據(jù)集中移除掉。

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

RecyclerView.Recycler的getScrapOrHiddenOrCachedHolderForPosition方法。

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {

    final int scrapCount = mAttachedScrap.size();
    //注釋1處,先從mAttachedScrap查找
    for (int i = 0; i < scrapCount; i++) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                    && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                //添加標志位FLAG_RETURNED_FROM_SCRAP
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
             return holder;
        }
    }

    if (!dryRun) {
        //注釋2處,從mHiddenViews中查找View,然后根據(jù)View獲取ViewHolder
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
         // This View is good to be used. We just need to unhide, detach and move to the
         // scrap list.
         final ViewHolder vh = getChildViewHolderInt(view);
         //將獲取到的View從mHiddenViews移除。
         mChildHelper.unhide(view);
         int layoutIndex = mChildHelper.indexOfChild(view);
         if (layoutIndex == RecyclerView.NO_POSITION) {
             throw new IllegalStateException("layout index should not be -1 after "
                        + "unhiding a view:" + vh + exceptionLabel());
         }
         mChildHelper.detachViewFromParent(layoutIndex);
         //注釋3處,將View對應的ViewHolder加入到mAttachedScrap或者mChangedScrap中
         scrapView(view);
         //添加標記位,多了一個FLAG_BOUNCED_FROM_HIDDEN_LIST的標記位
         vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                    | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
         return vh;
        }
    }

    //注釋4處,從mCachedViews中獲取
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // invalid view holders may be in cache if adapter has stable ids as they can be
        // retrieved via getScrapOrCachedViewForId
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                //找到ViewHolder后,從mCachedViews中移除。
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            return holder;
        }
    }
    return null;
}

注釋1處,先從mAttachedScrap查找。

注釋2處,從mHiddenViews中查找View,然后根據(jù)View獲取ViewHolder。獲取到的View會從mHiddenViews移除,然后注釋3處,將View對應的ViewHolder加入到mAttachedScrap或者mChangedScrap中。
這里順便提一下,mHiddenViews存儲的是在動畫過程中需要隱藏的Views。

注釋4處,從mCachedViews中獲取。

然后我們回到RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋2處。通過stable ids從mAttachedScrap、mCachedViews中獲取ViewHolder。

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋3處,從我們自定義的緩存擴展mViewCacheExtension中獲取ViewHolder,這個自定義的ViewCacheExtension就先忽略了。
(你自定義過ViewCacheExtension嗎?,沒有。你自定義過?我也沒有?正常人誰自定義ViewCacheExtension呀。是,自定義ViewCacheExtension的叫正常人嗎?哈哈,純屬搞笑。)

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋4處,從RecycledViewPool中獲取ViewHolder。

holder = getRecycledViewPool().getRecycledView(type);

RecycledView.RecycledViewPool類,這里提一下,RecycledViewPool可以用來在多個RecyclerView之間來復用View。

/**
 * RecycledViewPool可以讓你在多個RecyclerViews之間復用Views。 lets you share Views between multiple RecyclerViews.
 * <p>
 * 如果你想在多個RecyclerView之間復用Views,可以創(chuàng)建一個RecycledViewPool實例,然后調(diào)用 RecyclerView的 setRecycledViewPool(RecycledViewPool) 方法設置。
 * <p>
 * 如果你不提供RecycledViewPool實例,RecyclerView會自動創(chuàng)建一個。
 */
public static class RecycledViewPool {
    //每個viewType類型的ViewHolder默認緩存5個
    private static final int DEFAULT_MAX_SCRAP = 5;
    
    
    static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
    
    //使用SparseArray緩存不ViewType類型的ViewHolder,SparseArray的key就是ViewType類
    SparseArray<ScrapData> mScrap = new SparseArray<>();


    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        //注釋1處,先根據(jù)ViewType類獲取ScrapData,然后從ScrapData獲取ViewHolder。
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            for (int i = scrapHeap.size() - 1; i >= 0; i--) {
                if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                    return scrapHeap.remove(i);
                }
            }
        }
        return null;
    }
    //...

}

注釋1處,從RecycledViewPool中獲取ViewHolder,先根據(jù)ViewType類獲取ScrapData,然后從ScrapData獲取ViewHolder。

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋5處,無法從緩存中獲取ViewHolder,就使用適配器中創(chuàng)建一個新的ViewHolder。

RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋6處,綁定ViewHolder

bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);

Recycler的tryBindViewHolderByDeadline方法。

private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
                int position, long deadlineNs) {
    holder.mOwnerRecyclerView = RecyclerView.this;
    final int viewType = holder.getItemViewType();
    long startBindNs = getNanoTime();
    if (deadlineNs != FOREVER_NS
            && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
        // abort - we have a deadline we can't meet
        return false;
    }
    //適配器綁定ViewHolder,內(nèi)部最終會調(diào)用我們平常寫的onBindViewHolder方法。
    mAdapter.bindViewHolder(holder, offsetPosition);
    long endBindNs = getNanoTime();
    mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
    attachAccessibilityDelegateOnBind(holder);
    if (mState.isPreLayout()) {
        holder.mPreLayoutPosition = position;
    }
    return true;
}

結(jié)尾:關于RecyclerView的動畫相關的內(nèi)容,并沒有進行分析。主要還是關注RecyclerView的回收和復用的大致邏輯。

參考鏈接:

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容