Android 進(jìn)階學(xué)習(xí)(八) RecyclerView 學(xué)習(xí) (二) 關(guān)于RecyclerView 緩存機(jī)制

RecyclerView 的緩存機(jī)制還是非常值得到家參考的,先來說一下RecyclerView 關(guān)于緩存的方法, 關(guān)于RecyclerView 的緩存數(shù)據(jù)有兩個(gè)級(jí)別,一個(gè)是detach ,另一個(gè)就是remove , 關(guān)于detach 就是在RecyclerView 滑動(dòng)或者layout 時(shí)為了記錄屏幕內(nèi)條目信息而設(shè)定的,他主要的緩存的數(shù)據(jù)就是getChildCount列表所持有的數(shù)據(jù), 至于remove 就是為了緩存我們從數(shù)據(jù)列表所刪除的數(shù)據(jù),根據(jù)這個(gè)信息我們來從代碼來分析

   public final class Recycler { 
       ///這attach 相關(guān)的緩存的列表
       final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
       ///和remove 相關(guān)的緩存列表
       final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
       ///緩存池
       RecycledViewPool mRecyclerPool;
       private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
       //默認(rèn)的最大緩存?zhèn)€數(shù)  
       int mViewCacheMax = DEFAULT_CACHE_SIZE;
       static final int DEFAULT_CACHE_SIZE = 2;
}

我們看到Recycler 中有3個(gè)和緩存的相關(guān)的列表他們分別是 mAttachedScrap mCachedViews mRecyclerPool,我們來看看都是在哪些方法對(duì)這些列表做的操作,
我們先來看看detach方法,是如何將view 暫時(shí)和Recyclerview 分離的,并看一下他是將數(shù)據(jù)緩存的哪個(gè)列表中

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);
           }
       }

我們可以看到在detachAndScrapAttachedViews方法中顯示獲取了getChildCount ,緩存的就是RecyclerView的所持有的展示數(shù)據(jù),我們繼續(xù)看看scrapOrRecycleView這個(gè)方法是怎么操作的

scrapOrRecycleView

       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);
           }
       }

我們就看到根據(jù)viewHolder的狀態(tài),一部分走的是remove,我們這里暫時(shí)不管被remove的item,我們?nèi)タ纯磗crap的方法,即recycler.scrapView(view)

recycler.scrapView(view);

     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的狀態(tài),但是我們看到在detach時(shí)候,是將這個(gè)這個(gè)holder緩存在mAttachedScrap中,那么也就是說屏幕中的數(shù)據(jù)都放在這里,為什么要單獨(dú)給展示的list單獨(dú)一個(gè)緩存呢,其實(shí)他這個(gè)做是為了再下一次執(zhí)行addView 的過程中從mAttachedScrap獲取到數(shù)據(jù)就是已經(jīng)綁定過數(shù)據(jù)的holder了而且這個(gè)holder所對(duì)應(yīng)的數(shù)據(jù)還是正確的,這樣可以將bindViewHolder方法所執(zhí)行的時(shí)間省去了,可以加快view的繪制,

我們?cè)賮砜纯磖emove的方法

removeAndRecycleView

public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) {
           removeView(child);
           recycler.recycleView(child);
       }

先執(zhí)行了removeView 然后又recycleView 回收,我們來看看recycleView

recycler.recycleView(child);

    public void recycleView(@NonNull View view) {
           // This public recycle method tries to make view recycle-able since layout manager
           // intended to recycle this view (e.g. even if it is in scrap or change cache)
           ViewHolder holder = getChildViewHolderInt(view);
           if (holder.isTmpDetached()) {
               removeDetachedView(view, false);
           }
           if (holder.isScrap()) {
               holder.unScrap();
           } else if (holder.wasReturnedFromScrap()) {
               holder.clearReturnedFromScrapFlag();
           }
           recycleViewHolderInternal(holder);
       }

先是根據(jù)view找到這個(gè)view的holder,然后判斷這個(gè)holer.isScrap ,為什么其他地方只是說一下是holder的判斷,而這里卻單獨(dú)強(qiáng)調(diào)一下這個(gè)判斷,我們?cè)賮碚f一下detach 的過程,每次我們滑動(dòng)的過程中都會(huì)執(zhí)行detach 將數(shù)據(jù)放入到mAttachedScrap這個(gè)list中,那么這么多的數(shù)據(jù),這么頻繁的添加數(shù)據(jù)如果不刪除數(shù)據(jù)的話肯定是不行的,那么什么時(shí)候?qū)⑦@個(gè)數(shù)據(jù)刪除呢? 對(duì) !就是這里, holder.unScrap方法就是在remove過程中將數(shù)據(jù)從mAttachedScrap 中刪除的,其他情況我們看到了再說,這里就分析一下刪除的過程中的情況

holder.unScrap();

void unScrap() {
           mScrapContainer.unscrapView(this);
       }

在holder中他有調(diào)用了recycler的unscrapView方法,來處理接下來的邏輯,

####recycler.unscrapView(child);
      void unscrapView(ViewHolder holder) {
           if (holder.mInChangeScrap) {
               mChangedScrap.remove(holder);
           } else {
               mAttachedScrap.remove(holder);
           }
           holder.mScrapContainer = null;
           holder.mInChangeScrap = false;
           holder.clearReturnedFromScrapFlag();
       }

我們可以看到在unscrapView方法中,將數(shù)據(jù)移出了mAttachedScrap這個(gè)列表中的,
接下來我們繼續(xù)跟蹤remove過程中的recycleViewHolderInternal方法

recycleViewHolderInternal

void recycleViewHolderInternal(ViewHolder holder) {
           if (holder.isScrap() || holder.itemView.getParent() != null) {
               throw new IllegalArgumentException(
                       "Scrapped or attached views may not be recycled. isScrap:"
                               + holder.isScrap() + " isAttached:"
                               + (holder.itemView.getParent() != null) + exceptionLabel());
           }
           if (holder.isTmpDetached()) {
               throw new IllegalArgumentException("Tmp detached view should be removed "
                       + "from RecyclerView before it can be recycled: " + holder
                       + exceptionLabel());
           }
           if (holder.shouldIgnore()) {
               throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                       + " should first call stopIgnoringView(view) before calling recycle."
                       + exceptionLabel());
           }
           final boolean transientStatePreventsRecycling = holder
                   .doesTransientStatePreventRecycling();
           final boolean forceRecycle = mAdapter != null
                   && transientStatePreventsRecycling
                   && mAdapter.onFailedToRecycleView(holder);
           boolean cached = false;
           boolean recycled = false;
      
           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();
                   if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                       recycleCachedViewAt(0);
                       cachedViewSize--;
                   }
                   int targetCacheIndex = cachedViewSize;
                   if (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0  && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                       int cacheIndex = cachedViewSize - 1;
                       while (cacheIndex >= 0) {
                           int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                           if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                               break;
                           }
                           cacheIndex--;
                       }
                       targetCacheIndex = cacheIndex + 1;
                   }
                   mCachedViews.add(targetCacheIndex, holder);
                   cached = true;
               }
               if (!cached) {
                   addViewHolderToRecycledViewPool(holder, true);
                   recycled = true;
               }
           } else {

           }
           mViewInfoStore.removeViewHolder(holder);
           if (!cached && !recycled && transientStatePreventsRecycling) {
               holder.mOwnerRecyclerView = null;
           }
       }

這段代碼看起來很長(zhǎng),但是邏輯非常簡(jiǎn)單,前面很長(zhǎng)的拋異常,我們可以略過,主要從forceRecycle || holder.isRecyclable() 這個(gè)判斷開始,這個(gè)判斷的意思就是我們通過代碼設(shè)置希望對(duì)holder回收,在最大回收個(gè)數(shù)大于0的情況下,對(duì)mCachedViews 進(jìn)行操作,但是比較有意思的地方就是,如果現(xiàn)在的mCachedViews 滿了,我們需要將mCachedViews 列表中的數(shù)據(jù)放入到RecycledViewPool 中,代碼如下

if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                       recycleCachedViewAt(0);
                       cachedViewSize--;
 }

 void recycleCachedViewAt(int cachedViewIndex) {
           if (DEBUG) {
               Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
           }
           ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
           if (DEBUG) {
               Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
           }
           addViewHolderToRecycledViewPool(viewHolder, true);
           mCachedViews.remove(cachedViewIndex);
}
 
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
           clearNestedRecyclerViewIfNotNested(holder);
           if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
               holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
               ViewCompat.setAccessibilityDelegate(holder.itemView, null);
           }
           if (dispatchRecycled) {
               dispatchViewRecycled(holder);
           }
           holder.mOwnerRecyclerView = null;
           getRecycledViewPool().putRecycledView(holder);
       }

在為mCachedViews 清理出來數(shù)據(jù)后,再將我們需要remove的holder放入到mCachedViews 中,而mCachedViews 中的數(shù)據(jù)其實(shí)也是緩存的綁定完數(shù)據(jù)的holder,也就是如果這個(gè)holder是從mCachedViews 中匹配出來的,那么也不需要經(jīng)過bindViewHolder 這個(gè)過程,從上面我們看到mCachedViews 他的最大緩存這個(gè)是2個(gè),那是不是越大越好呢,肯定不是的,只要緩存的個(gè)數(shù)夠用其實(shí)就是可以了,但是我們什么時(shí)候需要對(duì)他擴(kuò)容來幫助我們來提高運(yùn)行速度呢,我們來做一個(gè)假設(shè)下面圖片這種情況,

image.png

如果我們使用的是一個(gè)GridLayoutManager 這個(gè)manager,每一行有3條數(shù)據(jù),那么最大2個(gè)的緩存池是肯定需要重新bindViewHoder的,這種情況我們就可以適當(dāng)?shù)膶⑦@個(gè)最大值擴(kuò)充一下,至于說是3還是6,這個(gè)就仁者見仁,智者見智了,

說完了數(shù)據(jù)緩存,我們?cè)賮砜纯次覀兪侨绾螌⒕彺娴臄?shù)據(jù)取出來的,

recycler.getViewForPosition(i);

getViewForPosition就是我們獲取到這個(gè)view 的入口,既然我么分析過了保存數(shù)據(jù)的過程,其實(shí)就可以猜測(cè)這個(gè)獲取的過程,肯定是最開始從detach 的mAttachedScrap 這個(gè)list里面獲取,因?yàn)樗蔷彺娴钠聊簧厦娴臄?shù)據(jù),也就是我么需要渲染的數(shù)據(jù),其次是mCachedViews 這個(gè)list,mCachedViews 的緩存list的size比較小,所以不能緩存太多數(shù)據(jù),然后是 RecycledViewPool 這個(gè)緩存池從這個(gè)里面獲取的holder可能是沒有綁定數(shù)據(jù),也可能綁定錯(cuò)誤的數(shù)據(jù),所以需要重新綁定數(shù)據(jù),最后才是new ,我么來具體看一下實(shí)現(xiàn)方法,由于這個(gè)里面的代碼比較長(zhǎng),我會(huì)刪掉一些不影響邏輯的方法

  @NonNull
       public View getViewForPosition(int position) {
           return getViewForPosition(position, false);
       }

       View getViewForPosition(int position, boolean dryRun) {
           return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
       }
 @Nullable
       ViewHolder tryGetViewHolderForPositionByDeadline(int position,
               boolean dryRun, long deadlineNs) {
           ....
           boolean fromScrapOrHiddenOrCache = false;
           ViewHolder holder = null;
           if (mState.isPreLayout()) {
               holder = getChangedScrapViewForPosition(position);
               fromScrapOrHiddenOrCache = holder != null;
           }
           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) {
                           holder.addFlags(ViewHolder.FLAG_INVALID);
                           if (holder.isScrap()) {
                               removeDetachedView(holder.itemView, false);
                               holder.unScrap();
                           } 
                        ....
                       }
                    ....
                   } 
                 ....
               }
           }
           if (holder == null) {
               final int offsetPosition = mAdapterHelper.findPositionOffset(position);
               final int type = mAdapter.getItemViewType(offsetPosition);
               if (mAdapter.hasStableIds()) {
                   holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                   ....
               }
               if (holder == null && mViewCacheExtension != null) {
                   final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
                  ....
               }
               if (holder == null) { 
                   holder = getRecycledViewPool().getRecycledView(type);
                   ....
               }
               if (holder == null) {     
                   holder = mAdapter.createViewHolder(RecyclerView.this, type);
                   ...
               }
           }  
           .....
           return holder;
       }

雖然我輸出了一大部分代碼,但是剩下的代碼還是比較多,邏輯非常清晰,我們一點(diǎn)一點(diǎn)來看,

我們先來看第一次獲取viewholder

if (mState.isPreLayout()) {
               holder = getChangedScrapViewForPosition(position);
               fromScrapOrHiddenOrCache = holder != null;
           }

我只知道isPreLayout和執(zhí)行動(dòng)畫有關(guān),但是他們之間到底什么是什么關(guān)系就不是很清楚了,這個(gè)過程還沒有分析,我們就先越過這個(gè)過程

再看第二個(gè)判斷,

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) {
                           holder.addFlags(ViewHolder.FLAG_INVALID);
                           if (holder.isScrap()) {
                               removeDetachedView(holder.itemView, false);
                               holder.unScrap();
                           } 
                        ....
                       }
                    ....
                   } 
                 ....
               }
           }

我們可以看到這個(gè)holder是通過getScrapOrHiddenOrCachedHolderForPosition 這個(gè)方法來找到,我們進(jìn)去看看他具體的實(shí)現(xiàn)邏輯,
在這之前來說一下我們?cè)赿etach的時(shí)候會(huì)將數(shù)據(jù)添加到mAttachedScrap這個(gè)list,我們知道了在remove的時(shí)候我們會(huì)將這個(gè)holder移出mAttachedScrap,從上面這個(gè)方法中我們看到同樣進(jìn)行了holder.unScrap();,也就是在如果在mAttachedScrap找到這個(gè)holder也會(huì)將它在list移除

getScrapOrHiddenOrCachedHolderForPosition

 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
           final int scrapCount = mAttachedScrap.size();
           for (int i = 0; i < scrapCount; i++) {
               final ViewHolder holder = mAttachedScrap.get(i);
               if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                       && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                   holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                   return holder;
               }
           }
           if (!dryRun) {
               View view = mChildHelper.findHiddenNonRemovedView(position);
               if (view != null) {
                   final ViewHolder vh = getChildViewHolderInt(view);
                   mChildHelper.unhide(view);
                   int layoutIndex = mChildHelper.indexOfChild(view);
                   mChildHelper.detachViewFromParent(layoutIndex);
                   scrapView(view);
                   vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                           | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                   return vh;
               }
           }

           final int cacheSize = mCachedViews.size();
           for (int i = 0; i < cacheSize; i++) {
               final ViewHolder holder = mCachedViews.get(i);
               if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                   if (!dryRun) {
                       mCachedViews.remove(i);
                   }          
                   return holder;
               }
           }
           return null;
       }

我們看到在這個(gè)getScrapOrHiddenOrCachedHolderForPosition 方法中首先遍歷mAttachedScrap,如果mAttachedScrap這個(gè)position 和我們需要查找的position相同則返回,如果沒有找到則根據(jù)我們根據(jù)dryRun來判斷,我們看到在View getViewForPosition(int position)方法中這個(gè)dryRun向下傳遞的是false,那么這里就是調(diào)用的從mChildHelper.findHiddenNonRemovedView 這個(gè)方法,

 View findHiddenNonRemovedView(int position) {
       final int count = mHiddenViews.size();
       for (int i = 0; i < count; i++) {
           final View view = mHiddenViews.get(i);
           RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
           if (holder.getLayoutPosition() == position
                   && !holder.isInvalid()
                   && !holder.isRemoved()) {
               return view;
           }
       }
       return null;
   }

這就是第一次查找,如果第一次查找我們沒有找到,那么就應(yīng)該去下一個(gè)地方繼續(xù)查找

       final int offsetPosition = mAdapterHelper.findPositionOffset(position);
       final int type = mAdapter.getItemViewType(offsetPosition);
        if (mAdapter.hasStableIds()) {
                   holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                           type, dryRun);
                   if (holder != null) {
                       // update position
                       holder.mPosition = offsetPosition;
                       fromScrapOrHiddenOrCache = true;
                   }
        }

先找到offsetPosition ,根絕offsetPosition 查找type,最后根據(jù)itemdid 和 type 去getScrapOrCachedViewForId方法查找

getScrapOrCachedViewForId

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
           final int count = mAttachedScrap.size();
           for (int i = count - 1; i >= 0; i--) {
               final ViewHolder holder = mAttachedScrap.get(i);
               if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                   if (type == holder.getItemViewType()) {
                       holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                       if (holder.isRemoved()) {
                           if (!mState.isPreLayout()) {
                               holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                       | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                           }
                       }
                       return holder;
                   } else if (!dryRun) {
                       mAttachedScrap.remove(i);
                       removeDetachedView(holder.itemView, false);
                       quickRecycleScrapView(holder.itemView);
                   }
               }
           }
           final int cacheSize = mCachedViews.size();
           for (int i = cacheSize - 1; i >= 0; i--) {
               final ViewHolder holder = mCachedViews.get(i);
               if (holder.getItemId() == id) {
                   if (type == holder.getItemViewType()) {
                       if (!dryRun) {
                           mCachedViews.remove(i);
                       }
                       return holder;
                   } else if (!dryRun) {
                       recycleCachedViewAt(i);
                       return null;
                   }
               }
           }
           return null;
       }

從上面的方法我們看到同樣的還是遍歷mAttachedScrap 這個(gè)list,只不過這次判斷的邏輯是 itemid 和type,同時(shí)相同才可以,如果itemid相同,type不同那么則將這個(gè)holder 放入到mCachedViews 中,接下來就是遍歷mCachedViews,繼續(xù)尋找,
若果上面兩種情況都沒找到,就會(huì)繼續(xù)去mViewCacheExtension 中尋找,但是mViewCacheExtension這個(gè)需要我們自己去實(shí)現(xiàn),這里就不多說了,繼續(xù)看下面

holder = getRecycledViewPool().getRecycledView(type);

這個(gè)就很簡(jiǎn)單了,從緩存池根據(jù)type獲取,
最后一個(gè)就是new

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

整個(gè)過程我們就分析完成了,一個(gè)非常巧妙的設(shè)計(jì),

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

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

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