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的緩存機制做了補強和完善
。
作者:·帝釋天