RecyclerView 源碼分析(一):Recycler

前言

RecyclerView 是一個好用又複雜的控件,其功能的高度解耦化,規範化的 ViewHolder 寫法,以及對動畫的友好支持,都是它與傳統 ListView 的區別。html

它有幾大模塊:java

  • LayoutManager:控制 item 的佈局
  • RecyclerView.Adapter:爲 RecyclerView 提供數據
  • ItemDecoration:爲 RecyclerView 添加分割線
  • ItemAnimator:控制 item 的動畫
  • Recycler:負責回收和提供 View,和 RecyclerView 的複用機制相關

下面就從源碼(API 28)角度分析 RecyclerView,RecyclerView 的源碼很複雜,很難在一篇文章內講完,因此打算分幾篇來說,本文是第一篇,將圍繞 RecyclerView 的內部類 Recycler 展開分析:android

RecyclerView.Recycler

首先看一下它的做用,源碼上是這樣寫的:緩存

A Recycler is responsible for managing scrapped or detached item views for reuse.app

意思就是 Recycler 負責管理廢棄或被 detached 的 item 視圖,以便重複利用。ide

它有如下幾個成員變量:源碼分析

主要成員變量

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;
複製代碼

這幾個成員變量都和 RecyclerView 的緩存相關,若是按照四級緩存的話,它們能夠這樣劃分:佈局

第一級緩存:mAttachedScrap、mChangedScrappost

第二級緩存:mCachedViews動畫

第三級緩存:ViewCacheExtension

第四級緩存:RecycledViewPool

後面再介紹 mAttachedScrap、mChangedScrap、mCachedViews 具體存的是哪些 ViewHolder。

如今先了解下 RecycledViewPool 和 ViewCacheExtension這兩個類:

RecycledViewPool

繼續先看官方註釋:

RecycledViewPool lets you share Views between multiple RecyclerViews.

RecycledViewPool 用於在多個 RecyclerView 間共享 View。

在使用時,只需建立 RecycledViewPool 實例,而後調用 RecyclerView 的 setRecycledViewPool(RecycledViewPool) 方法便可。

RecycledViewPool 存儲在 Recycler 中,經過 Recycler 存取。

成員變量

RecycledViewPool 有一個重要的成員變量:

// SparseArray 相似於 key 爲 int 類型 的 HashMap
    SparseArray<ScrapData> mScrap = new SparseArray<>();
複製代碼

其中 ScrapData 的定義以下:

static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;  // 5
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
複製代碼

mScrap 是一個 <int, ScrapData> 的映射,其中 int 表明了 viewType,ScrapData 則存儲了一個 ViewHolder 集合。

主要方法

getScrapDataForType

private ScrapData getScrapDataForType(int viewType) {
        ScrapData scrapData = mScrap.get(viewType);
        if (scrapData == null) {
            scrapData = new ScrapData();
            mScrap.put(viewType, scrapData);
        }
        return scrapData;
    }
複製代碼

該方法根據 viewType 獲取相應的 ScrapData,若是該 viewType 尚未綁定 ScrapData,就新建立一個 ScrapData 並綁定到該 viewType。

setMaxRecycledViews

public void setMaxRecycledViews(int viewType, int max) {
        ScrapData scrapData = getScrapDataForType(viewType);
        scrapData.mMaxScrap = max;
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        // 從後面開始刪除,直到知足新的容量
        while (scrapHeap.size() > max) {
            scrapHeap.remove(scrapHeap.size() - 1);
        }
    }
複製代碼

該方法能夠設置相應 viewType 的 View 容量,超出容量時,從後面開始刪除,直到知足新的容量。

getRecycledView

public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            return scrapHeap.remove(scrapHeap.size() - 1);
        }
        return null;
    }
複製代碼

該方法根據 viewType 獲取一個 ViewHolder,獲取到的 ViewHolder 將會被移除出 Scrap 堆。獲取不到則返回 null。

putRecycledView

public void putRecycledView(ViewHolder scrap) {
        final int viewType = scrap.getItemViewType();
        final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
        // 容量已滿,再也不添加
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
            return;
        }
        // 重置 ViewHolder,例如清空 flags
        scrap.resetInternal();
        // 將該 ViewHolder 添加到對應 viewType 的 集合中緩存起來
        scrapHeap.add(scrap);
    }
複製代碼

該方法也很好理解,根據 ViewHolder 的 viewType 放入 RecycledViewPool 的相應集合中,若是集合已滿,再也不添加。

接下來看另外一個類:

ViewCacheExtension

ViewCacheExtension 是一個由開發者控制的 View 緩存幫助類,其定義以下:

public abstract static class ViewCacheExtension {

        /** * Returns a View that can be binded to the given Adapter position. */
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);
    }
複製代碼

開發者能夠實現這個抽象類,經過調用 RecyclerView 的 setViewCacheExtension(ViewCacheExtension) 方法設置,最終將 ViewCacheExtension 存儲在 Recycler 中。

當調用 Recycler 的 getViewForPosition 方法時,若是 attached scrap 和 已經緩存都沒有找到合適的 View,就會調用 ViewCacheExtension 的 getViewForPositionAndType 方法來獲取 View。

須要注意的是,Recycler 不會對這個類作任何緩存處理,是否須要緩存 View 由開發者本身控制。

主要方法

看完這兩個類,如今回到 Recycler 中,看一下 Rcycler 的主要方法:

getViewForPosition

getViewForPosition 方法比較重要,用於獲取某個位置須要展現的 View,以下:

public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    
    View getViewForPosition(int position, boolean dryRun) {
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
複製代碼

繼續看 tryGetViewHolderForPositionByDeadline 方法,該方法會依次從幾個緩存中獲取,分別來看一下:

// 若是是處於預佈局階段(先簡單理解爲執行 dispatchLayoutStep1 方法)
        // (其實下面方法要返回 ture 還須要開啓「預處理動畫」,這跟動畫有關,先很少說)
	if (mState.isPreLayout()) {
		holder = getChangedScrapViewForPosition(position);
		fromScrapOrHiddenOrCache = holder != null;
	}
複製代碼

第一步,從 mChangedScrap 中獲取,獲取不到就返回 null。

若是 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
			    // 回收無效的 ViewHolder
			    // ...
			} else {
				fromScrapOrHiddenOrCache = true;
			}
		}
	}
複製代碼

第二步,根據 position 依次從 mAttachedScrap、mHiddenViews(存儲在 ChildHelper 類)、mCachedViews 中獲取緩存的 ViewHolder。

能夠從 mHiddenViews 獲取到緩存的話,就將其從 mHiddenViews 移除並添加到 Scrap 緩存(根據狀況添加到 mAttachedScrap 或 mChangedScrap)。能夠從 mCacheViews 中獲取到緩存的話,就將其從 mCacheViews 移除。

獲取到後,發現無效的話,將對獲取到的 ViewHolder 進行清理並回收(放入 mCachedViews 或 RecycledViewPool)。

獲取不到,就繼續往下執行:

// 默認返回 false,可經過 Adapter.setHasStableIds 方法設置該值
	if (mAdapter.hasStableIds()) {
		// 根據 id 依次在 mAttachedScrap、mCachedViews 中獲取緩存
		holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
				type, dryRun);
		if (holder != null) {
			holder.mPosition = offsetPosition;
			fromScrapOrHiddenOrCache = true;
		}
	}
複製代碼

第三步,根據 id 依次從 mAttachedScrap、mCachedViews 中獲取緩存,尚未獲取到就繼續往下:

// 若是用戶設置了 ViewCacheExtension
	if (holder == null && mViewCacheExtension != null) {
		// We are NOT sending the offsetPosition because LayoutManager does not
		// know it.
		final View view = mViewCacheExtension
				.getViewForPositionAndType(this, position, type);
		if (view != null) {
			holder = getChildViewHolder(view);
			// ...
		}
	}
複製代碼

第四步,從用戶設置的 ViewCacheExtension 中獲取緩存,沒有獲取到就繼續往下:

if (holder == null) { // fallback to pool
		holder = getRecycledViewPool().getRecycledView(type);
                // ...
	}
複製代碼

第五步,根據 viewType 從 RecycledViewPool 中獲得緩存。

RecycledViewPool 已是最後一級緩存了,若是這裏也沒有獲取到,只能經過 Adapter 的 createViewHolder 方法建立一個 ViewHolder:

if (holder == null) {

		holder = mAdapter.createViewHolder(RecyclerView.this, type);

                // ...
	}
複製代碼

最後小結一下獲取某個位置的 View 的過程:

  1. 前後根據 position 或 id 從 mChangedScrap 中獲取緩存
  2. 根據 position 依次從 mAttachedScrap、mHiddenViews(存儲在 ChildHelper 類)、mCachedViews 中獲取緩存
  3. 根據 id 依次從 mAttachedScrap、mCachedViews 中獲取緩存
  4. 從用戶設置的 ViewCacheExtension 中獲取緩存
  5. 從 RecycledViewPool 中獲得緩存的廢棄 ViewHolder
  6. 經過 Adapter 的 createViewHolder 方法建立一個 ViewHolder

另外,光有視圖還不夠,還要爲視圖設置數據,因此後面還有這樣一段代碼:

// ViewHolder 還未進行綁定操做、帶有 FLAG_UPDATE 或 FLAG_INVALID 標記
	else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        // ...
        
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
複製代碼

tryBindViewHolderByDeadline 方法最後會調用 Adapter 的 onBindViewHolder 方法,咱們平時就是在該方法上給視圖設置數據的。

recycleView

既然叫 Recycler,那確定要作回收工做了,recycleView 方法就完成了這些工做,下面看一下該方法的實現:

public void recycleView(@NonNull View view) {
        ViewHolder holder = getChildViewHolderInt(view);
        // ...
        recycleViewHolderInternal(holder);
    }
複製代碼

繼續看 recycleViewHolderInternal:

void recycleViewHolderInternal(ViewHolder holder) {
	// ...

        if (forceRecycle || holder.isRecyclable()) {
            if (mViewCacheMax > 0
                    && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_REMOVED
                    | ViewHolder.FLAG_UPDATE
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
						
                int cachedViewSize = mCachedViews.size();
		// 若 CacheViews 達到最大容量(2),將最老的緩存從 CacheViews 移除,並添加到 RecycledViewPool 中
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }

		// ...
		
		// 將 View 緩存到 mCachedViews 中
                mCachedViews.add(targetCacheIndex, holder);
                cached = true;
            }
            if (!cached) {
        	// 沒有添加到 mCachedViews 的話,就添加到 RecycledViewPool 中
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true;
            }
        }

		// ...
    }
複製代碼

能夠看到,回收過程主要涉及到兩層緩存,第一層緩存是 CacheViews,在添加時,若是發現原來的 CacheViews 已經達到最大容量,就將最老的緩存從 CacheViews 移除,並添加到 RecycledViewPool。第二層緩存是 RecycledViewPool,若是不能添加到 mCacheViews,就會添加到 RecycledViewPool 中。

補充

mChangedScrap 和 mAttachedScrap 中的 View 從何而來

從前面能夠得知,在執行 Recycler 的 recycleView 方法時,會將回收的 View 緩存到 mCahceViews 或 recycledViewPool 中,那麼另外兩個 Scrap 緩存(mChangedScrap 和 mAttachedScrap)中的 View 是什麼時候添加進來的呢?

不管是 mAttachedScrap 仍是 mChangedScrap ,它們得到 View 的途徑都只有一個,那就是經過 Recycler 的 scrapView 方法。先看下該方法:

Recycler#scrapView

void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
		
	// 知足這幾個條件中的一個就能夠進入 if 循環,有機會將 View 緩存到 mAttachedScrap
	// 1. ViewHolder 設置了 FLAG_REMOVED 或 FLAG_INVALID
	// 2. ViewHolder 沒有設置 FLAG_UPDATE
	// 3. 沒有設置動畫或者動畫能夠重用該 ViewHolder
        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());
            }
    	    // 給 ViewHolder 綁定 Recycler
            holder.setScrapContainer(this, false);
            mAttachedScrap.add(holder);
        } 
	// 不知足上述任意一個條件時,將 View 緩存到 mChangedScrap 中
	else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this, true);
            mChangedScrap.add(holder);
        }
    }
複製代碼

該方法經過判斷 ViewHolder 的 flag 以及是否設置 ItemAnimator 等,決定將 View 緩存到 mAttachedScrap 仍是 mChangedScrap。

那麼該方法在什麼時候調用呢?有兩種狀況:

  1. 以 LinearLayoutManager 爲例,在它的 onLayoutChildren 方法中,會調用
detachAndScrapAttachedViews(recycler);
複製代碼

該方法定義在 RecyclerView 的 LayoutManager 中,它繼續調用 scrapOrRecycleView 方法,若是在該方法符合條件就調用 Recycler 的 scrapView 方法。

  1. 經過 mHiddenViews 獲取到緩存時,也會調用 scrapView 方法。

場景分析

下面就根據一些場景來分析下 Recycler 是如何進行回收和複用的。

第一次 layout

因爲這裏不是專門分析 layout 過程的,就不從 onLayout 開始說了,中間的過程省略掉,它最終會調用到 LayoutManager 的 onLayoutChildren,這裏以 LinearLayoutManager 爲例:

LinearLayoutManager#onLayoutChildren

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // ...

        // 找到錨點(具體過程等到分析 layout 時再說)
		
	// (1)
	detachAndScrapAttachedViews(recycler);
		
        if (mAnchorInfo.mLayoutFromEnd) {
	    // ...
        } else {
            
            // (2)
            fill(recycler, mLayoutState, state, false);

	    // ...
        }
		
	    // ...
    }
複製代碼

首先看(1)處,detachAndScrapAttachedViews 方法會根據狀況將子 View 回收到相應緩存,具體過程以後再看,因爲如今是第一次 layout,RecyclerView 中沒有子 View,因此如今該方法沒啥用。

接下來看(2)處,這裏的 fill 方法比較重要,它的做用是填充佈局。看一下該方法:

LinearLayoutManager#fill

int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {

	// 進行 layout 時 layoutState.mScrollingOffset 的值被設置爲
	// LayoutState.SCROLLING_OFFSET_NaN,不會進入此 if 塊
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
	    // ...
            recycleByLayoutState(recycler, layoutState);
        }
		
	// 須要填充的空間
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
	// 還有須要填充的空間而且 item 數未滿
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
	    // ...
	
	    // (1)
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

	    // 計算剩餘空間

	    // 同上,在 layout 時不會進入 if 塊中
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
		// ...
                recycleByLayoutState(recycler, layoutState);
            }
			
		// ...
        }
    }
複製代碼

主要看(1)處的 layoutChunk 方法,只要還有須要填充的空間,就會不斷調用該方法:

LinearLayoutManager#layoutChunk

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
	// (1)
        View view = layoutState.next(recycler);

	// ...
		
	// 默認狀況下,layoutState.mScrapList 等於 null
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
		// (2)
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
		// ...
        }
    }
複製代碼

(2)處的 addView 方法就很少說了,該方法將獲得的子 View 添加到 RecyclerView 中。主要看(1)處,看看子 View 從何而來:

View next(RecyclerView.Recycler recycler) {
        // ...
        
        final View view = recycler.getViewForPosition(mCurrentPosition);

        return view;
    }
複製代碼

這個方法是否是很熟悉呢?沒錯,它就是以前分析的 Recycler 的 getViewForPosition 方法。

不過因爲如今沒有任何緩存,因此第一次 layout 的時候是經過 Adapter 的 createViewHolder 來建立子 View的,而且沒有添加任何緩存。

更新列表

更新列表可使用 Adapter 的一系列 notify 方法,這裏分析其中兩個方法:notifyDataSetChanged 和 notifyItemChanged(int)。

Adapter#notifyDataSetChanged

該方法最終調用了 RecyclerViewDataObserver 的 onChanged 方法:

@Override
    public void onChanged() {
	// ...

	// 該方法主要作了這兩件事
	// 1. 給全部 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID
	// 2. 默認狀況下(mHasStableIds 爲 false)清空 CacheViews
        processDataSetCompletelyChanged(true);
		
        if (!mAdapterHelper.hasPendingUpdates()) {
	    // 進行視圖重繪
            requestLayout();
        }
    }
複製代碼

該方法會進行視圖重繪,又來到了 layout 過程,繼續以 LinearLayoutManager 爲例,從它的 onLayoutChildren 方法看起,因爲分析第一次 layout 時已經看過一遍了,此次主要看下不一樣之處:

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
		// ...
		
		detachAndScrapAttachedViews(recycler);
		
		// ...
    }
複製代碼

主要區別在於 detachAndScrapAttachedViews 方法,此次它開始起做用了,該方法在 RecyclerView 的 LayoutManager 中定義,看下它的實現:

LayoutManager#detachAndScrapAttachedViews

public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
        final int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final View v = getChildAt(i);
            scrapOrRecycleView(recycler, i, v);
        }
    }
複製代碼

因爲不是第一次 layout,RecyclerView 這時已經有子 View 了,該方法遍歷子 View,調用 scrapOrRecycleView 方法:

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
	// 不能回收添加了 FLAG_IGNORE 標記的 ViewHolder
	// 可經過 LayoutManager 的 ignoreView 爲相應的 View 添加該標記
        if (viewHolder.shouldIgnore()) {
            return;
        }
	// 這些條件都知足,進入 if 塊
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
            removeViewAt(index);
            recycler.recycleViewHolderInternal(viewHolder);
        } else {
            // ...
        }
    }
複製代碼

這裏將子 View 移除並經過 Recycler 的 recycleViewHolderInternal 方法進行回收:

Recycler#recycleViewHolderInternal

void recycleViewHolderInternal(ViewHolder holder) {
	    // ...
            boolean cached = false;
            boolean recycled = false;

            if (forceRecycle || holder.isRecyclable()) {
		// 因爲此時的 ViewHolder 有 FLAG_INVALID 標記,不會進入此 if 塊
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
			//...
                }
		// cached 仍爲 false,進入此 if 塊
                if (!cached) {
		    // 經過 RecycledViewPool 的 putRecycledView 方法緩存該 ViewHolder
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } 
			
	    // ...
        }
複製代碼

最終被移除的子 View 緩存到了 RecycledViewPool 中。

後面在調用 fill 方法進行佈局填充時,就能夠從 RecycledViewPool 中拿取緩存的 View。

Adapter#notifyItemChanged

該方法傳入一個 int 參數,表示要數據有更新的 item 的 position。

public final void notifyItemChanged(int position) {
        mObservable.notifyItemRangeChanged(position, 1);
    }
複製代碼

最終調用 RecyclerViewDataObserver 的 onItemRangeChanged 方法:

@Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // 會在 mAdapterHelper 中建立一個 UpdateOp,將信息保存起來
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            // 若是能夠進行更新操做,執行該方法
            triggerUpdateProcessor();
        }
    }
複製代碼

繼續看 triggerUpdateProcessor 方法:

void triggerUpdateProcessor() {
        // 判斷條件默認爲 false,執行 else 塊
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            // ...
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
複製代碼

在保存了一些信息後,仍是進行視圖重繪。來到了 layout 過程後,仍是以 LinearLayoutManager 爲例,此次先看下佈局過程的 step1,也就是 dispatchLayoutStep1 方法:

RecyclerView#dispatchLayoutStep1

private void dispatchLayoutStep1() {
        // ...
        
        processAdapterUpdatesAndSetAnimationFlags();
        
        // ...
    }
複製代碼

主要看 processAdapterUpdatesAndSetAnimationFlags 方法,從名字也能夠看出,它負責更新 adapter 的信息:

private void processAdapterUpdatesAndSetAnimationFlags() {
	// ...

        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }

	// ...
    }
複製代碼

這裏藉助了 mAdapterHelper,它最終又經過接口回調(回調了 markViewHoldersUpdated 方法)調用了 RecyclerView 的 viewRangeUpdate 方法:

void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
        // ...

        for (int i = 0; i < childCount; i++) {
            // ...
            
            if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                // (1)
                holder.addFlags(ViewHolder.FLAG_UPDATE);
                // ...
            }
        }
    }
複製代碼

該方法就是遍歷全部子 View,找到全部發生了改變的子 View,進行相關操做。這裏重點看註釋(1),爲改變的 ViewHolder 添加了 FLAG_UPDATE 標記。先記住這點,在後面會用到。

接下來看 onLayoutChildren 方法,和 notifyDataSetChanged 同樣,主要的不一樣之處也是在於 detachAndScrapAttachedViews 方法,該方法遍歷子 View,調用 scrapOrRecycleView 方法,下面看一下該方法:

LayoutManager#scrapOrRecycleView

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
	// ...
	
	// 此次 ViewHolder 沒有添加 FLAG_INVALID 標記,進入 else 塊
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
		// ...
        } else {
            detachViewAt(index);
            recycler.scrapView(view);
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }
複製代碼

這裏就和 notifyDataSetChanged 時不同了,因爲在視圖重繪前沒有給 ViewHolder 添加 FLAG_INVALID 標記,此次進入的是 else 塊。

首先將 View 從 RecyclerView 中 detach 掉(而不是 remove 掉)。而後在回收時,調用的是 Recycler 的 scrapView 方法。該方法在前面也分析過了,這裏再看一次:

void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
		
	// 知足這幾個條件中的一個就能夠進入 if 循環
	// 1. ViewHolder 設置了 FLAG_REMOVED 或 FLAG_INVALID 
	// 2. ViewHolder 沒有設置 FLAG_UPDATE 
	// 3. 沒有設置動畫或者動畫能夠重用該 ViewHolder 
        if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
            // ...
            
            mAttachedScrap.add(holder);
        } 
	// 不知足上述任意一個條件時,將 View 緩存到 mChangedScrap 中
	else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this, true);
            mChangedScrap.add(holder);
        }
    }
複製代碼

重點看判斷裏面的條件 2,從前面的分析能夠得知,對於發生改變的 ViewHolder,給它設置了 FLAG_UPDATE,因此它如今三個條件都不知足,進入 else 塊,而對於其餘的 ViewHolder,因爲沒有設置 FLAG_UPDATE,因此知足條件 2,進入 if 循環。

因此經過 notifyItemChanged 方法更新列表時,發生了改變的子 View 將被緩存到 ChangedScrap 中,而沒有發生改變的子 View 則緩存到 AttachedScrap 中,以後經過填充佈局的時候對於不一樣 item 就能夠從相應的 Scrap 緩存中獲得子 View。

另外,Scrap 緩存只做用於佈局階段,在 layout 的 step3 中將會清空 mAttachedScrap 和 mChangedScrap。

其實還有一個常見的場景是滑動操做,滑動出屏幕的子 View 將會緩存到 mCachedView,不過這裏就不詳細說了,在以後會在其餘文章專門分析滑動這塊。

後記

本文圍繞 Recycler 展開敘述,重點是要經過它的幾個成員變量了解它的緩存機制,四級緩存分別是什麼,是在什麼時候調用的,各自起到的做用,不一樣場景下使用哪一種緩存等。

Recycler 和 LayoutManager 的佈局以及動畫都有聯繫,例如 LayoutManager 負責佈局,它決定獲取子 View 和回收子 View 的時機,具體的工做就交由 Recycler 負責。這些會在以後對 RecyclerView 的其餘方面做分析時進行更詳細的說明。

參考

相關文章
相關標籤/搜索