天天看點

RecyclerView緩存解釋

1、一級緩存 mAttachedScrap:

緩存螢幕中可見範圍的ViewHolder。他還可以儲存item自帶的動畫效果,因為這些viewholer資料上是沒有改變的,隻是位置改變而已,是以放置到scrap最為合适。

final ArrayList mAttachedScrap = new ArrayList<>();

2、二級緩存 mCachedViews:

緩存滑動時即将與RecyclerView分離的ViewHolder,按子View的position或id緩存,預設最多存放2個。

final ArrayList mCachedViews = new ArrayList();

static final int DEFAULT_CACHE_SIZE = 2;

3、三級緩存 mViewCacheExtension :

開發者自行實作的緩存,擴充緩存,也可能是谷歌自留的。

4、四級緩存 mRecyclerPool:

ViewHolder緩存池,本質上是一個SparseArray,其中key是ViewType(int類型),value存放的是 ArrayList< ViewHolder>,預設每個ArrayList中最多存放5個ViewHolder。是通過item的type來找holder的,找到之後需要重新綁定資料。

holder = getRecycledViewPool().getRecycledView(type);

  public ViewHolder getRecycledView(int viewType) {
        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()) {
                //擷取到holder後傳回,并從集合中移除這個holder。
                    return scrapHeap.remove(i);
                }
            }
        }
        return null;
    }

 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<>();
      

getViewForPosition()即是是從RecyclerView的回收機制實作類Recycler中擷取合适的View

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

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
//根據傳入的position擷取ViewHolder 
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ---------省略----------
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    //預布局 屬于特殊情況 從mChangedScrap中擷取ViewHolder
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    if (holder == null) {
        //1、嘗試從mAttachedScrap中擷取ViewHolder,此時擷取的是螢幕中可見範圍中的ViewHolder
        //2、mAttachedScrap緩存中沒有的話,繼續從mCachedViews嘗試擷取ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
     ----------省略----------
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        ---------省略----------
        final int type = mAdapter.getItemViewType(offsetPosition);
        //如果Adapter中聲明了Id,嘗試從id中擷取,這裡不屬于緩存
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
        }
        if (holder == null && mViewCacheExtension != null) {
            3、從自定義緩存mViewCacheExtension中嘗試擷取ViewHolder,該緩存需要開發者實作
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { // fallback to pool
            //4、從緩存池mRecyclerPool中嘗試擷取ViewHolder
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                //如果擷取成功,會重置ViewHolder狀态,是以需要重新執行Adapter#onBindViewHolder綁定資料
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        if (holder == null) {
            ---------省略----------
          //5、若以上緩存中都沒有找到對應的ViewHolder,最終會調用Adapter中的onCreateViewHolder建立一個
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }

    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //6、如果需要綁定資料,會調用Adapter#onBindViewHolder來綁定資料
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ----------省略----------
    return holder;
}
      

總結一下上述流程:通過mAttachedScrap、mCachedViews及mViewCacheExtension擷取的ViewHolder不需要重新建立布局及綁定資料;通過緩存池mRecyclerPool擷取的ViewHolder不需要重新建立布局,但是需要重新綁定資料;如果上述緩存中都沒有擷取到目标ViewHolder,那麼就會回調Adapter#onCreateViewHolder建立布局,以及回調Adapter#onBindViewHolder來綁定資料。

再說說自定義緩存

Recycler本身已經設定了好幾級緩存了,為什麼還要留個接口讓開發者去自行實作緩存呢?關于這一點,談一談我的了解:來看看Recycler中的其他緩存,其中mAttachedScrap用來處理可見螢幕的緩存;mCachedViews裡存儲的資料雖然是根據position來緩存,但是裡面的資料随時可能會被替換的;再來看mRecyclerPool,mRecyclerPool裡按viewType去存儲ArrayList< ViewHolder>,是以mRecyclerPool并不能按position去存儲ViewHolder,而且從mRecyclerPool取出的View每次都要去走Adapter#onBindViewHolder去重新綁定資料。假如我現在需要在一個特定的位置(比如position=0位置)一直展示某個View,且裡面的内容是不變的,那麼最好的情況就是在特定位置時,既不需要每次重新建立View,也不需要每次都去重新綁定資料,上面的幾種緩存顯然都是不适用的,這種情況該怎麼辦呢?可以通過自定義緩存ViewCacheExtension實作上述需求。

ViewCacheExtension适用場景:ViewHolder位置固定、内容固定、數量有限時使用。

比如在position=0時展示的是一個廣告,位置不變,内容不變​

ListView和RecycleView

ListView和RecyclerView緩存機制基本一緻:

ListView隻有兩級緩存 mActiveViews和mScrapView。

1). mActiveViews和mAttachedScrap功能相似,意義在于快速重用螢幕上可見的清單項ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意義在于緩存離開螢幕的ItemView,目的是讓即将進入螢幕的ItemView重用。

lietView使用mScrapView緩存是不用建立view,但是需重新綁定資料的。getView方法中會從convertView中判斷有沒有view。沒有再建立新的,有的話複用該view。

3). RecyclerView的優勢在于a.mCacheViews的使用,可以做到螢幕外的清單項ItemView進入螢幕内時也無須bindView快速重用;b.mRecyclerPool可以供多個RecyclerView共同使用,在特定場景下,如viewpaper+多個清單頁下有優勢.客觀來說,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善

作者:·帝釋天