天天看點

RecyclerView的Recycler

處理回收複用相關工作的

參考部落格:https://www.jianshu.com/p/9306b365da57

幾個重要的集合

mAttachedScrap

緩存顯示到螢幕的item的holder。臨時存放onLayout過程中的childern。

應用場景:RecyclerView在onLayout時會先把childern移除掉,在重新添加進去

mChangedScrap 看名字應該跟 ViewHolder 的資料發生變化時有關吧
mCachedViews 滑動中的回收和複用都先處理這個List,可以直接添加到view中,不需要重新onBindViewHolder。注意僅僅緩存的原來位置的item,其他位置的item無法使用。好像是 ViewPager 之類的緩存一樣
mRecyclerPool 存在這裡的holder資料資訊會被重置,需要重新調用onBindViewHolder來綁定

RecyclerView的繪制顯示

最終繼承于View,那麼在View繪制的過程分三步:onMeasure測量view的大小,onLayout确定view的布局,onDraw将view繪制到界面上。其中重點在onLayout中。

1)LayoutManager的next()

根據debug從onLayout過程中對應的各個方法的調用過程,最終調用到對應的LayoutManager的next()。如圖所示

RecyclerView的Recycler

那麼進入到next()看看裡面有些什麼邏輯。

2)next()

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

從上面的代碼中可以看到又調用recycler的getViewForPosition()方法,來擷取對應position的view。通過跟進recycler的getViewForPosition()方法,最終進入到tryGetViewHolderForPositionByDeadline()傳回可用的holder。

RecyclerView的滑動過程

根據debug發現最終也是調用到到tryGetViewHolderForPositionByDeadline()傳回可用的holder。

RecyclerView的Recycler

下面重點介紹下緩存讀取的過程

RecyclerView的複用機制中從緩存中讀取holder

主要在tryGetViewHolderForPositionByDeadline()方法中實作。

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
           // ...... 省略代碼          
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
           // ...... 省略代碼
         // 1)從mAttachedScrap或者mCachedViews找到可用的holder
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                // ...... //省略代碼
                }
            }
            if (holder == null) {
               //  ...... //省略代碼
                final int type = mAdapter.getItemViewType(offsetPosition);
          // 2) 查找設定過stableId的holder,這個需要開發者去設定才生效
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                       // ...... //省略代碼
                    }
                }
          3)查找開發者設定的緩存方式,這個一般隻有開發者設定過才有效
                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 (holder == null) { // fallback to pool
          4)查找ViewPool中的緩存的holder
                    holder = getRecycledViewPool().getRecycledView(type);
                 //...... //省略代碼  
                }
          5)如果上述4種方式下來都沒有可用holder,則調用mAdapter的createViewHolder來建立holder
                if (holder == null) {             
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    //...... //省略代碼 
                }
            }
           //...... //省略代碼 
           //設定holder的LayoutParams
           //...... //省略代碼 
            return holder;
        }
           

在該方法裡面涉及了RecyclerView的所有緩存的處理邏輯。

1)首先從mAttachedScrap或者mCachedViews找到可用的holder。

可以從getScrapOrHiddenOrCachedHolderForPosition()中可以看到這個裡面其實就是從mAttachedScrap或者mCachedViews兩個集合中找到可用的holder。

mAttachedScrap這個裡面主要就是onLayout過程中臨時存放的item的holder。mCachedViews中對應的就是之前滑出螢幕的item的holder。預設到為2,如果超出則将第0個移除到ViewPool中。注意這個隻能是對應position上的holder才可以用。

從中取出的holder都會去驗證可行性

2)若第一步中沒有找到可用的holder,則繼續向下執行,到getScrapOrCachedViewForId()。

這個方法裡面主要就是開發者設定過stableId的,會重複去mAttachedScrap和mCachedViews查找可用的holder,注意這個stableId并不是xml設定的id,是Adapter持有一個屬性,除非重寫Adapter 的 setHasStableIds(),才會進行這一步的查找。

3)若2中還是沒有找到可用的holder,則繼續向下執行,執行到mViewCacheExtension!=null 的情況。

mViewCacheExtension是提供的自定義實作的類,需要開發者重寫 getViewForPositionAndType() 方法來實作自己的複用政策,忽略該步。

4)若3中還是沒有找到可用的holder,則進入到getRecycledViewPool().getRecycledView(type)。

這個是RecyclerView中的第四級緩存,RecycledViewPool根據不同的item type建立不同的list。在getRecycledView,在複用的時候,隻要ViewPool中相同的type有holder緩存的話,就從最後一個拿出來複用。

這個集合裡面的元素,隻有緩存的holder超過mCachedViews設定的個數的時候,才會将holder加入到這裡面來。注意加入到這裡的holder由于調用holder.resetInternal();重置holder,是以從這裡拿的holder需要重新onBindViewHolder。

7)通過上面的四步之後,發現若holder還是null,則調用mAdapter.createViewHolder重新建立holder

最後将holder設定LayoutParam,傳回。

RecyclerView的回收機制

1、概述

先往mCacheScrap放2個,當超出2個之後,從mCacheScrap中拿出一個給到ViewPool等待複用。

2、代碼解讀

1)

拿LinearLayoutManager舉例說明,在向下滑動的過程中,根據下面debug的方法跟蹤,進入到recycleByLayoutState()

RecyclerView的Recycler

2)recycleByLayoutState()

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }
           

這個裡面的代碼主要就是根據滑動是向上或者向下滑動來确定怎麼回收,我們拿向上滑動舉例說明下,進入到recycleViewsFromStart()中。

3)recycleViewsFromStart()

根據debug的日志可以看到,最終進入到recycler的recycleViewHolderInternal()進行回收holder

RecyclerView的Recycler

進入到recycleViewHolderInternal()進行檢視是怎麼回收的

3)recycleViewHolderInternal()

void recycleViewHolderInternal(ViewHolder holder) {
           //......省略代碼,省略代碼
           //取判斷是否可以将holder放入到mCachedViews中
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                   //超出mCachedViews設定的數量之後,将集合中的第一個元素取出,放到ViewPool中
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize;
                   //.......省略代碼,判斷是否可以放入到mCachedViews中
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
               //如果仍沒有被緩存,則加入到ViewPool中
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
             //NOTE: A view can fail to be recycled when it is scrolled off while an 
                // animation runs.
              //.......省略代碼
            }
            // even if the holder is not removed, we still call this method so that it is
            // removed from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }

        }
           

從代碼中可以看出,首先會先去判斷mCachedViews中的集合元素是否超過設定的最大值,若超過了,則将第一個元素移到ViewPool中;若沒有超過最大元素,則放入到mCachedViews集合中等待複用。

總結

1)RecyclerView在緩存和回收的過程中,會遵循先複用後回收的原則。是以就有以下這種情況:

第一屏顯示完之後,在加載第二屏的view的時候,再将第一屏的view移除螢幕的時候,第二屏的view會重新從onCreateHolder到onBinderView的過程,因為這個時候沒有view緩存,是以無法取得緩存的holder,是以第二屏無法複用第一屏的資料。加載完第二屏的view之後,才會将第一屏的view添加到mCachedViews或ViewPool中。

09-25 17:16:55.966 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:16:55.968 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 0
09-25 17:16:55.970 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:16:55.971 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 1
09-25 17:16:55.972 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:16:55.975 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 2
09-25 17:16:55.976 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:16:55.979 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 3
09-25 17:16:55.980 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:16:55.983 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 4
09-25 17:17:27.745 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:17:27.750 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 5
09-25 17:17:27.828 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:17:27.832 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 6
09-25 17:17:27.839 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:17:27.841 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 7
09-25 17:17:27.924 12821-12821/com.j1.aidl W/RecyclerViewActivity: onCreateViewHolder --- 
09-25 17:17:27.926 12821-12821/com.j1.aidl D/RecyclerViewActivity: onBindViewHolder ,holder.position = null,position = 8
           

該例子中一屏含有5個item,在顯示第二屏的item的時候,仍然會從onCreateViewHolder到onBindViewHolder 

2)緊接着1繼續滑動,發現明明顯示了5個item,發現卻隻有3個走了onBindViewHolder

原理:當第一行在回收的時候,會先放入的mCacheView,滿了再将舊的移到ViewPool中,是以5個卡位其中2個放到mCacheView,3個緩存到viewpool中,在ViewPool中的需要重新bind資料,而mCacheView直接可以複用。

至于是哪2個緩存在 mCachedViews,這是由 LayoutManager 控制