??RecyclerView作為一個非常惹人愛的控件,有一部分的功勞歸于它優(yōu)秀的緩存機制。RecyclerView的緩存機制屬于RecyclerView的核心部分,同時也是比較難的部分。盡管緩存機制那么難,但是還是不能抵擋得住我們的好奇心??。今天我們來看看它的神奇之處。
??本文參考資料:
??由于本文跟本系列的前兩篇文章都有關(guān)聯(lián),所以為了便于理解,可以去看作者本系列的前兩篇文章。
??注意,本文所有的代碼都來自于27.1.1。
1. 概述
??在正式分析源碼之前,我先對緩存機制做一個概述,同時也會對一些概念進行統(tǒng)一解釋,這些對后面的分析有很大的幫助,因為如果不理解這些概念的話,后面容易看得雨里霧里的。
(1).四級緩存
??首先,我將RecyclerView的緩存分為四級,可能有的人將它分為三級,這些看個人的理解。這里統(tǒng)一說明一下每級緩存的意思。
| 緩存級別 | 實際變量 | 含義 |
|---|---|---|
| 一級緩存 |
mAttachedScrap和mChangedScrap
|
這是優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當(dāng)前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了Adapter的notifyItemChanged方法??赡苡腥藢@兩個緩存還是有點疑惑,不要急,待會會詳細的解釋。 |
| 二級緩存 | mCachedViews |
默認大小為2,通常用來存儲預(yù)取的ViewHolder,同時在回收ViewHolder時,也會可能存儲一部分的ViewHolder,這部分的ViewHolder通常來說,意義跟一級緩存差不多。 |
| 三級緩存 | ViewCacheExtension |
自定義緩存,通常用不到,在本文中先忽略 |
| 四級緩存 | RecyclerViewPool |
根據(jù)ViewType來緩存ViewHolder,每個ViewType的數(shù)組大小為5,可以動態(tài)的改變。 |
??如上表,統(tǒng)一的解釋了每個緩存的含義和作用。在這里,我再來對其中的幾個緩存做一個詳細的解釋。
mAttachedScrap:上表中說,它表示存儲的是當(dāng)前還在屏幕中ViewHolder。實際上是從屏幕上分離出來的ViewHolder,但是又即將添加到屏幕上去的ViewHolder。比如說,RecyclerView上下滑動,滑出一個新的Item,此時會重新調(diào)用LayoutManager的onLayoutChildren方法,從而會將屏幕上所有的ViewHolder先scrap掉(含義就是廢棄掉),添加到mAttachedScrap里面去,然后在重新布局每個ItemView時,會從優(yōu)先mAttachedScrap里面獲取,這樣效率就會非常的高。這個過程不會重新onBindViewHolder。mCachedViews:默認大小為2,不過通常是3,3由默認的大小2 + 預(yù)取的個數(shù)1。所以在RecyclerView在首次加載時,mCachedViews的size為3(這里以LinearLayoutManager的垂直布局為例)。通常來說,可以通過RecyclerView的setItemViewCacheSize方法設(shè)置大小,但是這個不包括預(yù)取大小;預(yù)取大小通過LayoutManager的setItemPrefetchEnabled方法來控制。
(2).ViewHolder的幾個狀態(tài)值
??我們在看RecyclerView的源碼時,可能到處都能看到調(diào)用ViewHolder的isInvalid、isRemoved、isBound、isTmpDetached、isScrap和isUpdated這幾個方法。這里我統(tǒng)一的解釋一下。
| 方法名 | 對應(yīng)的Flag | 含義或者狀態(tài)設(shè)置的時機 |
|---|---|---|
isInvalid |
FLAG_INVALID |
表示當(dāng)前ViewHolder是否已經(jīng)失效。通常來說,在3種情況下會出現(xiàn)這種情況:1.調(diào)用了Adapter的notifyDataSetChanged方法;2. 手動調(diào)用RecyclerView的invalidateItemDecorations方法;3. 調(diào)用RecyclerView的setAdapter方法或者swapAdapter方法。 |
isRemoved |
FLAG_REMOVED |
表示當(dāng)前的ViewHolder是否被移除。通常來說,數(shù)據(jù)源被移除了部分?jǐn)?shù)據(jù),然后調(diào)用Adapter的notifyItemRemoved方法。 |
isBound |
FLAG_BOUND |
表示當(dāng)前ViewHolder是否已經(jīng)調(diào)用了onBindViewHolder。 |
isTmpDetached |
FLAG_TMP_DETACHED |
表示當(dāng)前的ItemView是否從RecyclerView(即父View)detach掉。通常來說有兩種情況下會出現(xiàn)這種情況:1.手動了RecyclerView的detachView相關(guān)方法;2. 在從mHideViews里面獲取ViewHolder,會先detach掉這個ViewHolder關(guān)聯(lián)的ItemView。這里又多出來一個mHideViews,待會我會詳細的解釋它是什么。 |
isScrap |
無Flag來表示該狀態(tài),用mScrapContainer是否為null來判斷 |
表示是否在mAttachedScrap或者mChangedScrap數(shù)組里面,進而表示當(dāng)前ViewHolder是否被廢棄。 |
isUpdated |
FLAG_UPDATE |
表示當(dāng)前ViewHolder是否已經(jīng)更新。通常來說,在3種情況下會出現(xiàn)情況:1.isInvalid方法存在的三種情況;2.調(diào)用了Adapter的onBindViewHolder方法;3. 調(diào)用了Adapter的notifyItemChanged方法 |
(3). ChildHelper的mHiddenViews
??在四級緩存中,我們并沒有將mHiddenViews算入其中。因為mHiddenViews只在動畫期間才會有元素,當(dāng)動畫結(jié)束了,自然就清空了。所以mHiddenViews并不算入4級緩存中。
??這里還有一個問題,就是上面在解釋mChangedScrap時,也在說,當(dāng)調(diào)用Adapter的notifyItemChanged方法,會將更新了的ViewHolder反放入mChangedScrap數(shù)組里面。那到底是放入mChangedScrap還是mHiddenViews呢?同時可能有人對mChangedScrap和mAttachedScrap有疑問,這里我做一個統(tǒng)一的解釋:
首先,如果調(diào)用了
Adapter的notifyItemChanged方法,會重新回調(diào)到LayoutManager的onLayoutChildren方法里面,而在onLayoutChildren方法里面,會將屏幕上所有的ViewHolder回收到mAttachedScrap和mChangedScrap。這個過程就是將ViewHolder分別放到mAttachedScrap和mChangedScrap,而什么條件下放在mAttachedScrap,什么條件放在mChangedScrap,這個就是他們倆的區(qū)別。
??接下來我們來看一段代碼,就能分清mAttachedScrap和mChangedScrap的區(qū)別了
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);
}
}
??可能很多人初次看到這方法時,會非常的懵逼,我也是如此。今天我們就來看看這個方法。這個根本的目的就是,判斷ViewHolder的flag狀態(tài),從而來決定是放入mAttachedScrap還是mChangedScrap。從上面的代碼,我們得出:
mAttachedScrap里面放的是兩種狀態(tài)的ViewHolder:1.被同時標(biāo)記為remove和invalid;2.完全沒有改變的ViewHolder。這里還有第三個判斷,這個跟RecyclerView的ItemAnimator有關(guān),如果ItemAnimator為空或者ItemAnimator的canReuseUpdatedViewHolder方法為true,也會放入到mAttachedScrap。那正常情況下,什么情況返回為true呢?從SimpleItemAnimator的源碼可以看出來,當(dāng)ViewHolder的isInvalid方法返回為true時,會放入到mAttachedScrap里面。也就是說,如果ViewHolder失效了,也會放到mAttachedScrap里面。- 那么
mChangedScrap里面放什么類型flag的ViewHolder呢?當(dāng)然是ViewHolder的isUpdated方法返回為true時,會放入到mChangedScrap里面去。所以,調(diào)用Adapter的notifyItemChanged方法時,并且RecyclerView的ItemAnimator不為空,會放入到mChangedScrap里面。
??了解了mAttachedScrap和mChangedScrap的區(qū)別之后,接下我們來看Scrap數(shù)組和mHiddenViews的區(qū)別。
mHiddenViews只存放動畫的ViewHolder,動畫結(jié)束了自然就清空了。之所以存在mHiddenViews這個數(shù)組,我猜測是存在動畫期間,進行復(fù)用的可能性,此時就可以在mHiddenViews進行復(fù)用了。而Scrap數(shù)組跟mHiddenViews兩者完全不沖突,所以存在一個ViewHolder同時在Scrap數(shù)組和mHiddenViews的可能性。但是這并不影響,因為在動畫結(jié)束時,會從mHiddenViews里面移除。
??本文在分析RecyclerView的換出機制時,打算從兩個大方面入手:1.復(fù)用;2.回收。
??我們先來看看復(fù)用的部分邏輯,因為只有理解了RecyclerView究竟是如何復(fù)用的,對回收才能更加明白。
2. 復(fù)用
??RecyclerView對ViewHolder的復(fù)用,我們得從LayoutState的next方法開始。LayoutManager在布局itemView時,需要獲取一個ViewHolder對象,就是通過這個方法來獲取,具體的復(fù)用邏輯也是在這個方面開始調(diào)用的。我們來看看:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
??next方法里面其實也沒做什么事,就是調(diào)用RecyclerView的getViewForPosition方法來獲取一個View的。而getViewForPosition方法最終會調(diào)用到RecyclerView的tryGetViewHolderForPositionByDeadline方法。所以,RecyclerView真正復(fù)用的核心就在這個方法,我們今天來詳細的分析一下這個方法。
(1). 通過Position方式來獲取ViewHolder
??通過這種方式來獲取優(yōu)先級比較高,因為每個ViewHolder還沒被改變,通常在這種情況下,都是某一個ItemView對應(yīng)的ViewHolder被更新導(dǎo)致的,所以在屏幕上其他的ViewHolder,可以快速對應(yīng)原來的ItemView。我們來看看相關(guān)的源碼。
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
??我們來簡單的分析一下這兩步。先來看看第一步。
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
??如果當(dāng)前是預(yù)布局階段,那么就從mChangedScrap里面去獲取ViewHolder。那什么階段是預(yù)布局階段呢?這里我對預(yù)布局這個概念簡單的解釋。
預(yù)布局又可以稱之為
preLayout,當(dāng)當(dāng)前的RecyclerView處于dispatchLayoutStep1階段時,稱之為預(yù)布局;dispatchLayoutStep2稱為真正布局的階段;dispatchLayoutStep3稱為postLayout階段。同時要想真正開啟預(yù)布局,必須有ItemAnimator,并且每個RecyclerView對應(yīng)的LayoutManager必須開啟預(yù)處理動畫。
??是不是感覺聽了解釋之后更加的懵逼了?為了解釋一個概念,反而引出了更多的概念了?關(guān)于動畫的問題,不出意外,我會在下一篇文章分析,本文就不對動畫做過多的解釋了。在這里,為了簡單,只要RecyclerView處于dispatchLayoutStep1,我們就當(dāng)做它處于預(yù)布局階段。
??為什么只在預(yù)布局的時候才從mChangedScrap里面去取呢?
??首先,我們得知道mChangedScrap數(shù)組里面放的是什么類型的 ViewHolder。從前面的分析中,我們知道,只有當(dāng)ItemAnimator不為空,被changed的ViewHolder會放在mChangedScrap數(shù)組里面。因為chang動畫前后相同位置上的ViewHolder是不同的,所以當(dāng)預(yù)布局時,從mChangedScrap緩存里面去,而正式布局時,不會從mChangedScrap緩存里面去,這就保證了動畫前后相同位置上是不同的ViewHolder。為什么要保證動畫前后是不同的ViewHolder呢?這是RecyclerView動畫機制相關(guān)的知識,這里就不詳細的解釋,后續(xù)有專門的文章來分析它,在這里,我們只需要記住,chang動畫執(zhí)行的有一個前提就是動畫前后是不同的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是無效的,得做一些清理操作,然后重新放入到緩存里面,具體對應(yīng)的緩存就是mCacheViews和RecyclerViewPool。recycleViewHolderInternal方法就是回收ViewHolder的方法,后面再分析回收相關(guān)的邏輯會重點分析這個方法,這里就不進行追究了。
(2). 通過viewType方式來獲取ViewHolder
??前面分析了通過Position的方式來獲取ViewHolder,這里我們來分析一下第二種方式--ViewType。不過在這里,我先對前面的方式做一個簡單的總結(jié),RecyclerView通過Position來獲取ViewHolder,并不需要判斷ViewType是否合法,因為如果能夠通過Position來獲取ViewHolder,ViewType本身就是正確對應(yīng)的。
??而這里通過ViewType來獲取ViewHolder表示,此時ViewHolder緩存的Position已經(jīng)失效了。ViewType方式來獲取ViewHolder的過程,我將它分為3步:
- 如果
Adapter的hasStableIds方法返回為true,優(yōu)先通過ViewType和id兩個條件來尋找。如果沒有找到,那么就進行第2步。- 如果
Adapter的hasStableIds方法返回為false,在這種情況下,首先會在ViewCacheExtension里面找,如果還沒有找到的話,最后會在RecyclerViewPool里面來獲取ViewHolder。- 如果以上的復(fù)用步驟都沒有找到合適的
ViewHolder,最后就會調(diào)用Adapter的onCreateViewHolder方法來創(chuàng)建一個新的ViewHolder。
??在這里,我們需要注意的是,上面的第1步 和 第2步有前提條件,就是兩個都必須比較ViewType。接下來,我通過代碼簡單的分析一下每一步。
A. 通過id來尋找ViewHolder
??通過id尋找合適的ViewHolder主要是通過調(diào)用getScrapOrCachedViewForId方法來實現(xiàn)的,我們簡單的看一下代碼:
// 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數(shù)組尋找合適的ViewHolder。
B. 從RecyclerViewPool里面獲取ViewHolder
??ViewCacheExtension存在的情況是非常的少見,這里為了簡單,就不展開了(實際上我也不懂!),所以這里,我們直接來看RecyclerViewPool方式。
??在這里,我們需要了解RecyclerViewPool的數(shù)組結(jié)構(gòu)。我們簡單的分析一下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的內(nèi)部,使用SparseArray來存儲每個ViewType對應(yīng)的ViewHolder數(shù)組,其中每個數(shù)組的最大size為5。這個數(shù)據(jù)結(jié)構(gòu)是不是非常簡單呢?
??簡單的了解了RecyclerViewPool的數(shù)據(jù)結(jié)構(gòu),接下來我們來看看復(fù)用的相關(guān)的代碼:
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. 調(diào)用Adapter的onCreateViewHolder方法創(chuàng)建一個新的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");
}
}
??上面的代碼主要的目的就是調(diào)用Adapter的createViewHolder方法來創(chuàng)建一個ViewHolder,在這個過程就是簡單計算了創(chuàng)建一個ViewHolder的時間。
??關(guān)于復(fù)用機制的理解,我們就到此為止。其實RecyclerView的復(fù)用機制一點都不復(fù)雜,我覺得讓大家望而卻步的原因,是因為我們不知道為什么在這么做,如果了解這么做的原因,一切都顯得那么理所當(dāng)然。
??分析RecyclerView的復(fù)用部分,接下來,我們來分析一下回收部分。
3. 回收
??回收是RecyclerView復(fù)用機制內(nèi)部非常重要。首先,有復(fù)用的過程,肯定就有回收的過程;其次,同時理解了復(fù)用和回收兩個過程,這可以幫助我們在宏觀上理解RecyclerView的工作原理;最后,理解RecyclerView在何時會回收ViewHolder,這對使用RecyclerView有很大的幫助。
??其實回收的機制也沒有想象中那么的難,本文打算從幾個方面來分析RecyclerView的回收過程。
- scrap數(shù)組
- mCacheViews數(shù)組
- mHiddenViews數(shù)組
- RecyclerViewPool數(shù)組
??接下來,我們將一一的分析。
(1). scrap數(shù)組
??關(guān)于ViewHolder回收到scrap數(shù)組里面,其實我在前面已經(jīng)簡單的分析了,重點就在于Recycler的scrapView方法里面。我們來看看scrapView在哪里被調(diào)用了。有如下兩個地方:
- 在
getScrapOrHiddenOrCachedHolderForPosition方法里面,如果從mHiddenViews獲得一個ViewHolder的話,會先將這個ViewHolder從mHiddenViews數(shù)組里面移除,然后調(diào)用Recycler的scrapView方法將這個ViewHolder放入到scrap數(shù)組里面,并且標(biāo)記FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LIST兩個flag。- 在
LayoutManager里面的scrapOrRecycleView方法也會調(diào)用Recycler的scrapView方法。而有兩種情形下會出現(xiàn)如此情況:1. 手動調(diào)用了LayoutManager相關(guān)的方法;2.RecyclerView進行了一次布局(調(diào)用了requestLayout方法)
(2). mCacheViews數(shù)組
??mCacheViews數(shù)組作為二級緩存,回收的路徑相較于一級緩存要多。關(guān)于mCacheViews數(shù)組,重點在于Recycler的recycleViewHolderInternal方法里面。我將mCacheViews數(shù)組的回收路徑大概分為三類,我們來看看:
- 在重新布局回收了。這種情況主要出現(xiàn)在調(diào)用了
Adapter的notifyDataSetChange方法,并且此時Adapter的hasStableIds方法返回為false。從這里看出來,為什么notifyDataSetChange方法效率為什么那么低,同時也知道了為什么重寫hasStableIds方法可以提高效率。因為notifyDataSetChange方法使得RecyclerView將回收的ViewHolder放在二級緩存,效率自然比較低。- 在復(fù)用時,從一級緩存里面獲取到
ViewHolder,但是此時這個ViewHolder已經(jīng)不符合一級緩存的特點了(比如Position失效了,跟ViewType對不齊),就會從一級緩存里面移除這個ViewHolder,從添加到mCacheViews里面- 當(dāng)調(diào)用
removeAnimatingView方法時,如果當(dāng)前ViewHolder被標(biāo)記為remove,會調(diào)用recycleViewHolderInternal方法來回收對應(yīng)的ViewHolder。調(diào)用removeAnimatingView方法的時機表示當(dāng)前的ItemAnimator已經(jīng)做完了。
(3). mHiddenViews數(shù)組
??一個ViewHolder回收到mHiddenView數(shù)組里面的條件比較簡單,如果當(dāng)前操作支持動畫,就會調(diào)用到RecyclerView的addAnimatingView方法,在這個方法里面會將做動畫的那個View添加到mHiddenView數(shù)組里面去。通常就是動畫期間可以會進行復(fù)用,因為mHiddenViews只在動畫期間才會有元素。
(4). RecyclerViewPool
??RecyclerViewPool跟mCacheViews,都是通過recycleViewHolderInternal方法來進行回收,所以情景與mCacheViews差不多,只不過當(dāng)不滿足放入mCacheViews時,才會放入到RecyclerViewPool里面去。
(5). 為什么hasStableIds方法返回true會提高效率呢?
??了解了RecyclerView的復(fù)用和回收機制之后,這個問題就變得很簡單了。我從兩個方面來解釋原因。
A. 復(fù)用方面
??我們先來看看復(fù)用怎么能體現(xiàn)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時,會優(yōu)先到1級或者二級緩存里面去尋找,而不是直接去RecyclerViewPool里面去尋找。從這里,我們可以看到,在復(fù)用方面,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數(shù)組里面。這剛好與前面對應(yīng)了。
??通過如上兩點,我們就能很好的理解為什么hasStableIds方法返回true會提高效率。
4. 總結(jié)
??RecyclerView回收和復(fù)用機制到這里分析的差不多了。這里做一個小小的總結(jié)。
- 在
RecyclerView內(nèi)部有4級緩存,每一級的緩存所代表的意思都不一樣,同時復(fù)用的優(yōu)先也是從上到下,各自的回收也是不一樣。mHideenViews的存在是為了解決在動畫期間進行復(fù)用的問題。ViewHolder內(nèi)部有很多的flag,在理解回收和復(fù)用機制之前,最好是將ViewHolder的flag梳理清楚。
??最后用一張圖片來結(jié)束本文的介紹。

??如果不出意外的話,下一篇文章應(yīng)該是分析
RecyclerView的動畫機制