轉載自瓊珶和予
RecyclerView 源碼分析(三)RecyclerView的緩存機制
- RecyclerView的緩存機制
-
- 1. 概述
-
- (1) 四級緩存
- (2) ViewHolder的幾個狀态值
- (3) ChildHelper的mHiddenViews
- 2. 複用
-
- (1) 通過Position方式來擷取ViewHolder
-
- mChangedScrap裡面去擷取ViewHolder
- mAttachedScrap、 mHiddenViews、mCachedViews擷取ViewHolder
- (2) 通過viewType方式來擷取ViewHolder
-
- A. 通過id來尋找ViewHolder
- B. 從RecyclerViewPool裡面擷取ViewHolder
- C. 調用Adapter的onCreateViewHolder方法建立一個新的ViewHolder
- 3. 回收
-
- (1) scrap數組
- (2) mCacheViews數組
- (3) mHiddenViews數組
- (4) RecyclerViewPool
- (5) 為什麼hasStableIds方法傳回true會提高效率呢?
-
- A. 複用方面
- B. 回收方面
- 4. 總結
RecyclerView的緩存機制
RecyclerView作為一個非常惹人愛的控件,有一部分的功勞歸于它優秀的緩存機制。RecyclerView的緩存機制屬于RecyclerView的核心部分,同時也是比較難的部分。盡管緩存機制那麼難,但是還是不能抵擋得住我們的好奇心。今天我們來看看它的神奇之處。
本文參考資料:
RecyclerView緩存原理,有圖有真相
【進階】RecyclerView源碼解析(二)——緩存機制
深入 RecyclerView 源碼探究四:回收複用和動畫
手摸手第二彈,可視化 RecyclerView 緩存機制
RecyclerView 源碼分析(一) - RecyclerView的三大流程
由于本文跟本系列的前兩篇文章都有關聯,是以為了便于了解,可以去看作者本系列的前兩篇文章。
注意,本文所有的代碼都來自于27.1.1。
1. 概述
在正式分析源碼之前,我先對緩存機制做一個概述,同時也會對一些概念進行統一解釋,這些對後面的分析有很大的幫助,因為如果不了解這些概念的話,後面容易看得雨裡霧裡的。
(1) 四級緩存
首先,我将RecyclerView的緩存分為四級,可能有的人将它分為三級,這些看個人的了解。這裡統一說明一下每級緩存的意思。
緩存級别 | 實際變量 | 含義 |
---|---|---|
一級緩存 | mAttachedScrap和mChangedScrap | 這是 比如說調用了Adapter的notifyItemChanged方法。可能有人對這兩個緩存還是有點疑惑,不要急,待會會詳細的解釋。 |
二級緩存 | mCachedViews | 。 |
三級緩存 | ViewCacheExtension | 自定義緩存,通常用不到,在本文中先忽略 |
四級緩存 | RecyclerViewPool | 。 |
如上表,統一的解釋了每個緩存的含義和作用。在這裡,我再來對其中的幾個緩存做一個詳細的解釋。
- mAttachedScrap:上表中說,
。它表示存儲的是目前還在螢幕中ViewHolder
。比如說,實際上是從螢幕上分離出來的ViewHolder,但是又即将添加到螢幕上去的ViewHolder
,這樣效率就會非常的高。RecyclerView上下滑動,滑出一個新的Item,此時會重新調用LayoutManager的onLayoutChildren方法,進而會将螢幕上所有的ViewHolder先scrap掉(含義就是廢棄掉),添加到mAttachedScrap裡面去,然後在重新布局每個ItemView時,會從優先mAttachedScrap裡面擷取
。這個過程不會重新onBindViewHolder
- mCachedViews:
。是以預設大小為2,不過通常是3,3由預設的大小2 + 預取的個數1
。通常來說,可以通過RecyclerView的setItemViewCacheSize方法設定大小,但是這個不包括預取大小;預取大小通過LayoutManager的setItemPrefetchEnabled方法來控制。在RecyclerView在首次加載時,mCachedViews的size為3(這裡以LinearLayoutManager的垂直布局為例)
(2) ViewHolder的幾個狀态值
我們在看RecyclerView的源碼時,可能到處都能看到調用
ViewHolder的isInvalid、isRemoved、isBound、isTmpDetached、isScrap和isUpdated這幾個方法
。這裡我統一的解釋一下。
方法名 | 對應的Flag | 含義或者狀态設定的時機 |
---|---|---|
isInvalid | FLAG_INVALID | 表示目前ViewHolder是否已經失效。通常來說, 。 |
isRemoved | FLAG_REMOVED | 表示目前的ViewHolder是否被移除。通常來說, 。 |
isBound | FLAG_BOUND | 表示目前ViewHolder是否已經調用了onBindViewHolder。 |
isTmpDetached | FLAG_TMP_DETACHED | 表示目前的ItemView是否從RecyclerView(即父View)detach掉。通常來說 。這裡又多出來一個mHideViews,待會我會詳細的解釋它是什麼。 |
isScrap | 無Flag來表示該狀态,用mScrapContainer是否為null來判斷 | 表示是否在mAttachedScrap或者mChangedScrap數組裡面,進而表示目前ViewHolder是否被廢棄。 |
isUpdated | FLAG_UPDATE | 表示目前ViewHolder是否已經更新。通常來說, |
(3) ChildHelper的mHiddenViews
在四級緩存中,我們并沒有将mHiddenViews算入其中。因為mHiddenViews隻在動畫期間才會有元素,當動畫結束了,自然就清空了。是以mHiddenViews并不算入4級緩存中。
這裡還有一個問題,就是上面在解釋mChangedScrap時,也在說,當調用Adapter的notifyItemChanged方法,會将更新了的ViewHolder反放入mChangedScrap數組裡面。那到底是放入mChangedScrap還是mHiddenViews呢?同時可能有人對mChangedScrap和mAttachedScrap有疑問,這裡我做一個統一的解釋:
首先,如果調用了Adapter的notifyItemChanged方法,會重新回調到LayoutManager的onLayoutChildren方法裡面,而在onLayoutChildren方法裡面,會将螢幕上所有的ViewHolder回收到mAttachedScrap和mChangedScrap。
這個過程就是将ViewHolder分别放到mAttachedScrap和mChangedScrap,而什麼條件下放在mAttachedScrap,什麼條件放在mChangedScrap,這個就是他們倆的差別。
接下來我們來看一段代碼,就能厘清mAttachedScrap和mChangedScrap的差別了
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);
}
}
可能很多人初次看到這方法時,會非常的懵逼,我也是如此。今天我們就來看看scrapView這個方法。scrapView這個方法根本的目的就是,判斷ViewHolder的flag狀态,進而來決定是放入mAttachedScrap還是mChangedScrap。從上面的代碼,我們得出:
- mAttachedScrap裡面放的是兩種狀态的ViewHolder:1.被同時标記為remove和invalid;2.完全沒有改變的ViewHolder。這裡還有第三個判斷,這個跟RecyclerView的ItemAnimator有關,如果ItemAnimator為空或者ItemAnimator的canReuseUpdatedViewHolder方法為true,也會放入到mAttachedScrap。
。那正常情況下,什麼情況傳回為true呢?從SimpleItemAnimator的源碼可以看出來,當ViewHolder的isInvalid方法傳回為true時,會放入到 mAttachedScrap裡面。也就是說,如果ViewHolder失效了,也會放到mAttachedScrap裡面
- 那麼mChangedScrap裡面放什麼類型flag的ViewHolder呢?當然是ViewHolder的isUpdated方法傳回為true時,會放入到mChangedScrap裡面去。是以,調用Adapter的notifyItemChanged方法時,并且RecyclerView的ItemAnimator不為空,會放入到mChangedScrap裡面。
了解了mAttachedScrap和mChangedScrap的差別之後,接下我們來看Scrap數組和mHiddenViews的差別。
mHiddenViews隻存放動畫的ViewHolder,動畫結束了自然就清空了。之是以存在 mHiddenViews這個數組,我猜測是存在動畫期間,進行複用的可能性,此時就可以在mHiddenViews進行複用了。而Scrap數組跟mHiddenViews兩者完全不沖突,是以存在一個ViewHolder同時在Scrap數組和mHiddenViews的可能性。但是這并不影響,因為在動畫結束時,會從mHiddenViews裡面移除。
本文在分析RecyclerView的緩存機制時,打算從兩個大方面入手:1.複用;2.回收。
我們先來看看複用的部分邏輯,因為隻有了解了RecyclerView究竟是如何複用的,對回收才能更加明白。
2. 複用
RecyclerView對ViewHolder的複用,我們得從LayoutState的next方法開始。
LayoutManager在布局itemView時,需要擷取一個ViewHolder對象,就是通過這個方法來擷取,具體的複用邏輯也是在這個方面開始調用的
。我們來看看:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
next方法裡面其實也沒做什麼事,就是調用RecyclerView的getViewForPosition方法來擷取一個View的。而getViewForPosition方法最終會調用到RecyclerView的tryGetViewHolderForPositionByDeadline方法。是以,RecyclerView真正複用的核心就在這個方法,我們今天來詳細的分析一下這個方法。
(1) 通過Position方式來擷取ViewHolder
通過這種方式來擷取優先級比較高,因為每個ViewHolder還沒被改變
,通常在這種情況下,都是某一個ItemView對應的ViewHolder被更新導緻的,是以
在螢幕上其他的ViewHolder,可以快速對應原來的ItemView
。我們來看看相關的源碼。

if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
如上的代碼分為兩步:
- 從mChangedScrap裡面去擷取ViewHolder,這裡面存儲的是更新的ViewHolder。
- 分别mAttachedScrap、 mHiddenViews、mCachedViews擷取ViewHolder
mChangedScrap裡面去擷取ViewHolder
我們來簡單的分析一下這兩步。先來看看第一步。
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
如果目前是預布局階段,那麼就從mChangedScrap裡面去擷取ViewHolder。
那什麼階段是預布局階段呢?這裡我對預布局這個概念簡單的解釋。
預布局又可以稱之為preLayout,當目前的RecyclerView處于dispatchLayoutStep1階段時,稱之為預布局;dispatchLayoutStep2稱為真正布局的階段;dispatchLayoutStep3稱為postLayout階段。
同時要想真正開啟預布局,必須有ItemAnimator,并且每個RecyclerView對應的LayoutManager必須開啟預處理動畫。
是不是感覺聽了解釋之後更加的懵逼了?為了解釋一個概念,反而引出了更多的概念了?關于動畫的問題,不出意外,我會在下一篇文章分析,本文就不對動畫做過多的解釋了。在這裡,為了簡單,隻要RecyclerView處于dispatchLayoutStep1,我們就當做它處于預布局階段。
為什麼隻在預布局的時候才從mChangedScrap裡面去取呢?
首先,我們得知道mChangedScrap數組裡面放的是什麼類型的 ViewHolder。從前面的分析中,我們知道,隻有當ItemAnimator不為空,被changed的ViewHolder會放在mChangedScrap數組裡面。因為
change動畫前後相同位置上的ViewHolder是不同的,是以當預布局時,從mChangedScrap緩存裡面去,而正式布局時,不會從mChangedScrap緩存裡面去,這就保證了動畫前後相同位置上是不同的ViewHolder
。為什麼要保證動畫前後是不同的ViewHolder呢?這是RecyclerView動畫機制相關的知識,這裡就不詳細的解釋,後續有專門的文章來分析它,在這裡,我們隻需要記住,change動畫執行的有一個前提就是動畫前後是不同的ViewHolder。
然後,我們再來看看第二步。
mAttachedScrap、 mHiddenViews、mCachedViews擷取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
這一步了解起來比較容易,分别從mAttachedScrap、 mHiddenViews、mCachedViews擷取ViewHolder。但是我們需要的是,如果擷取的ViewHolder是無效的,得做一些清理操作,然後重新放入到緩存裡面,具體對應的緩存就是mCacheViews和RecyclerViewPool。recycleViewHolderInternal方法就是回收ViewHolder的方法,後面再分析回收相關的邏輯會重點分析這個方法,這裡就不進行追究了。
(2) 通過viewType方式來擷取ViewHolder
前面分析了通過Position的方式來擷取ViewHolder,這裡我們來分析一下第二種方式–ViewType。不過在這裡,我先對前面的方式做一個簡單的總結,RecyclerView通過Position來擷取ViewHolder,并不需要判斷ViewType是否合法,因為如果能夠通過Position來擷取ViewHolder,ViewType本身就是正确對應的。
而這裡通過ViewType來擷取ViewHolder表示,此時ViewHolder緩存的Position已經失效了。
ViewType方式來擷取ViewHolder的過程,我将它分為3步:
- 如果Adapter的hasStableIds方法傳回為true,優先通過ViewType和id兩個條件來尋找。如果沒有找到,那麼就進行第2步。
- 如果Adapter的hasStableIds方法傳回為false,在這種情況下,首先會在ViewCacheExtension裡面找,如果還沒有找到的話,最後會在RecyclerViewPool裡面來擷取ViewHolder。
- 如果以上的複用步驟都沒有找到合适的ViewHolder,最後就會調用Adapter的onCreateViewHolder方法來建立一個新的ViewHolder。
在這裡,我們需要注意的是,上面的第1步和第2步有前提條件,就是兩個都必須比較ViewType。接下來,我通過代碼簡單的分析一下每一步。
A. 通過id來尋找ViewHolder
通過id尋找合适的ViewHolder主要是通過調用getScrapOrCachedViewForId方法來實作的,我們簡單的看一下代碼:
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
而getScrapOrCachedViewForId方法本身沒有什麼分析的必要,就是分别從mAttachedScrap和mCachedViews數組尋找合适的ViewHolder。
B. 從RecyclerViewPool裡面擷取ViewHolder
ViewCacheExtension存在的情況是非常的少見,這裡為了簡單,就不展開了(實際上我也不懂!),是以這裡,我們直接來看RecyclerViewPool方式。
在這裡,我們需要了解RecyclerViewPool的數組結構。我們簡單的分析一下RecyclerViewPool這個類。
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<>();
在RecyclerViewPool的内部,使用SparseArray來存儲每個ViewType對應的ViewHolder數組,其中每個數組的最大size為5。這個資料結構是不是非常簡單呢?
簡單的了解了RecyclerViewPool的資料結構,接下來我們來看看複用的相關的代碼:
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
相信這段代碼不用我來分析吧,表達的意思非常簡單。
C. 調用Adapter的onCreateViewHolder方法建立一個新的ViewHolder
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
上面的代碼主要的目的就是
調用Adapter的createViewHolder方法來建立一個ViewHolder,在這個過程就是簡單計算了建立一個ViewHolder的時間
。
關于複用機制的了解,我們就到此為止。其實RecyclerView的複用機制一點都不複雜,我覺得讓大家望而卻步的原因,是因為我們不知道為什麼在這麼做,如果了解這麼做的原因,一切都顯得那麼理所當然。
分析RecyclerView的複用部分,接下來,我們來分析一下回收部分。
3. 回收
回收是RecyclerView複用機制内部非常重要。首先,有複用的過程,肯定就有回收的過程;其次,同時了解了複用和回收兩個過程,這可以幫助我們在宏觀上了解RecyclerView的工作原理;最後,了解RecyclerView在何時會回收ViewHolder,這對使用RecyclerView有很大的幫助。
其實回收的機制也沒有想象中那麼的難,本文打算從幾個方面來分析RecyclerView的回收過程。
- scrap數組
- mCacheViews數組
- mHiddenViews數組
-
RecyclerViewPool數組
接下來,我們将一一的分析。
(1) scrap數組
關于ViewHolder回收到scrap數組裡面,其實我在前面已經簡單的分析了,重點就在于Recycler的scrapView方法裡面。我們來看看scrapView在哪裡被調用了。有如下兩個地方:
- 在getScrapOrHiddenOrCachedHolderForPosition方法裡面,如果從mHiddenViews獲得一個ViewHolder的話,會先将這個ViewHolder從mHiddenViews數組裡面移除,然後調用Recycler的scrapView方法将這個ViewHolder放入到scrap數組裡面,并且标記FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LIST兩個flag。
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 - 在LayoutManager裡面的scrapOrRecycleView方法也會調用Recycler的scrapView方法。
而有兩種情形下會出現如此情況:1. 手動調用了LayoutManager相關的方法;2. RecyclerView進行了一次布局(調用了requestLayout方法)
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
(2) mCacheViews數組
mCacheViews數組作為二級緩存,回收的路徑相較于一級緩存要多。關于mCacheViews數組,重點在于Recycler的recycleViewHolderInternal方法裡面。我将mCacheViews數組的回收路徑大概分為三類,我們來看看:
-
在重新布局回收了。這種情況主要出現在調用了Adapter的notifyDataSetChange方法,并且此時Adapter的hasStableIds方法傳回為false。
從這裡看出來,為什麼notifyDataSetChange方法效率為什麼那麼低,同時也知道了為什麼重寫hasStableIds方法可以提高效率。
。因為notifyDataSetChange方法使得RecyclerView将回收的ViewHolder放在二級緩存,效率自然比較低
- 在複用時,從一級緩存裡面擷取到ViewHolder,但是此時這個ViewHolder已經不符合一級緩存的特點了(比如Position失效了,跟ViewType對不齊),就會從一級緩存裡面移除這個ViewHolder,從添加到mCacheViews裡面
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 - 當調用removeAnimatingView方法時,如果目前ViewHolder被标記為remove,會調用recycleViewHolderInternal方法來回收對應的ViewHolder。
。調用removeAnimatingView方法的時機表示目前的ItemAnimator已經做完了
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制 RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
(3) mHiddenViews數組
一個ViewHolder回收到mHiddenView數組裡面的條件比較簡單,如果目前操作支援動畫,就會調用到RecyclerView的addAnimatingView方法,在這個方法裡面會将做動畫的那個View添加到mHiddenView數組裡面去。
通常就是動畫期間可能會進行複用,因為mHiddenViews隻在動畫期間才會有元素
。
(4) RecyclerViewPool
RecyclerViewPool跟mCacheViews,都是通過recycleViewHolderInternal方法來進行回收,是以情景與mCacheViews差不多,隻不過當不滿足放入mCacheViews時,才會放入到RecyclerViewPool裡面去。
(5) 為什麼hasStableIds方法傳回true會提高效率呢?
了解了RecyclerView的複用和回收機制之後,這個問題就變得很簡單了。我從兩個方面來解釋原因。
A. 複用方面
我們先來看看複用怎麼能展現hasStableIds能提高效率呢?來看看代碼:
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
在前面通過Position方式來擷取一個ViewHolder失敗之後,如果Adapter的hasStableIds方法傳回為true,在進行通過ViewType方式來擷取ViewHolder時,會優先到1級或者二級緩存裡面去尋找,而不是直接去RecyclerViewPool裡面去尋找。從這裡,我們可以看到,在複用方面,hasStableIds方法提高了效率。
B. 回收方面
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
從上面的代碼中,我們可以看出,如果hasStableIds方法傳回為true的話,這裡所有的回收都進入scrap數組裡面。這剛好與前面對應了。
通過如上兩點,我們就能很好的了解為什麼hasStableIds方法傳回true會提高效率。
4. 總結
RecyclerView回收和複用機制到這裡分析的差不多了。這裡做一個小小的總結。
- 在RecyclerView内部有4級緩存,每一級的緩存所代表的意思都不一樣,同時複用的優先也是從上到下,各自的回收也是不一樣。
- mHideenViews的存在是為了解決在動畫期間進行複用的問題。
- ViewHolder内部有很多的flag,在了解回收和複用機制之前,最好是将ViewHolder的flag梳理清楚。
最後用一張圖檔來結束本文的介紹。
如果不出意外的話,下一篇文章應該是分析RecyclerView的動畫機制