RecyclerView 是一個強大又靈活的 View,能夠用有限的 View 來展現大量的數據。今天咱們來看下 RecyclerView 內部是經過怎樣的緩存複用機制來實現這一功能的。java
Recycler 是 RecyclerView 的內部類,也是這套複用機制的核心,顯然 Recycler 的主要成員變量也都是用來緩存和複用 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;
}
複製代碼
這些緩存集合能夠分爲 4 個級別,按優先級從高到底爲:數組
一級緩存:mAttachedScrap 和 mChangedScrap ,用來緩存還在屏幕內的 ViewHolder緩存
二級緩存:mCachedViews ,用來緩存移除屏幕以外的 ViewHolder,默認狀況下緩存容量是 2,能夠經過 setViewCacheSize 方法來改變緩存的容量大小。若是 mCachedViews 的容量已滿,則會根據 FIFO 的規則移除舊 ViewHolderide
三級緩存:ViewCacheExtension ,開發給用戶的自定義擴展緩存,須要用戶本身管理 View 的建立和緩存。我的感受這個拓展脫離了 Adapter.createViewHolder 使用的話會形成 View 建立和數據綁定和其它代碼太分散,不利於維護,使用場景不多僅作了解佈局
/* * Note that, Recycler never sends Views to this method to be cached. It is developers * responsibility to decide whether they want to keep their Views in this custom cache or let * the default recycling policy handle it. */
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(...);
}
複製代碼
四級緩存:RecycledViewPool ,ViewHolder 緩存池,在有限的 mCachedViews 中若是存不下新的 ViewHolder 時,就會把 ViewHolder 存入 RecyclerViewPool 中。動畫
接下來咱們看下這四級緩存是怎麼工做的this
RecyclerView 做爲一個 「平平無奇」 的 View,子 View 的排列和佈局固然是從 onLayout 入手了,調用鏈:spa
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 :) 填充給定的佈局,註釋很自信的說這個方法很獨立,稍微改動就能做爲幫助類的一個公開方法,程序員的快樂就是這麼樸實無華。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循環調用,每次調用填充一個 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,經過 Recycler 獲取指定位置的 ItemView
-> Recycler.getViewForPosition(int, boolean) // 調用下面方法獲取 ViewHolder,並返回上面須要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(...) // 終於找到你,還好沒放棄~
複製代碼
能夠看出最終調用 tryGetViewHolderForPositionByDeadline ,來看看這個方法是怎麼拿到相應位置上的 ViewHolder :code
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 0) 預佈局從 mChangedScrap 裏面去獲取 ViewHolder,動畫相關
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// 1) 分別從 mAttachedScrap、 mHiddenViews、mCachedViews 獲取 ViewHolder
// 這個 mHiddenViews 是用來作動畫期間的複用
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 若是 Adapter 的 hasStableIds 方法返回爲 true
// 優先經過 ViewType 和 ItemId 兩個條件從 mAttachedScrap 和 mCachedViews 尋找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not know it.
// 3) 從自定義緩存獲取,別問,問就是別用
View view = mViewCacheExtension getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
// 4) 從 RecycledViewPool 獲取 ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 緩存全取過了,沒有,那隻好 create 一個咯
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
// This is very ugly but the only place we can grab this information
// 大半夜看到這句註釋的時候笑出聲!像極了我寫出醜代碼時的無奈。
// 在這後面有一些刷新 ViewHolder 信息的代碼,放這很醜,但又只能放這,爲了能走到這,前面有多少 if (holder == null)
}
複製代碼
分析完複用的部分,接下來再看一下 ViewHolder 存入緩存的部分
所謂的緩存,就是看一下是怎麼樣往前面提到的四級緩存添加數據的
若是調用了 Adapter 的 notifyXXX 方法,會從新回調到 LayoutManager 的onLayoutChildren 方法裏面, 而在 onLayoutChildren 方法裏面,會將屏幕上全部的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。
// 調用鏈
LinearLayoutManager.onLayoutChildren(...)
-> LayoutManager.detachAndScrapAttachedViews(recycler)
-> LayoutManager.scrapOrRecycleView(..., view)
-> Recycler.scrapView(view);
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 後面會講,緩存到 mCacheViews 和 RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 緩存到 scrap
recycler.scrapView(view);
}
}
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 標記爲移除或失效的 || 徹底沒有改變 || item 無動畫或動畫不復用
mAttachedScrap.add(holder);
} else {
mChangedScrap.add(holder);
}
}
複製代碼
其實還有一種狀況會調用到 scrapView , 從 mHiddenViews 得到一個 ViewHolder 的話(發生在支持動畫的操做),會先將這個 ViewHolder 從 mHiddenViews 數組裏面移除,而後調用:
Recycler.tryGetViewHolderForPositionByDeadline(...)
-> Recycler.getScrapOrHiddenOrCachedHolderForPosition(...)
-> Recycler.scrapView(view)
複製代碼
這兩級緩存的代碼都在 Recycler 的這個方法裏:
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
if(mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 1. mCacheViews 滿了,最先加入的不要了放 RecyclerViewPool
recycleCachedViewAt(0);
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 2. 不能放進 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(holder, true);
}
}
}
// Recycles a cached view and removes the view from the list
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
複製代碼
在這咱們知道 recycleViewHolderInternal 會把 ViewHolder 緩存到 mCacheViews ,而不知足加到 mCacheViews 的會緩存到 RecycledViewPool 。那又是何時調用的 recycleViewHolderInternal 呢?有如下三種狀況:
到這裏,RecyclerView 的緩存複用機制就分析完了,總結一下:
本文源碼基於:recyclerview:1.2.0-alpha03