天天看點

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

轉載自瓊珶和予

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 這是

優先級最高的緩存,RecyclerView在擷取ViewHolder時,優先會到這兩個緩存來找。其中mAttachedScrap存儲的是目前還在螢幕中的ViewHolder,mChangedScrap存儲的是資料被更新的ViewHolder,

比如說調用了Adapter的notifyItemChanged方法。可能有人對這兩個緩存還是有點疑惑,不要急,待會會詳細的解釋。
二級緩存 mCachedViews

預設大小為2,通常用來存儲預取的ViewHolder,同時在回收ViewHolder時,也會可能存儲一部分的ViewHolder,這部分的ViewHolder通常來說,意義跟一級緩存差不多

三級緩存 ViewCacheExtension 自定義緩存,通常用不到,在本文中先忽略
四級緩存 RecyclerViewPool

根據ViewType來緩存ViewHolder,每個ViewType的數組大小為5,可以動态的改變

如上表,統一的解釋了每個緩存的含義和作用。在這裡,我再來對其中的幾個緩存做一個詳細的解釋。

  1. mAttachedScrap:上表中說,

    它表示存儲的是目前還在螢幕中ViewHolder

    實際上是從螢幕上分離出來的ViewHolder,但是又即将添加到螢幕上去的ViewHolder

    。比如說,

    RecyclerView上下滑動,滑出一個新的Item,此時會重新調用LayoutManager的onLayoutChildren方法,進而會将螢幕上所有的ViewHolder先scrap掉(含義就是廢棄掉),添加到mAttachedScrap裡面去,然後在重新布局每個ItemView時,會從優先mAttachedScrap裡面擷取

    ,這樣效率就會非常的高。

    這個過程不會重新onBindViewHolder

  2. mCachedViews:

    預設大小為2,不過通常是3,3由預設的大小2 + 預取的個數1

    。是以

    在RecyclerView在首次加載時,mCachedViews的size為3(這裡以LinearLayoutManager的垂直布局為例)

    。通常來說,可以通過RecyclerView的setItemViewCacheSize方法設定大小,但是這個不包括預取大小;預取大小通過LayoutManager的setItemPrefetchEnabled方法來控制。

(2) ViewHolder的幾個狀态值

我們在看RecyclerView的源碼時,可能到處都能看到調用

ViewHolder的isInvalid、isRemoved、isBound、isTmpDetached、isScrap和isUpdated這幾個方法

。這裡我統一的解釋一下。

方法名 對應的Flag 含義或者狀态設定的時機
isInvalid FLAG_INVALID 表示目前ViewHolder是否已經失效。通常來說,

在3種情況下會出現這種情況:1.調用了Adapter的notifyDataSetChanged方法;2. 手動調用RecyclerView的invalidateItemDecorations方法;3. 調用RecyclerView的setAdapter方法或者swapAdapter方法

isRemoved FLAG_REMOVED 表示目前的ViewHolder是否被移除。通常來說,

資料源被移除了部分資料,然後調用Adapter的notifyItemRemoved方法

isBound FLAG_BOUND 表示目前ViewHolder是否已經調用了onBindViewHolder。
isTmpDetached FLAG_TMP_DETACHED 表示目前的ItemView是否從RecyclerView(即父View)detach掉。通常來說

有兩種情況下會出現這種情況:1.手動了RecyclerView的detachView相關方法;2. 在從mHideViews裡面擷取ViewHolder,會先detach掉這個ViewHolder關聯的ItemView

。這裡又多出來一個mHideViews,待會我會詳細的解釋它是什麼。
isScrap 無Flag來表示該狀态,用mScrapContainer是否為null來判斷 表示是否在mAttachedScrap或者mChangedScrap數組裡面,進而表示目前ViewHolder是否被廢棄。
isUpdated FLAG_UPDATE 表示目前ViewHolder是否已經更新。通常來說,

在3種情況下會出現情況:1.isInvalid方法存在的三種情況;2.調用了Adapter的onBindViewHolder方法;3. 調用了Adapter的notifyItemChanged方法

(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。從上面的代碼,我們得出:

  1. mAttachedScrap裡面放的是兩種狀态的ViewHolder:1.被同時标記為remove和invalid;2.完全沒有改變的ViewHolder。這裡還有第三個判斷,這個跟RecyclerView的ItemAnimator有關,如果ItemAnimator為空或者ItemAnimator的canReuseUpdatedViewHolder方法為true,也會放入到mAttachedScrap。

    那正常情況下,什麼情況傳回為true呢?從SimpleItemAnimator的源碼可以看出來,當ViewHolder的isInvalid方法傳回為true時,會放入到 mAttachedScrap裡面。也就是說,如果ViewHolder失效了,也會放到mAttachedScrap裡面

  2. 那麼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

。我們來看看相關的源碼。

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
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;
        }
    }
}
           

如上的代碼分為兩步:

  1. 從mChangedScrap裡面去擷取ViewHolder,這裡面存儲的是更新的ViewHolder。
  2. 分别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步:

  1. 如果Adapter的hasStableIds方法傳回為true,優先通過ViewType和id兩個條件來尋找。如果沒有找到,那麼就進行第2步。
  2. 如果Adapter的hasStableIds方法傳回為false,在這種情況下,首先會在ViewCacheExtension裡面找,如果還沒有找到的話,最後會在RecyclerViewPool裡面來擷取ViewHolder。
  3. 如果以上的複用步驟都沒有找到合适的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。

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

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);
        }
    }
}
           
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

相信這段代碼不用我來分析吧,表達的意思非常簡單。

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的回收過程。

  1. scrap數組
  2. mCacheViews數組
  3. mHiddenViews數組
  4. RecyclerViewPool數組

      

    接下來,我們将一一的分析。

(1) scrap數組

關于ViewHolder回收到scrap數組裡面,其實我在前面已經簡單的分析了,重點就在于Recycler的scrapView方法裡面。我們來看看scrapView在哪裡被調用了。有如下兩個地方:

  1. 在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的緩存機制
  2. 在LayoutManager裡面的scrapOrRecycleView方法也會調用Recycler的scrapView方法。

    而有兩種情形下會出現如此情況:1. 手動調用了LayoutManager相關的方法;2. RecyclerView進行了一次布局(調用了requestLayout方法)

    RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
    RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

(2) mCacheViews數組

mCacheViews數組作為二級緩存,回收的路徑相較于一級緩存要多。關于mCacheViews數組,重點在于Recycler的recycleViewHolderInternal方法裡面。我将mCacheViews數組的回收路徑大概分為三類,我們來看看:

  1. 在重新布局回收了。這種情況主要出現在調用了Adapter的notifyDataSetChange方法,并且此時Adapter的hasStableIds方法傳回為false。

    從這裡看出來,為什麼notifyDataSetChange方法效率為什麼那麼低,同時也知道了為什麼重寫hasStableIds方法可以提高效率。

    因為notifyDataSetChange方法使得RecyclerView将回收的ViewHolder放在二級緩存,效率自然比較低

  2. 在複用時,從一級緩存裡面擷取到ViewHolder,但是此時這個ViewHolder已經不符合一級緩存的特點了(比如Position失效了,跟ViewType對不齊),就會從一級緩存裡面移除這個ViewHolder,從添加到mCacheViews裡面
    RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
  3. 當調用removeAnimatingView方法時,如果目前ViewHolder被标記為remove,會調用recycleViewHolderInternal方法來回收對應的ViewHolder。

    調用removeAnimatingView方法的時機表示目前的ItemAnimator已經做完了

    RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
    RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

(3) mHiddenViews數組

一個ViewHolder回收到mHiddenView數組裡面的條件比較簡單,如果目前操作支援動畫,就會調用到RecyclerView的addAnimatingView方法,在這個方法裡面會将做動畫的那個View添加到mHiddenView數組裡面去。

通常就是動畫期間可能會進行複用,因為mHiddenViews隻在動畫期間才會有元素

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

(4) RecyclerViewPool

RecyclerViewPool跟mCacheViews,都是通過recycleViewHolderInternal方法來進行回收,是以情景與mCacheViews差不多,隻不過當不滿足放入mCacheViews時,才會放入到RecyclerViewPool裡面去。

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制
RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

(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回收和複用機制到這裡分析的差不多了。這裡做一個小小的總結。

  1. 在RecyclerView内部有4級緩存,每一級的緩存所代表的意思都不一樣,同時複用的優先也是從上到下,各自的回收也是不一樣。
  2. mHideenViews的存在是為了解決在動畫期間進行複用的問題。
  3. ViewHolder内部有很多的flag,在了解回收和複用機制之前,最好是将ViewHolder的flag梳理清楚。

最後用一張圖檔來結束本文的介紹。

RecyclerView 源碼分析(三)RecyclerView的緩存機制RecyclerView的緩存機制

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