RecyclerView#Adapter#notifyDataSetChanged方法后,為何還會新建ViewHolder?

環(huán)境

android sdk版本: 30

依賴:

implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.2.1"

案例分析:

RecyclerView寬高固定;LayoutManagerLienarLayoutManager,vertical方向;數(shù)據(jù)20條,足以鋪滿整個屏幕。

現(xiàn)象:
①先創(chuàng)建Adapter,設(shè)置20條數(shù)據(jù)。
②調(diào)用RecyclerView#Adapter#notifyDataSetChanged方法后,當(dāng)前頁面中只有5個ViewHolder復(fù)用,其余的ViewHolder會走Adapter#createViewHolder方法創(chuàng)建新的ViewHolder。

原理:

為了搞清楚原理,我們先看一下,剛進(jìn)入頁面時,RecyclerView#Adapter#onCreateViewHolder方法的調(diào)用棧。

RecyclerView#Adapter#onCreateViewHolder方法的調(diào)用棧

RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 668行
LinearLayoutManager#fill(): 1591行
LinearLayoutManager#layoutChunk(): 1631行
LinearLayoutManager#LayoutState#next(): 2330行
RecyclerView#Recycler#getViewForPosition(int position): 6296行
RecyclerView#Recycler#getViewForPosition(position, boolean dryRun): 6300行
RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs): 6416行
RecyclerView#Adapter#createViewHolder(@NonNull ViewGroup parent, int viewType): 7295行
RecyclerView#Adapter#onCreateViewHolder(@NonNull ViewGroup parent, int viewType)

其核心是RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline方法,它主要有兩個作用,一個是獲取ViewHolder;另一個給ViewHolder綁定數(shù)據(jù)。

獲取ViewHolder是有順序的,會先嘗試從各級緩存里面去獲取,會依次從Recycler scrap、cache、RecycledViewPool中獲取,如果都獲取不到,就直接創(chuàng)建一個ViewHolder。

RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline

Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly.
獲取給定位置的ViewHolder。會依次從Recycler scrap、cache、RecycledViewPool中獲取,如果都獲取不到,就直接創(chuàng)建一個ViewHolder

核心:獲取viewHolder;給viewHolder綁定數(shù)據(jù)。
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    // 如果需要預(yù)先布局,就嘗試從mChangedScrap中去獲取。
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        ...
    }
    // 1) Find by position from scrap/hidden list/cache
    // 嘗試依次從mAttachedScrap、mChildHelper的mHiddenViews、mCachedViews中去獲取可復(fù)用的ViewHolder
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        // 校驗holder是否有效,無效就清除vh
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                ...
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        ...
        // 獲取這個位置對應(yīng)的數(shù)據(jù)類型,通過重寫的Adapter#getItemViewType方法。
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        // 如果設(shè)置了stable ids,就根據(jù)id依次從mAttachedScrap、mCachedViews中查找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
            ...
        }
        // 嘗試從mViewCacheExtension中獲取VH
        if (holder == null && mViewCacheExtension != null) {
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
                ...
            }
        }
        // 嘗試從 RecycledViewPool 中獲取
        if (holder == null) { // fallback to pool
            ...
            holder = getRecycledViewPool().getRecycledView(type);
            ...
        }
        // 調(diào)用RecyclerView#Adapter#onCreateViewHolder生成ViewHolder
        if (holder == null) {
            ...
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            ...           
        }
    }
    ...
    // 給viewholder綁定數(shù)據(jù)
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        ...
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {       
        ...
        // 給viewholder綁定數(shù)據(jù)
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    // 給holder.itemView設(shè)置RecyclerView#LayoutParams。并將其相互綁定。
    ...
    return holder;
}

實例分析。

我們這里調(diào)用RecyclerView#Adapter#notifyDataSetChanged方法后,既有復(fù)用的ViewHolder,也有新建的ViewHolder。復(fù)用的ViewHolder來自于哪里?為什么是5個?為什么還要新建ViewHolder

帶著這些問題,我們debug下我們的場景,看下ViewHolder的來源。

核心在于調(diào)用RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline方法,關(guān)鍵在于下面這段代碼:

holder = getRecycledViewPool().getRecycledView(type);

我們知道,RecycledViewPool中是以viewType來存放不同的ViewHolder的,每個type最多存放五個。

所以我們在RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline中,從RecycledViewPool中最多能找到五個可復(fù)用的ViewHolder,其余的只能走新建ViewHolder流程了。

RecycledViewPool

先來看下RecycledViewPool的說明:

RecycledViewPool lets you share Views between multiple RecyclerViews.
If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool and use setRecycledViewPool(RecyclerView.RecycledViewPool).
RecyclerView automatically creates a pool for itself if you don't provide one.

大意是RecycledViewPool可以讓你在多個recyclerview之間共享視圖。
如果你想在RecyclerViews中回收視圖,可以創(chuàng)建一個RecycledViewPool的實例并使用setRecycledViewPool(RecyclerView.RecycledViewPool)。
如果你不提供一個RecycledViewPool實例,那么RecyclerView會自動為自己創(chuàng)建一個。

我們看下RecyclerView#Recycler#getRecycledViewPool方法:確實是自動創(chuàng)建了一個。

RecycledViewPool getRecycledViewPool() {
    if (mRecyclerPool == null) {
        mRecyclerPool = new RecycledViewPool();
    }
    return mRecyclerPool;
}

再看下RecycledViewPool的結(jié)構(gòu):

public static class RecycledViewPool {
    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<ScrapData> mScrap = new SparseArray<>();
    ...
}

如上所示,里面有一個SparseArray<ScrapData>類型的變量mScrap,用來存儲不同類型的ViewHolder。ScrapData數(shù)據(jù)中包含ArrayList<ViewHolder>類型變量mScrapHeap,用來存放具體的ViewHolder,它的最大容量是5(DEFAULT_MAX_SCRAP)。

RecycledViewPool中的數(shù)據(jù)何時添加的

本例中RecycledViewPool中的數(shù)據(jù)是從哪里添加的呢?

本例中,向ScrapData#mScrapHeap添加ViewHolder數(shù)據(jù)的調(diào)用鏈如下:

RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 633行
RecyclerView#LayoutManager#detachAndScrapAttachedViews(): 9493行
RecyclerView#LayoutManager#scrapOrRecycleView(): 9508行
RecyclerView#Recycler#recycleViewHolderInternal(): 6671行
RecyclerView#Recycler#addViewHolderToRecycledViewPool(): 6723行
RecyclerView#RecyclerViewPool#putRecycledView(ViewHolder scrap): 5931行
scrapHeap.add(scrap);

核心是LinearLayoutManager#onLayoutChildren()方法中,如下的這段代碼:

detachAndScrapAttachedViews(recycler);

也就是說,在調(diào)用RecyclerView#Adapter#notifyDataSetChanged方法后,會觸發(fā)繪制流程。在Linearlayout#layoutChildren方法中,會先對ViewHolder進(jìn)行緩存,然后會對ViewHolder進(jìn)行復(fù)用。

相關(guān)資料

demo-AdapterOnCreateViewHolderTestActivity

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

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

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