轉載請以連接形式標明出處: 本文出自:103style的博客android
源碼 base on androidx.recyclerview:recyclerview:1.1.0-alpha05
git
代碼說明中已省略了暫不須要關注的代碼.github
測試demo地址緩存
更新於:2019/10/29 15.04 以前因爲用Android P模擬器測試因此一直顯示 mAttachedScrap 中的數據一直爲空。真機顯示正常,爲屏幕內當前顯示item的條數。bash
RecyclerView
子項的流程獲取的流程大體爲: RecyclerView.onLayout(...)
→ LayoutManager.onLayoutChildren(...)
→ LayoutState.next(...)
。ide
源代碼調用以下:佈局
RecyclerView
的 onLayout(...)
方法:測試
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
}
void dispatchLayout() {
dispatchLayoutStep2();
}
private void dispatchLayoutStep2() {
mLayout.onLayoutChildren(mRecycler, mState);
}
複製代碼
RecyclerView
設置的 LayoutManager
的 onLayoutChildren(...)
方法: 以 LinearLayoutManager
爲例:動畫
public void onLayoutChildren(...) {
fill(recycler, mLayoutState, state, false);
}
int fill(...) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
void layoutChunk(...) {
View view = layoutState.next(recycler);
}
List<RecyclerView.ViewHolder> mScrapList;
static class LayoutState {
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
}
複製代碼
這裏主要看 recycler.getViewForPosition(mCurrentPosition)
,mScrapList
是有動畫效果時預佈局保存的ViewHolder
。ui
Recycler中的 mAttachedScrap、mChangedScrap、mCachedViews
ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
static final int DEFAULT_CACHE_SIZE = 2;
複製代碼
mAttachedScrap:用於緩存顯示在屏幕上並符合對應規則的 ViewHolder
,模擬器的話會出現 裏面的數據時空的 。 mChangedScrap:用於緩存顯示在屏幕上不符合 mAttachedScrap
規則的 ViewHolder
。
Recycler.scrapView(View view)
, 在 調用 attchView
和 detachView
的時候調用。
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
mChangedScrap.add(holder);
}
}
複製代碼
mCachedViews:保存滑動過程當中從可見到不可見的ViewHolder
,大小爲DEFAULT_CACHE_SIZE = 2
,而且本來數據信息都在,因此能夠直接添加到 RecyclerView
中顯示,不須要再次從新 onBindViewHolder()
,在數據多時滑動過程當中會變成 3
。
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void unused(ViewHolder viewHolder) {
mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}
};
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
//從可見變成不可見
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
if (record.preInfo == null) {
// 相似出現消失但發生在不一樣的佈局通道之間。
// 當佈局管理器使用自動測量時,可能會發生這種狀況
callback.unused(viewHolder);
}
}
}
}
複製代碼
ChildHelper.mHiddenViews
mHiddenViews
中保留在添加或刪除數據或其餘操做,保存在屏幕內可見的 帶動畫子視圖。
List<View> mHiddenViews = new ArrayList<View>();
複製代碼
mHiddenViews
是在 RecycleView
中 addAnimatingView(ViewHolder viewHolder)
的時候,根據特定狀況添加的。
private void addAnimatingView(ViewHolder viewHolder) {
final boolean alreadyParented = view.getParent() == this;
if (viewHolder.isTmpDetached()) {
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
複製代碼
RecycledViewPool.mScrap
每一個 viewType
的緩存最可能是 DEFAULT_MAX_SCRAP = 5
個,viewType
即 RecyclerView
中 子view 的樣式,即 adapter
中的 getItemViewType
獲取的值。
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
複製代碼
添加和獲取的方法:
DEFAULT_MAX_SCRAP = 5
時,則丟棄。 存數據即爲 釋放 mCachedViews
的數據則會放入緩存池。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()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
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;
}
scrapHeap.add(scrap);
}
複製代碼
RecyclerView.Recycler
的 getViewForPosition(int position)
方法:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
複製代碼
RecyclerView.Recycler
的tryGetViewHolderForPositionByDeadline(...)
方法獲取 holder
。 也就是對應的緩存機制的主要邏輯,找到即返回。
mState.isPreLayout()
,是否是執行動畫的預佈局,是的話則嘗試從 mChangedScrap
中獲取。mAttachedScrap
、mHiddenViews
、mCachedViews
獲取。mAdapter.hasStableIds()
以及 mViewCacheExtension!=null
判斷是否從用戶設置的相關邏輯中獲取。RecycledViewPool
中的 mScrap
的 ScrapData
中獲取。mAdapter.createViewHolder
從新建立。ViewHolder tryGetViewHolderForPositionByDeadline(...) {
...
if (mState.isPreLayout()) {
//若是是預佈局,即執行簡單的動畫 就會嘗試這裏
holder = getChangedScrapViewForPosition(position);
}
// 從 scrap/hidden list/cache 嘗試獲取
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
...
final int type = mAdapter.getItemViewType(offsetPosition);
//經過 adapter.setHasStableIds設置 默認爲false
if (mAdapter.hasStableIds()) {}
//mViewCacheExtension 經過 RecyclerView.setViewCacheExtension設置 默認爲null
if (holder == null && mViewCacheExtension != null) {
//經過自定義的緩存機制區獲取
}
if (holder == null) {
// 嘗試從RecycledViewPool中去獲取holder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
//調用Adapter.createViewHolder建立holder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
...
return holder;
}
複製代碼
若是是預佈局,則嘗試從 mChangedScrap 中獲取:
ViewHolder getChangedScrapViewForPosition(int position) {
// 若是是預佈局,請檢查 mChangedScrap 是否徹底匹配。
for (int i = 0; i < mChangedScrap.size(); i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// 默認 false
if (mAdapter.hasStableIds()) {
...
}
return null;
}
複製代碼
嘗試從緩存中 mAttachedScrap、mHiddenViews、mCachedViews獲取: RecyclerView.Recycler
的getScrapOrHiddenOrCachedHolderForPosition(...)
獲取 holder
.
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// 嘗試從mAttachedScrap獲取
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//dryRun: getViewForPosition(position, false)傳入的是false
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
...
return vh;
}
}
// 從一級緩存中嘗試獲取
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// 若是適配器具備穩定的ID,則無效的視圖持有者可能在緩存中,
// 由於能夠經過getScrapOrCachedViewForId檢索它們
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
複製代碼
從緩存池RecycledViewPool中的 mScrap 的 ScrapData 中獲取ViewHolder,並從緩存池中移除: RecycledViewPool
的 getRecycledView(int viewType)
:
SparseArray<ScrapData> mScrap = new SparseArray<>();
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()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
複製代碼
demo主要是添加了 兩種類型 的數據,默認添加 50 條數據,設置一屏幕最多顯示 8 條數據, 而後經過 滑動,添加,刪除 數據,再經過反射來獲取對應緩存的數據。
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 1
TestAdapter: onBindViewHolder position = 0
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 2
TestAdapter: onBindViewHolder position = 1
...
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 8
TestAdapter: onBindViewHolder position = 7
MainActivity1572332870607: ChildHelper.mHiddenViews =0
MainActivity1572332870607: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332870607: Recycler.mViewCacheMax =2
MainActivity1572332870607: Recycler.mAttachedScrap.size() = 0
MainActivity1572332870607: Recycler.mChangedScrap.size() = 0
MainActivity1572332870607: Recycler.mCachedViews.size() = 0
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
MainActivity1572332870611: ChildHelper.mHiddenViews =0
MainActivity1572332870611: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332870611: Recycler.mViewCacheMax =2
MainActivity1572332870611: Recycler.mAttachedScrap.size() = 8
MainActivity1572332870611: Recycler.mChangedScrap.size() = 0
MainActivity1572332870611: Recycler.mCachedViews.size() = 0
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
複製代碼
mViewCacheMax
變成了 3
, Recycler.mCachedViews
緩存了 position
爲 0
和 9
對應位置的數據。即屏幕上下都有不可見的數據是,緩存的是上下各一條不可見視圖的數據。TestAdapter: onCreateViewHolder onCreateViewHolderCount = 9
TestAdapter: onBindViewHolder position = 8
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 10
TestAdapter: onBindViewHolder position = 9
MainActivity1572332997091: ChildHelper.mHiddenViews =0
MainActivity1572332997091: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332997091: Recycler.mViewCacheMax =3
MainActivity1572332997091: Recycler.mAttachedScrap.size() = 8
MainActivity1572332997091: Recycler.mChangedScrap.size() = 0
MainActivity1572332997091: Recycler.mCachedViews.size() = 2
MainActivity: mCachedViews:---0 = ViewHolder{1bcb007 position=0 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{c735183 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
複製代碼
咱們看到沒有調用 onCreateViewHolder, Recycler 中的 mViewCacheMax
仍是 3
, Recycler.mCachedViews
緩存了 position
爲 9
和 8
對應位置的數據。即滑動到頂端緩存的是 下方不可見的兩條數據。
MainActivity1572333360514: ChildHelper.mHiddenViews =0
MainActivity1572333360514: LayoutManager.LayoutState.mScrapList =0
MainActivity1572333360514: Recycler.mViewCacheMax =3
MainActivity1572333360514: Recycler.mAttachedScrap.size() = 8
MainActivity1572333360514: Recycler.mChangedScrap.size() = 0
MainActivity1572333360514: Recycler.mCachedViews.size() = 2
MainActivity: mCachedViews:---0 = ViewHolder{c735183 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{9f16d94 position=8 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
複製代碼
咱們看到調用了 2
次 onCreateViewHolder, Recycler 中的 mViewCacheMax
仍是 3
, Recycler.mCachedViews
緩存了 position
爲 2
、3
和 12
對應位置的的 三條
數據。 RecycledViewPool
中緩存了一條 viewType = 2
的數據,即上圖有兩列的 item.
TestAdapter: onBindViewHolder position = 1
MainActivity1572333690740: ChildHelper.mHiddenViews =1
MainActivity: mHiddenViews:---0 = android.widget.LinearLayout{9d33071 V.E...... ......I. 0,2268-1440,2588}
MainActivity1572333690740: LayoutManager.LayoutState.mScrapList =0
MainActivity1572333690740: Recycler.mViewCacheMax =3
MainActivity1572333690740: Recycler.mAttachedScrap.size() = 8
MainActivity1572333690740: Recycler.mChangedScrap.size() = 0
MainActivity1572333690740: Recycler.mCachedViews.size() = 3
MainActivity: mCachedViews:---0 = ViewHolder{15ffd7e position=11 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{c735183 position=10 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---2 = ViewHolder{9f16d94 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
複製代碼
咱們發現 ChildHelper.mHiddenViews
中多了條數據。只有看到添加或刪除動畫時,mHiddenViews中才會有數據 。
TestAdapter: onBindViewHolder position = 1
MainActivity1571565149149: ChildHelper.mHiddenViews =1
MainActivity: mHiddenViews:---0 = android.widget.LinearLayout{c44a637 V.E...... ......I. 0,1589-1080,1813}
MainActivity1571565149149: LayoutManager.LayoutState.mScrapList =0
MainActivity1571565149149: Recycler.mViewCacheMax =3
MainActivity1571565149149: Recycler.mAttachedScrap.size() = 8
MainActivity1571565149149: Recycler.mChangedScrap.size() = 0
MainActivity1571565149149: Recycler.mCachedViews.size() = 3
MainActivity: mCachedViews:---0 = ViewHolder{3d52e3f position=11 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{e8dba5e position=10 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---2 = ViewHolder{6647799 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 1
MainActivity: mScrapHeap:---0 = ViewHolder{996130c position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}
複製代碼
經過上文咱們能夠了解到:
獲取 子View 最終是調用了 LayoutManager 中 LayoutState 的 next(RecyclerView.Recycler recycler)。 而後判斷是不是顯示動畫效果,是的話則直接從 mScrapList 獲取,不然經過緩存機制去獲取。
緩存數據相關的變量分別表明的意思: mAttachedScrap:用於緩存顯示在屏幕上並符合對應規則的 ViewHolder,測試發如今Android P 的模擬器上 裏面的數據時空的。 mChangedScrap:用於緩存顯示在屏幕上不符合 mAttachedScrap 規則的 ViewHolder。 mCachedViews:保存滑動過程當中從可見到不可見的 ViewHolder,大小爲DEFAULT_CACHE_SIZE = 2
,而且本來數據信息都在,因此能夠直接添加到 RecyclerView 中顯示,不須要再次從新 onBindViewHolder()
,在數據多時,滑動過程當中 mCachedViews
的大小會變成 3
。 mHiddenViews 中保留在添加或刪除數據或其餘操做,保存在屏幕內可見的 帶動畫子視圖。
RecyclerView 的緩存機制大體是:
mChangedScrap
中獲取。mAttachedScrap
、mHiddenViews
、mCachedViews
獲取。mAdapter.hasStableIds()
以及 mViewCacheExtension!=null
判斷是否從用戶自定義設置的相關邏輯中獲取。RecycledViewPool
中 mScrap
的 ScrapData
中獲取,每種樣式最多緩存 5個。mAdapter.createViewHolder
建立新的。若是有寫錯的地方,請幫忙評論指正,感謝!
若是以爲不錯的話,請幫忙點個讚唄。
以上
掃描下面的二維碼,關注個人公衆號 Android1024, 點關注,不迷路。