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

??RecyclerView作為一個非常惹人愛的控件,有一部分的功勞歸于它優(yōu)秀的緩存機制。RecyclerView的緩存機制屬于RecyclerView的核心部分,同時也是比較難的部分。盡管緩存機制那么難,但是還是不能抵擋得住我們的好奇心??。今天我們來看看它的神奇之處。
??本文參考資料:

  1. RecyclerView緩存原理,有圖有真相
  2. 【進階】RecyclerView源碼解析(二)——緩存機制
  3. 深入 RecyclerView 源碼探究四:回收復(fù)用和動畫
  4. 手摸手第二彈,可視化 RecyclerView 緩存機制
  5. RecyclerView 源碼分析(一) - RecyclerView的三大流程

??由于本文跟本系列的前兩篇文章都有關(guān)聯(lián),所以為了便于理解,可以去看作者本系列的前兩篇文章。
??注意,本文所有的代碼都來自于27.1.1。

1. 概述

??在正式分析源碼之前,我先對緩存機制做一個概述,同時也會對一些概念進行統(tǒng)一解釋,這些對后面的分析有很大的幫助,因為如果不理解這些概念的話,后面容易看得雨里霧里的。

(1).四級緩存

??首先,我將RecyclerView的緩存分為四級,可能有的人將它分為三級,這些看個人的理解。這里統(tǒng)一說明一下每級緩存的意思。

緩存級別 實際變量 含義
一級緩存 mAttachedScrapmChangedScrap 這是優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當(dāng)前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了AdapternotifyItemChanged方法??赡苡腥藢@兩個緩存還是有點疑惑,不要急,待會會詳細的解釋。
二級緩存 mCachedViews 默認大小為2,通常用來存儲預(yù)取的ViewHolder,同時在回收ViewHolder時,也會可能存儲一部分的ViewHolder,這部分的ViewHolder通常來說,意義跟一級緩存差不多。
三級緩存 ViewCacheExtension 自定義緩存,通常用不到,在本文中先忽略
四級緩存 RecyclerViewPool 根據(jù)ViewType來緩存ViewHolder,每個ViewType的數(shù)組大小為5,可以動態(tài)的改變。

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

  1. mAttachedScrap:上表中說,它表示存儲的是當(dāng)前還在屏幕中ViewHolder。實際上是從屏幕上分離出來的ViewHolder,但是又即將添加到屏幕上去的ViewHolder。比如說,RecyclerView上下滑動,滑出一個新的Item,此時會重新調(diào)用LayoutManageronLayoutChildren方法,從而會將屏幕上所有的ViewHolderscrap掉(含義就是廢棄掉),添加到mAttachedScrap里面去,然后在重新布局每個ItemView時,會從優(yōu)先mAttachedScrap里面獲取,這樣效率就會非常的高。這個過程不會重新onBindViewHolder。
  2. mCachedViews:默認大小為2,不過通常是3,3由默認的大小2 + 預(yù)取的個數(shù)1。所以在RecyclerView在首次加載時,mCachedViewssize為3(這里以LinearLayoutManager的垂直布局為例)。通常來說,可以通過RecyclerViewsetItemViewCacheSize方法設(shè)置大小,但是這個不包括預(yù)取大小;預(yù)取大小通過LayoutManagersetItemPrefetchEnabled方法來控制。

(2).ViewHolder的幾個狀態(tài)值

??我們在看RecyclerView的源碼時,可能到處都能看到調(diào)用ViewHolderisInvalidisRemoved、isBound、isTmpDetached、isScrapisUpdated這幾個方法。這里我統(tǒng)一的解釋一下。

方法名 對應(yīng)的Flag 含義或者狀態(tài)設(shè)置的時機
isInvalid FLAG_INVALID 表示當(dāng)前ViewHolder是否已經(jīng)失效。通常來說,在3種情況下會出現(xiàn)這種情況:1.調(diào)用了AdapternotifyDataSetChanged方法;2. 手動調(diào)用RecyclerViewinvalidateItemDecorations方法;3. 調(diào)用RecyclerViewsetAdapter方法或者swapAdapter方法。
isRemoved FLAG_REMOVED 表示當(dāng)前的ViewHolder是否被移除。通常來說,數(shù)據(jù)源被移除了部分?jǐn)?shù)據(jù),然后調(diào)用AdapternotifyItemRemoved方法。
isBound FLAG_BOUND 表示當(dāng)前ViewHolder是否已經(jīng)調(diào)用了onBindViewHolder。
isTmpDetached FLAG_TMP_DETACHED 表示當(dāng)前的ItemView是否從RecyclerView(即父View)detach掉。通常來說有兩種情況下會出現(xiàn)這種情況:1.手動了RecyclerViewdetachView相關(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)用了AdapteronBindViewHolder方法;3. 調(diào)用了AdapternotifyItemChanged方法

(3). ChildHelper的mHiddenViews

??在四級緩存中,我們并沒有將mHiddenViews算入其中。因為mHiddenViews只在動畫期間才會有元素,當(dāng)動畫結(jié)束了,自然就清空了。所以mHiddenViews并不算入4級緩存中。
??這里還有一個問題,就是上面在解釋mChangedScrap時,也在說,當(dāng)調(diào)用AdapternotifyItemChanged方法,會將更新了的ViewHolder反放入mChangedScrap數(shù)組里面。那到底是放入mChangedScrap還是mHiddenViews呢?同時可能有人對mChangedScrapmAttachedScrap有疑問,這里我做一個統(tǒng)一的解釋:

首先,如果調(diào)用了AdapternotifyItemChanged方法,會重新回調(diào)到LayoutManageronLayoutChildren方法里面,而在onLayoutChildren方法里面,會將屏幕上所有的ViewHolder回收到mAttachedScrapmChangedScrap。這個過程就是將ViewHolder分別放到mAttachedScrapmChangedScrap,而什么條件下放在mAttachedScrap,什么條件放在mChangedScrap,這個就是他們倆的區(qū)別。

??接下來我們來看一段代碼,就能分清mAttachedScrapmChangedScrap的區(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。從上面的代碼,我們得出:

  1. mAttachedScrap里面放的是兩種狀態(tài)的ViewHolder:1.被同時標(biāo)記為removeinvalid;2.完全沒有改變的ViewHolder。這里還有第三個判斷,這個跟RecyclerViewItemAnimator有關(guān),如果ItemAnimator為空或者ItemAnimatorcanReuseUpdatedViewHolder方法為true,也會放入到mAttachedScrap。那正常情況下,什么情況返回為true呢?從SimpleItemAnimator的源碼可以看出來,當(dāng)ViewHolderisInvalid方法返回為true時,會放入到 mAttachedScrap里面。也就是說,如果ViewHolder失效了,也會放到mAttachedScrap里面。
  2. 那么mChangedScrap里面放什么類型flag的ViewHolder呢?當(dāng)然是ViewHolderisUpdated方法返回為true時,會放入到mChangedScrap里面去。所以,調(diào)用AdapternotifyItemChanged方法時,并且RecyclerViewItemAnimator不為空,會放入到mChangedScrap里面。

??了解了mAttachedScrapmChangedScrap的區(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ù)用

??RecyclerViewViewHolder的復(fù)用,我們得從LayoutStatenext方法開始。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)用RecyclerViewgetViewForPosition方法來獲取一個View的。而getViewForPosition方法最終會調(diào)用到RecyclerViewtryGetViewHolderForPositionByDeadline方法。所以,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;
                    }
                }
            }

??如上的代碼分為兩步:

  1. mChangedScrap里面去獲取ViewHolder,這里面存儲的是更新的ViewHolder。
  2. 分別mAttachedScrap、 mHiddenViewsmCachedViews獲取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;
                    }
                }
            }

??這一步理解起來比較容易,分別從mAttachedScrapmHiddenViews、mCachedViews獲取ViewHolder。但是我們需要的是,如果獲取的ViewHolder是無效的,得做一些清理操作,然后重新放入到緩存里面,具體對應(yīng)的緩存就是mCacheViewsRecyclerViewPool。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步:

  1. 如果AdapterhasStableIds方法返回為true,優(yōu)先通過ViewTypeid兩個條件來尋找。如果沒有找到,那么就進行第2步。
  2. 如果AdapterhasStableIds方法返回為false,在這種情況下,首先會在ViewCacheExtension里面找,如果還沒有找到的話,最后會在RecyclerViewPool里面來獲取ViewHolder。
  3. 如果以上的復(fù)用步驟都沒有找到合適的ViewHolder,最后就會調(diào)用AdapteronCreateViewHolder方法來創(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方法本身沒有什么分析的必要,就是分別從mAttachedScrapmCachedViews數(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)用AdaptercreateViewHolder方法來創(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的回收過程。

  1. scrap數(shù)組
  2. mCacheViews數(shù)組
  3. mHiddenViews數(shù)組
  4. RecyclerViewPool數(shù)組

??接下來,我們將一一的分析。

(1). scrap數(shù)組

??關(guān)于ViewHolder回收到scrap數(shù)組里面,其實我在前面已經(jīng)簡單的分析了,重點就在于RecyclerscrapView方法里面。我們來看看scrapView在哪里被調(diào)用了。有如下兩個地方:

  1. getScrapOrHiddenOrCachedHolderForPosition方法里面,如果從mHiddenViews獲得一個ViewHolder的話,會先將這個ViewHoldermHiddenViews數(shù)組里面移除,然后調(diào)用RecyclerscrapView方法將這個ViewHolder放入到scrap數(shù)組里面,并且標(biāo)記FLAG_RETURNED_FROM_SCRAPFLAG_BOUNCED_FROM_HIDDEN_LIST兩個flag。
  2. LayoutManager里面的scrapOrRecycleView方法也會調(diào)用RecyclerscrapView方法。而有兩種情形下會出現(xiàn)如此情況:1. 手動調(diào)用了LayoutManager相關(guān)的方法;2. RecyclerView進行了一次布局(調(diào)用了requestLayout方法)

(2). mCacheViews數(shù)組

??mCacheViews數(shù)組作為二級緩存,回收的路徑相較于一級緩存要多。關(guān)于mCacheViews數(shù)組,重點在于RecyclerrecycleViewHolderInternal方法里面。我將mCacheViews數(shù)組的回收路徑大概分為三類,我們來看看:

  1. 在重新布局回收了。這種情況主要出現(xiàn)在調(diào)用了AdapternotifyDataSetChange方法,并且此時AdapterhasStableIds方法返回為false。從這里看出來,為什么notifyDataSetChange方法效率為什么那么低,同時也知道了為什么重寫hasStableIds方法可以提高效率。因為notifyDataSetChange方法使得RecyclerView將回收的ViewHolder放在二級緩存,效率自然比較低。
  2. 在復(fù)用時,從一級緩存里面獲取到ViewHolder,但是此時這個ViewHolder已經(jīng)不符合一級緩存的特點了(比如Position失效了,跟ViewType對不齊),就會從一級緩存里面移除這個ViewHolder,從添加到mCacheViews里面
  3. 當(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)用到RecyclerViewaddAnimatingView方法,在這個方法里面會將做動畫的那個View添加到mHiddenView數(shù)組里面去。通常就是動畫期間可以會進行復(fù)用,因為mHiddenViews只在動畫期間才會有元素。

(4). RecyclerViewPool

??RecyclerViewPoolmCacheViews,都是通過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失敗之后,如果AdapterhasStableIds方法返回為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é)。

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

??最后用一張圖片來結(jié)束本文的介紹。


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容