RecyclerView緩存復(fù)用原理

RecyclerView緩存復(fù)用機(jī)制

來到RecyclerView的Adapter代碼中:

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
        Log.i("minfo", "onCreateViewHolder");
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        ((TextView)holder.textView).setText(datas.get(position));
        Log.i("minfo", "onBindViewHolder");
    }

在onCreateViewHolder方法和onBindViewHolder方法打印,不斷滑動(dòng)recyclerView,會(huì)發(fā)現(xiàn)一個(gè)現(xiàn)象就是剛開始都會(huì)調(diào)兩個(gè)方法,但是調(diào)用一定次數(shù)之后,就只會(huì)調(diào)onBindViewHolder方法了。那么這個(gè)滑動(dòng)過程中,就是在不斷地回收復(fù)用。

首先要明白,RecyclerView緩存復(fù)用的是什么?緩存復(fù)用的是ViewHolder,ViewHolder是itemView的封裝類。

Recycler 是 RecyclerView 的內(nèi)部類,也是這套復(fù)用機(jī)制的核心,顯然 Recycler 的主要成員變量也都是用來緩存和復(fù)用 ViewHolder 的:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
 
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
    
    RecycledViewPool mRecyclerPool;
    
    private ViewCacheExtension mViewCacheExtension;
}
復(fù)用機(jī)制

在滑動(dòng)過程中,recyclerView不斷在復(fù)用ViewHolder來顯示item,那么就從recyclerView的滑動(dòng)事件入手源碼,然后步步深入:

 RecyclerView.onLayout(...)
-> RecyclerView.dispatchLayout()    
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 類型為 LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 為例
-> LinearLayoutManager.fill(...) // The magic functions :) 填充給定的布局,注釋很自信的說這個(gè)方法很獨(dú)立,稍微改動(dòng)就能作為幫助類的一個(gè)公開方法,程序員的快樂就是這么樸實(shí)無華。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循環(huán)調(diào)用,每次調(diào)用填充一個(gè) ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler) 
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通過 Recycler 獲取指定位置的 ItemView    
-> Recycler.getViewForPosition(int, boolean) // 調(diào)用下面方法獲取 ViewHolder,并返回上面需要的 viewHolder.itemView 
-> Recycler.tryGetViewHolderForPositionByDeadline(...)

最終調(diào)用 tryGetViewHolderForPositionByDeadline方法來獲取ViewHolder,這個(gè)方法里面是復(fù)用的核心,方法部分代碼:

ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 從 mChangedScrap 里面去獲取 ViewHolder,動(dòng)畫相關(guān)
        holder = getChangedScrapViewForPosition(position);
    }
    
    if (holder == null) {
        // mAttachedScrap、 mHiddenViews、mCachedViews 獲取 ViewHolder
        //    這個(gè) mHiddenViews 是用來做動(dòng)畫期間的復(fù)用
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    
    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 如果 Adapter 的 hasStableIds 方法返回為 true
        //    優(yōu)先通過 ViewType 和 ItemId 兩個(gè)條件從 mAttachedScrap 和 mCachedViews 尋找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }
      
        if (holder == null && mViewCacheExtension != null) {
            //從自定義緩存獲取
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }
    
    if (holder == null) {
        //從 RecycledViewPool 獲取 ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }
  
    if (holder == null) {
        // 如果四級緩存里都沒有,那就再調(diào)用createViewHolder創(chuàng)建ViewHolder
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
}

結(jié)合RecyclerView類中的源碼及注釋可知,Recycler會(huì)依次從mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中嘗試獲取指定位置或ID的ViewHolder對象以供重用,如果全都獲取不到則直接重新創(chuàng)建。

緩存機(jī)制
總共有四級緩存,按照優(yōu)先級分:
  • 一級緩存:

一級緩存的代碼:

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

scrap是用來保存被rv移除掉但最近又馬上要使用的緩存,比如說rv中自帶item的動(dòng)畫效果。

mChangedScrap/mAttachedScrap

稍微仔細(xì)看的話就能發(fā)現(xiàn)scrap緩存有兩個(gè)成員mChangedScrap和mAttachedScrap,它們保存的對象有些不一樣,一般調(diào)用adapter的notifyItemRangeChanged被移除的viewholder會(huì)保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder會(huì)被保存到mAttachedScrap中。用來緩存還在屏幕內(nèi)的viewholder。

二級緩存:mCachedViews ,用來緩存移除屏幕之外的 ViewHolder,默認(rèn)情況下緩存容量是 2,可以通過 setViewCacheSize 方法來改變緩存的容量大小。如果 mCachedViews 的容量已滿,則會(huì)根據(jù) 先進(jìn)先出 的規(guī)則移除舊 ViewHolder,再將viewHolder添加進(jìn)RecycledViewPool中。

三級緩存:ViewCacheExtension ,開發(fā)給用戶的自定義擴(kuò)展緩存,需要用戶自己管理 View 的創(chuàng)建和緩存。通過提供給開發(fā)者抽象方法來自己實(shí)現(xiàn)緩存。

四級緩存:RecycledViewPool ,ViewHolder 緩存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 時(shí),會(huì)從cachedView緩存中移除,然后將移除的 ViewHolder 存入RecyclerViewPool 中。

將緩存viewHolder添加到recyclerViewPool中,根據(jù)itemtype獲取對應(yīng)的ScrapData數(shù)據(jù),里面存儲(chǔ)著該itemType的緩存集合。RecycledViewPool默認(rèn)大小為5,當(dāng)大于了最大容量,就不再緩存。

可以通過以下方式修改RecycledViewPool的緩存大小:

RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max);
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
 
//根據(jù)itemtype獲取對應(yīng)的ScrapData數(shù)據(jù)
private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }
mCachedViews二級緩存與四級緩存RecycledViewPool的區(qū)別:

cacheView緩存的viewHolder帶有數(shù)據(jù),而四級緩存已經(jīng)清空了數(shù)據(jù),只是緩存了viewHolder,從 RecyclerViewPool 中取出來的 ViewHolder 需要重新執(zhí)行 bindViewHolder才能使用。所以二級緩存的復(fù)用速度會(huì)比四級緩存的復(fù)用速度更高。

mCachedViews只會(huì)緩存2個(gè)viewholder,就是屏幕滑出去的上面一個(gè)item和下面一個(gè)item。當(dāng)上面一個(gè)item剛滑出去或者下面一個(gè)item剛滑出去,緩存在mCachedViews里面,內(nèi)容不會(huì)被清空。此時(shí)再把這個(gè)item滑入屏幕內(nèi),不會(huì)走onBindViewHolder,因?yàn)閿?shù)據(jù)還在。而隨著往上或往下繼續(xù)滑動(dòng)更多,移除屏幕的item超過2個(gè),mCachedViews緩存容量不足,就會(huì)將滑出屏幕的viewholder往recyclerViewPool中緩存。而把這些更多的item滑回屏幕時(shí),這些緩存的viewholder數(shù)據(jù)已經(jīng)被清掉,所以會(huì)調(diào)用onBindViewHolder方法重設(shè)數(shù)據(jù)

繪圖總結(jié):

參考:
RecyclerView 的緩存復(fù)用機(jī)制
https://www.bilibili.com/video/BV1Yb4y1R7xn?p=3&spm_id_from=pageDriver&vd_source=40c24e77b23dc2e50de2b7c87c6fed59

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

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

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