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

  RecyclerView做爲一個很是惹人愛的控件,有一部分的功勞歸於它優秀的緩存機制。RecyclerView的緩存機制屬於RecyclerView的核心部分,同時也是比較難的部分。儘管緩存機制那麼難,可是仍是不能抵擋得住咱們的好奇心😂。今天咱們來看看它的神奇之處。數組

  本文參考資料:緩存

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

  因爲本文跟本系列的前兩篇文章都有關聯,因此爲了便於理解,能夠去看做者本系列的前兩篇文章。bash

  注意,本文全部的代碼都來自於27.1.1。數據結構

1. 概述

  在正式分析源碼以前,我先對緩存機制作一個概述,同時也會對一些概念進行統一解釋,這些對後面的分析有很大的幫助,由於若是不理解這些概念的話,後面容易看得雨裏霧裏的。ide

(1).四級緩存

  首先,我將RecyclerView的緩存分爲四級,可能有的人將它分爲三級,這些看我的的理解。這裏統一說明一下每級緩存的意思。源碼分析

緩存級別 實際變量 含義
一級緩存 mAttachedScrapmChangedScrap 這是優先級最高的緩存,RecyclerView在獲取ViewHolder時,優先會到這兩個緩存來找。其中mAttachedScrap存儲的是當前還在屏幕中的ViewHoldermChangedScrap存儲的是數據被更新的ViewHolder,好比說調用了AdapternotifyItemChanged方法。可能有人對這兩個緩存仍是有點疑惑,不要急,待會會詳細的解釋。
二級緩存 mCachedViews 默認大小爲2,一般用來存儲預取的ViewHolder,同時在回收ViewHolder時,也會可能存儲一部分的ViewHolder,這部分的ViewHolder一般來講,意義跟一級緩存差很少。
三級緩存 ViewCacheExtension 自定義緩存,一般用不到,在本文中先忽略
四級緩存 RecyclerViewPool 根據ViewType來緩存ViewHolder,每一個ViewType的數組大小爲5,能夠動態的改變。

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

  1. mAttachedScrap:上表中說,它表示存儲的是當前還在屏幕中ViewHolder。其實是從屏幕上分離出來的ViewHolder,可是又即將添加到屏幕上去的ViewHolder。好比說,RecyclerView上下滑動,滑出一個新的Item,此時會從新調用LayoutManageronLayoutChildren方法,從而會將屏幕上全部的ViewHolderscrap掉(含義就是廢棄掉),添加到mAttachedScrap裏面去,而後在從新佈局每一個ItemView時,會從優先mAttachedScrap裏面獲取,這樣效率就會很是的高。這個過程不會從新onBindViewHolder
  2. mCachedViews:默認大小爲2,不過一般是3,3由默認的大小2 + 預取的個數1。因此在RecyclerView在首次加載時,mCachedViewssize爲3(這裏以LinearLayoutManager的垂直佈局爲例)。一般來講,能夠經過RecyclerViewsetItemViewCacheSize方法設置大小,可是這個不包括預取大小;預取大小經過LayoutManagersetItemPrefetchEnabled方法來控制。

(2).ViewHolder的幾個狀態值

  咱們在看RecyclerView的源碼時,可能處處都能看到調用ViewHolderisInvalidisRemovedisBoundisTmpDetachedisScrapisUpdated這幾個方法。這裏我統一的解釋一下。post

方法名 對應的Flag 含義或者狀態設置的時機
isInvalid FLAG_INVALID 表示當前ViewHolder是否已經失效。一般來講,在3種狀況下會出現這種狀況:1.調用了AdapternotifyDataSetChanged方法;2. 手動調用RecyclerViewinvalidateItemDecorations方法;3. 調用RecyclerViewsetAdapter方法或者swapAdapter方法。
isRemoved FLAG_REMOVED 表示當前的ViewHolder是否被移除。一般來講,數據源被移除了部分數據,而後調用AdapternotifyItemRemoved方法。
isBound FLAG_BOUND 表示當前ViewHolder是否已經調用了onBindViewHolder
isTmpDetached FLAG_TMP_DETACHED 表示當前的ItemView是否從RecyclerView(即父View)detach掉。一般來講有兩種狀況下會出現這種狀況:1.手動了RecyclerViewdetachView相關方法;2. 在從mHideViews裏面獲取ViewHolder,會先detach掉這個ViewHolder關聯的ItemView。這裏又多出來一個mHideViews,待會我會詳細的解釋它是什麼。
isScrap 無Flag來表示該狀態,用mScrapContainer是否爲null來判斷 表示是否在mAttachedScrap或者mChangedScrap數組裏面,進而表示當前ViewHolder是否被廢棄。
isUpdated FLAG_UPDATE 表示當前ViewHolder是否已經更新。一般來講,在3種狀況下會出現狀況:1.isInvalid方法存在的三種狀況;2.調用了AdapteronBindViewHolder方法;3. 調用了AdapternotifyItemChanged方法

(3). ChildHelper的mHiddenViews

  在四級緩存中,咱們並無將mHiddenViews算入其中。由於mHiddenViews只在動畫期間纔會有元素,當動畫結束了,天然就清空了。因此mHiddenViews並不算入4級緩存中。fetch

  這裏還有一個問題,就是上面在解釋mChangedScrap時,也在說,當調用AdapternotifyItemChanged方法,會將更新了的ViewHolder反放入mChangedScrap數組裏面。那究竟是放入mChangedScrap仍是mHiddenViews呢?同時可能有人對mChangedScrapmAttachedScrap有疑問,這裏我作一個統一的解釋:動畫

首先,若是調用了AdapternotifyItemChanged方法,會從新回調到LayoutManageronLayoutChildren方法裏面,而在onLayoutChildren方法裏面,會將屏幕上全部的ViewHolder回收到mAttachedScrapmChangedScrap。這個過程就是將ViewHolder分別放到mAttachedScrapmChangedScrap,而什麼條件下放在mAttachedScrap,什麼條件放在mChangedScrap,這個就是他們倆的區別。

  接下來咱們來看一段代碼,就能分清mAttachedScrapmChangedScrap的區別了

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狀態,從而來決定是放入mAttachedScrap仍是mChangedScrap。從上面的代碼,咱們得出:

  1. mAttachedScrap裏面放的是兩種狀態的ViewHolder:1.被同時標記爲removeinvalid;2.徹底沒有改變的ViewHolder。這裏還有第三個判斷,這個跟RecyclerViewItemAnimator有關,若是ItemAnimator爲空或者ItemAnimatorcanReuseUpdatedViewHolder方法爲true,也會放入到mAttachedScrap。那正常狀況下,什麼狀況返回爲true呢?從SimpleItemAnimator的源碼能夠看出來,當ViewHolderisInvalid方法返回爲true時,會放入到 mAttachedScrap裏面。也就是說,若是ViewHolder失效了,也會放到mAttachedScrap裏面。
  2. 那麼mChangedScrap裏面放什麼類型flag的ViewHolder呢?固然是ViewHolderisUpdated方法返回爲true時,會放入到mChangedScrap裏面去。因此,調用AdapternotifyItemChanged方法時,而且RecyclerViewItemAnimator不爲空,會放入到mChangedScrap裏面。

  瞭解了mAttachedScrapmChangedScrap的區別以後,接下咱們來看Scrap數組和mHiddenViews的區別。

mHiddenViews只存放動畫的ViewHolder,動畫結束了天然就清空了。之因此存在 mHiddenViews這個數組,我猜想是存在動畫期間,進行復用的可能性,此時就能夠在mHiddenViews進行復用了。而Scrap數組跟mHiddenViews二者徹底不衝突,因此存在一個ViewHolder同時在Scrap數組和mHiddenViews的可能性。可是這並不影響,由於在動畫結束時,會從mHiddenViews裏面移除。

  本文在分析RecyclerView的換出機制時,打算從兩個大方面入手:1.複用;2.回收。

  咱們先來看看複用的部分邏輯,由於只有理解了RecyclerView到底是如何複用的,對回收才能更加明白。

2. 複用

  RecyclerViewViewHolder的複用,咱們得從LayoutStatenext方法開始。LayoutManager在佈局itemView時,須要獲取一個ViewHolder對象,就是經過這個方法來獲取,具體的複用邏輯也是在這個方面開始調用的。咱們來看看:

View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }
複製代碼

  next方法裏面其實也沒作什麼事,就是調用RecyclerViewgetViewForPosition方法來獲取一個View的。而getViewForPosition方法最終會調用到RecyclerViewtryGetViewHolderForPositionByDeadline方法。因此,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; } } } 複製代碼

  如上的代碼分爲兩步:

  1. mChangedScrap裏面去獲取ViewHolder,這裏面存儲的是更新的ViewHolder
  2. 分別mAttachedScrapmHiddenViewsmCachedViews獲取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數組裏面。由於chang動畫先後相同位置上的ViewHolder是不一樣的,因此當預佈局時,從mChangedScrap緩存裏面去,而正式佈局時,不會從mChangedScrap緩存裏面去,這就保證了動畫先後相同位置上是不一樣的ViewHolder。爲何要保證動畫先後是不一樣的ViewHolder呢?這是RecyclerView動畫機制相關的知識,這裏就不詳細的解釋,後續有專門的文章來分析它,在這裏,咱們只須要記住,chang動畫執行的有一個前提就是動畫先後是不一樣的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; } } } 複製代碼

  這一步理解起來比較容易,分別從mAttachedScrapmHiddenViewsmCachedViews獲取ViewHolder。可是咱們須要的是,若是獲取的ViewHolder是無效的,得作一些清理操做,而後從新放入到緩存裏面,具體對應的緩存就是mCacheViewsRecyclerViewPoolrecycleViewHolderInternal方法就是回收ViewHolder的方法,後面再分析回收相關的邏輯會重點分析這個方法,這裏就不進行追究了。

(2). 經過viewType方式來獲取ViewHolder

  前面分析了經過Position的方式來獲取ViewHolder,這裏咱們來分析一下第二種方式--ViewType。不過在這裏,我先對前面的方式作一個簡單的總結,RecyclerView經過Position來獲取ViewHolder,並不須要判斷ViewType是否合法,由於若是可以經過Position來獲取ViewHolderViewType自己就是正確對應的。

  而這裏經過ViewType來獲取ViewHolder表示,此時ViewHolder緩存的Position已經失效了。ViewType方式來獲取ViewHolder的過程,我將它分爲3步:

  1. 若是AdapterhasStableIds方法返回爲true,優先經過ViewTypeid兩個條件來尋找。若是沒有找到,那麼就進行第2步。
  2. 若是AdapterhasStableIds方法返回爲false,在這種狀況下,首先會在ViewCacheExtension裏面找,若是尚未找到的話,最後會在RecyclerViewPool裏面來獲取ViewHolder。
  3. 若是以上的複用步驟都沒有找到合適的ViewHolder,最後就會調用AdapteronCreateViewHolder方法來建立一個新的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方法自己沒有什麼分析的必要,就是分別從mAttachedScrapmCachedViews數組尋找合適的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"); } } 複製代碼

  上面的代碼主要的目的就是調用AdaptercreateViewHolder方法來建立一個ViewHolder,在這個過程就是簡單計算了建立一個ViewHolder的時間。

  關於複用機制的理解,咱們就到此爲止。其實RecyclerView的複用機制一點都不復雜,我以爲讓你們望而卻步的緣由,是由於咱們不知道爲何在這麼作,若是瞭解這麼作的緣由,一切都顯得那麼理所固然。

  分析RecyclerView的複用部分,接下來,咱們來分析一下回收部分。

3. 回收

  回收是RecyclerView複用機制內部很是重要。首先,有複用的過程,確定就有回收的過程;其次,同時理解了複用和回收兩個過程,這能夠幫助咱們在宏觀上理解RecyclerView的工做原理;最後,理解RecyclerView在什麼時候會回收ViewHolder,這對使用RecyclerView有很大的幫助。

  其實回收的機制也沒有想象中那麼的難,本文打算從幾個方面來分析RecyclerView的回收過程。

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

  接下來,咱們將一一的分析。

(1). scrap數組

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

  1. getScrapOrHiddenOrCachedHolderForPosition方法裏面,若是從mHiddenViews得到一個ViewHolder的話,會先將這個ViewHoldermHiddenViews數組裏面移除,而後調用RecyclerscrapView方法將這個ViewHolder放入到scrap數組裏面,而且標記FLAG_RETURNED_FROM_SCRAPFLAG_BOUNCED_FROM_HIDDEN_LIST兩個flag。
  2. LayoutManager裏面的scrapOrRecycleView方法也會調用RecyclerscrapView方法。而有兩種情形下會出現如此狀況:1. 手動調用了LayoutManager相關的方法;2. RecyclerView進行了一次佈局(調用了requestLayout方法)

(2). mCacheViews數組

  mCacheViews數組做爲二級緩存,回收的路徑相較於一級緩存要多。關於mCacheViews數組,重點在於RecyclerrecycleViewHolderInternal方法裏面。我將mCacheViews數組的回收路徑大概分爲三類,咱們來看看:

  1. 在從新佈局回收了。這種狀況主要出如今調用了AdapternotifyDataSetChange方法,而且此時AdapterhasStableIds方法返回爲false。從這裏看出來,爲何notifyDataSetChange方法效率爲何那麼低,同時也知道了爲何重寫hasStableIds方法能夠提升效率。由於notifyDataSetChange方法使得RecyclerView將回收的ViewHolder放在二級緩存,效率天然比較低。
  2. 在複用時,從一級緩存裏面獲取到ViewHolder,可是此時這個ViewHolder已經不符合一級緩存的特色了(好比Position失效了,跟ViewType對不齊),就會從一級緩存裏面移除這個ViewHolder,從添加到mCacheViews裏面
  3. 當調用removeAnimatingView方法時,若是當前ViewHolder被標記爲remove,會調用recycleViewHolderInternal方法來回收對應的ViewHolder。調用removeAnimatingView方法的時機表示當前的ItemAnimator已經作完了。

(3). mHiddenViews數組

  一個ViewHolder回收到mHiddenView數組裏面的條件比較簡單,若是當前操做支持動畫,就會調用到RecyclerViewaddAnimatingView方法,在這個方法裏面會將作動畫的那個View添加到mHiddenView數組裏面去。一般就是動畫期間能夠會進行復用,由於mHiddenViews只在動畫期間纔會有元素。

(4). RecyclerViewPool

  RecyclerViewPoolmCacheViews,都是經過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失敗以後,若是AdapterhasStableIds方法返回爲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的動畫機制。
相關文章
相關標籤/搜索