RecyclerView源碼學(xué)習(xí)筆記二(緩存機(jī)制)

在本文我們繼續(xù)學(xué)習(xí)RecyclerView緩存的相關(guān)知識客税。
緩存分為緩存取出和緩存存入惨恭,首先來分析下緩存取出:

一系草、緩存取出

在上一篇文章中,我們分析到了一個(gè)比較關(guān)鍵的方法--layoutChunk唆涝,再來看下其源碼:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                 LayoutState layoutState, LayoutChunkResult result) {
    //獲取view
    View view = layoutState.next(recycler);
    //代碼省略......
}

這里只保留了一行很重要的代碼View view = layoutState.next(recycler);找都,看下next方法。

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

這個(gè)next方法就是從緩存中來獲取view廊酣,首先是判斷mScrapList是否為空能耻,不為空就調(diào)用nextViewFromScrapList();方法。這里是以LinearLayoutManager來分析的亡驰,mScrapList是LinearLayoutManager的一個(gè)成員晓猛,它主要與動(dòng)畫相關(guān),因此這里就不再過多的分析它了凡辱。接著往下看占锯,調(diào)用了Recycler的getViewForPosition(int position)方法组去,Recycler是RecyclerView的一個(gè)內(nèi)部類凛剥,它主要負(fù)責(zé)RecyclerView的緩存管理熔恢。看下這個(gè)內(nèi)部類:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

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

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;

    static final int DEFAULT_CACHE_SIZE = 2;

    //代碼省略......

    這里對幾個(gè)成員做一下說明:
    mAttachedScrap:存儲當(dāng)前還在屏幕中的ViewHolder乳乌,意思就是剛剛從屏幕中分離出來蚓曼,然后又即將添加到屏幕上的ViewHolder。
    比如說钦扭,RecyclerView數(shù)據(jù)發(fā)生更新纫版,調(diào)用notifyDataSetChanged()方法刷新界面,在列表數(shù)據(jù)刷新的時(shí)候存儲屏幕內(nèi)被移除的ViewHolder客情。
    mChangedScrap:存儲的是當(dāng)前被更新的ViewHolder其弊,比如說調(diào)用了adapter調(diào)用notifyItemChanged方法。
    mAttachedScrap和mChangedScrap共同組成第一級緩存膀斋。也叫屏幕內(nèi)緩存梭伐,不參與RecyclerView在滑動(dòng)狀態(tài)下ViewHolder的回收和復(fù)用。

    mCachedViews:默認(rèn)大小為2仰担,也叫離屏緩存糊识。即在Recycler View滑動(dòng)的時(shí)候,回收滑出屏幕的ViewHolder以及提供滑動(dòng)到屏幕內(nèi)的ViewHolder的復(fù)用摔蓝。
    mCachedViews構(gòu)成第二級緩存赂苗。

    mViewCacheExtension:是一個(gè)抽象靜態(tài)類,用于充當(dāng)附加的緩存池贮尉。當(dāng)RecyclerView從第一級緩存中沒有找到需要的view時(shí)拌滋,就會從ViewCacheExtension中查找。
    不過這個(gè)緩存是由我們開發(fā)者自己維護(hù)的猜谚,如果沒有設(shè)置這個(gè)緩存败砂,就不會啟用它赌渣。一般情況下我們也不會去設(shè)置它,系統(tǒng)已經(jīng)預(yù)先提供了兩級緩存了昌犹。
    如果有特殊需求坚芜,比如要在調(diào)用系統(tǒng)的緩存池之前,返回一個(gè)特定的視圖斜姥,才會用到它鸿竖。

    RecycledViewPool:非常強(qiáng)大的緩存,其默認(rèn)大小為5疾渴,用于在多個(gè)嵌套的RecyclerView之間緩存共享的viewholder千贯,接下來會介紹它屯仗。
}

參考網(wǎng)址:https://juejin.im/entry/5c66ce2b51882562c704ebad

從上面我們可以看到搞坝,RecyclerView緩存的是viewholder,這也是它跟ListView不同的地方魁袜,ListView需要我們手動(dòng)判斷緩存是否為空桩撮,而RecyclerView由于緩存的是viewholder,直接就在內(nèi)部就幫我們判斷好了峰弹,我們只需要使用即可店量。

重點(diǎn)看下RecycledViewPool這個(gè)緩存類:

/**
 * RecycledViewPool lets you share Views between multiple RecyclerViews.
 * 在多個(gè)RecyclerView之間共享緩存的viewholder
 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
 * <p>
 * RecyclerView automatically creates a pool for itself if you don't provide one.
 * 如果我們沒有為RecyclerView設(shè)置RecycledViewPool,RecyclerView會自動(dòng)為自己創(chuàng)建一個(gè)鞠呈,當(dāng)然了這個(gè)自己自動(dòng)創(chuàng)建的只能自己單獨(dú)使用
 */
public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;

    /**
     * Tracks both pooled holders, as well as create/bind timing metadata for the given type.
     * <p>
     * Note that this tracks running averages of create/bind time across all RecyclerViews
     * (and, indirectly, Adapters) that use this pool.
     * <p>
     * 對于緩存的ViewHolder的統(tǒng)一管理
     */
    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<>();

    //代碼省略......

    /**
     * Sets the maximum number of ViewHolders to hold in the pool before discarding.
     * 設(shè)置RecyclerViewPool能緩存的ViewHolder的最大數(shù)量融师。
     */
    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);
        }
    }

    /**
     * Returns the current number of Views held by the RecycledViewPool of the given view type.
     * 獲取RecyclerViewPool緩存的ViewHolder數(shù)量。
     */
    public int getRecycledViewCount(int viewType) {
        return getScrapDataForType(viewType).mScrapHeap.size();
    }

    /**
     * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
     * present.
     * 從RecyclerViewPool中獲取一個(gè)ViewHolder緩存蚁吝,并將其從RecyclerViewPool緩存中移除旱爆。
     */
    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        //根據(jù)viewtype獲取緩存
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
             //從pool中刪除并返回符合條件的viewholder
            return scrapHeap.remove(scrapHeap.size() - 1);
        }
        return null;
    }

    //代碼省略......

    /**
     * Add a scrap ViewHolder to the pool.
     * <p>
     * If the pool is already full for that ViewHolder's type, it will be immediately discarded.
     *
     * @param scrap ViewHolder to be added to the pool.
     */
    public void putRecycledView(ViewHolder scrap) {
        //獲取要緩存的viewholder的viewType
        final int viewType = scrap.getItemViewType();
        //根據(jù)viewType來獲取viewholder要保存的位置
        final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
        //如果緩存區(qū)滿了,則什么也不做窘茁,直接返回
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
            return;
        }
        if (DEBUG && scrapHeap.contains(scrap)) {
            throw new IllegalArgumentException("this scrap item already exists");
        }
        scrap.resetInternal();
        //將viewholder存入緩存中
        scrapHeap.add(scrap);
    }

    //代碼省略......

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

從名字上看怀伦,RecycledViewPool是一個(gè)緩存池,其內(nèi)部實(shí)際上是通過一個(gè)默認(rèn)大小為5的ArrayList來實(shí)現(xiàn)的山林,我們也可以手動(dòng)設(shè)置其緩存容量房待。然后ArrayList又是用ScrapData這個(gè)內(nèi)部類來統(tǒng)一管理,最后再將這個(gè)ScrapData放到SparseArray中驼抹,組成緩存的最終形勢桑孩。我們在實(shí)現(xiàn)了自己的RecycledViewPool后,只需要調(diào)用RecyclerView#setRecycledViewPool(RecycledViewPool)就可以為多個(gè)RecyclerView設(shè)置共享的RecycledViewPool框冀。
順帶說下洼怔,這個(gè)SparseArray內(nèi)部其實(shí)是由兩個(gè)數(shù)組來實(shí)現(xiàn)的,一個(gè)數(shù)組存儲鍵左驾,另一個(gè)數(shù)組存儲值镣隶,其效率比HashMap要高一點(diǎn)极谊,但是只能存儲少量數(shù)據(jù),數(shù)據(jù)量大的話其效率不如HashMap安岂。在這里轻猖,鍵是viewType,值是scrapData域那。

分析完了Recycler及RecycledViewPool后咙边,我們繼續(xù)跟進(jìn)getViewForPosition方法:

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

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

最終調(diào)用了 tryGetViewHolderForPositionByDeadline方法。

ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    //代碼省略......
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        //默認(rèn)情況下是false次员,只有動(dòng)畫的情況下才是true
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    //第一次嘗試败许,從attachScrap和cachedSCrap中查找viewholder
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                //如果獲取到的viewholder不可用于當(dāng)前這個(gè)位置
                // recycle holder (and unscrap if relevant) since it can't be used
                if (!dryRun) {
                    //如果viewholder可以從scrap / cache 中移除
                    // we would like to recycle this but need to make sure it is not used by
                    // animation logic etc.
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    //移除這個(gè)viewholder
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                //將viewholder置空,表示沒有查找到淑蔚,繼續(xù)往下查找
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //代碼省略......
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        //第二次嘗試市殷,對應(yīng)hasStableId情況
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        //如果第二次嘗試后holder仍為空,并且我們設(shè)置了自定義緩存(ViewCacheExtension)
        if (holder == null && mViewCacheExtension != null) {
            //從ViewCacheExtension中查找
            final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
            if (view != null) {
                //根據(jù)查找到的view獲取相應(yīng)的viewholder
                holder = getChildViewHolder(view);
                if (holder == null) {
                    throw new IllegalArgumentException("getViewForPositionAndType returned"
                            + " a view which does not have a ViewHolder"
                            + exceptionLabel());
                } else if (holder.shouldIgnore()) {
                    throw new IllegalArgumentException("getViewForPositionAndType returned"
                            + " a view that is ignored. You must call stopIgnoring before"
                            + " returning this view." + exceptionLabel());
                }
            }
        }
        //前面的緩存均為獲取到viewholder刹衫,從RecycledViewPool中查找
        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);
                }
            }
        }
        //從緩存中查找失敗醋寝,調(diào)用adapter的createViewHolder方法創(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");
            }
        }
    }

    //代碼省略......
    return holder;
}

我們從上往下慢慢看:

根據(jù)position獲取緩存
if (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}

/**
 * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
 * {@link LayoutManager} layout items where they will be at the beginning of a set of
 * predictive item animations.
 * 在有動(dòng)畫情況下這里返回true,否則返回false
 */
public boolean isPreLayout() {
    return mInPreLayout;
}

//從mChangedScrap緩存中獲取viewholder
ViewHolder getChangedScrapViewForPosition(int position) {
    // If pre-layout, check the changed scrap for an exact match.
    final int changedScrapSize;
    if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
        return null;
    }
    // find by position
    //按照位置查找
    for (int i = 0; i < changedScrapSize; i++) {
        final ViewHolder holder = mChangedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    // find by id
    //按照id查找
    if (mAdapter.hasStableIds()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
            final long id = mAdapter.getItemId(offsetPosition);
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
        }
    }
    return null;
}

首先判斷isPreLayout()是否為true带迟,這里主要是跟動(dòng)畫有關(guān)音羞,如果為true,也就是有動(dòng)畫仓犬,則調(diào)用 getChangedScrapViewForPosition(position)方法嗅绰,從這個(gè)方法的名字當(dāng)中我們也可以看出是從changedScraps緩存中獲取。
如果上面isPreLayout()為false時(shí)或者isPreLayout()為true并且沒有獲取到viewholder時(shí)搀继,開始從從attachScrap和cachedScrap緩存中查找(這里是按照position來查找的)窘面。

if (holder == null) {
    //從attachScrap和cachedScrap緩存中查找viewholder
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
        //如果查找到viewholder不適合當(dāng)前位置
        if (!validateViewHolderForOffsetPosition(holder)) {
            // recycle holder (and unscrap if relevant) since it can't be used
            //dryRun標(biāo)記表示viewholder是否可以從緩存中移除,為false表示可以移除律歼,這里傳入的是false
            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);
                //從緩存中移除viewholder
                if (holder.isScrap()) {
                     removeDetachedView(holder.itemView, false);
                     holder.unScrap();
                } else if (holder.wasReturnedFromScrap()) {
                     holder.clearReturnedFromScrapFlag();
                }
                recycleViewHolderInternal(holder);
            }
            //將viewholder置空民镜,繼續(xù)下面的查找
            holder = null;
        } else {
            fromScrapOrHiddenOrCache = true;
        }
    }
}

這里重點(diǎn)關(guān)注下getScrapOrHiddenOrCachedHolderForPosition這個(gè)方法,從名字就可以看出是按照position來查找緩存的险毁。

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
    final int scrapCount = mAttachedScrap.size();

    // Try first for an exact, non-invalid match from scrap.
    //從mAttachedScrap中查找
    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;
        }
    }
    //dryRun為false
    if (!dryRun) {
        //從hiddenView中查找view
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
            // This View is good to be used. We just need to unhide, detach and move to the
            // scrap list.
            //根據(jù)查找到的view獲取viewholder
            final ViewHolder vh = getChildViewHolderInt(view);
            //將view從hiddenView中移除
            mChildHelper.unhide(view);
            int layoutIndex = mChildHelper.indexOfChild(view);
            if (layoutIndex == RecyclerView.NO_POSITION) {
                throw new IllegalStateException("layout index should not be -1 after "
                        + "unhiding a view:" + vh + exceptionLabel());
            }
            mChildHelper.detachViewFromParent(layoutIndex);
            //將view添加到scrap緩存中(這里是添加到了mAttachedScrap或者mChangedScrap中)
            scrapView(view);
            vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                    | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
            return vh;
        }
    }

    // Search in our first-level recycled view cache.
    //從mCachedViews中查找
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // invalid view holders may be in cache if adapter has stable ids as they can be
        // retrieved via getScrapOrCachedViewForId
        //如果查找到viewholder是有效的制圈,并且viewholder的位置和position的位置相同
        if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
            if (!dryRun) {
                //從緩存中刪除這個(gè)viewholder
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            //返回holder
            return holder;
        }
    }
    return null;
}

簡要總結(jié)下getScrapOrHiddenOrCachedHolderForPosition的流程:

  • 1、首先從mAttachedScrap中查找viewholder畔况,如果找到了并且驗(yàn)證符合條件鲸鹦,直接返回。
  • 2跷跪、否則從hiddenView中查找view馋嗜,再根據(jù)這個(gè)view獲取viewholder,并將view從hiddenView中移除吵瞻,接著將viewholder保存到緩存中(mAttachedScrap或者mChangedScrap)葛菇,最后返回甘磨。
  • 3、如果1和2中都沒有獲取到眯停,則從mCachedViews中查找viewholder济舆,并校驗(yàn)這個(gè)viewholder是否滿足條件,如果滿足莺债,將其從mCachedViews中刪除滋觉,并返回viewholder。

沿著tryGetViewHolderForPositionByDeadline方法繼續(xù)往下分析齐邦,上面是按照position來查找緩存的椎侠,如果沒有查找到,那么按照stableId再次進(jìn)行查找措拇。

根據(jù)itemId和viewType獲取緩存
if (holder == null) {
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    //省略.......
    final int type = mAdapter.getItemViewType(offsetPosition);
    // 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;
        }
    }

首先通過mAdapter.getItemViewType(offsetPosition)這個(gè)方法獲取type我纪,這個(gè)getItemViewType也是我們設(shè)置RecyclerView列表多種布局的一個(gè)重要方法,并且后面所有緩存的獲取都是根據(jù)這個(gè)type來的儡羔。也就是說前面介紹的也就是注釋中 0)1) 獲取的緩存是沒有區(qū)分type宣羊,接下來的緩存獲取都是區(qū)分type的璧诵。
接下來判斷mAdapter.hasStableIds()是否為true汰蜘。

/**
 * Returns true if this adapter publishes a unique <code>long</code> value that can
 * act as a key for the item at a given position in the data set. If that item is relocated
 * in the data set, the ID returned for that item should be the same.
 *
 * @return true if this adapter's items have stable IDs
 */
public final boolean hasStableIds() {
    return mHasStableIds;
}

這里關(guān)注下getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)這個(gè)方法,這里通過adapter的getItemId方法獲取相應(yīng)的itemId之宿,這個(gè)getItemId方法我們可重寫也可不重寫族操。

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    // Look in our attached views first
    final int count = mAttachedScrap.size();
    //從mAttachedScrap緩存中查找
    for (int i = count - 1; i >= 0; i--) {
        final ViewHolder holder = mAttachedScrap.get(i);
        //判斷緩存中的viewholder所在的itemId是否符合傳入的itemId
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            //判斷type
            if (type == holder.getItemViewType()) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                if (holder.isRemoved()) {
                    // this might be valid in two cases:
                    // > item is removed but we are in pre-layout pass
                    // >> do nothing. return as is. make sure we don't rebind
                    // > item is removed then added to another position and we are in
                    // post layout.
                    // >> remove removed and invalid flags, add update flag to rebind
                    // because item was invisible to us and we don't know what happened in
                    // between.
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                //itemId和viewType都滿足,直接返回holder
                return holder;
            } else if (!dryRun) {
                //如果viewholder的itemId符合條件比被,但是viewType不符合條件
                // if we are running animations, it is actually better to keep it in scrap
                // but this would force layout manager to lay it out which would be bad.
                // Recycle this scrap. Type mismatch.
                //從緩存中移除viewholder
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                //將viewholder緩存到RecyclerViewPool中
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    // Search the first-level cache
    //從mCachedViews緩存中查找
    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        final ViewHolder holder = mCachedViews.get(i);
        //判斷緩存中的viewholder所在的itemId是否符合傳入的itemId
        if (holder.getItemId() == id) {
            //判斷type
            if (type == holder.getItemViewType()) {
                //如果viewholder的itemId符合條件色难,但是viewType不符合條件
                if (!dryRun) {
                    //從緩存中移除viewholder
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                //將viewholder從mCachedViews中移除,并緩存到RecyclerViewPool中
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

這個(gè)方法其實(shí)和上面的getScrapOrHiddenOrCachedHolderForPosition方法差不多等缀,區(qū)別是該方法是按照itemId和viewType來判斷的枷莉,而getScrapOrHiddenOrCachedHolderForPosition是按照position來判斷的。根據(jù)itemId和viewType來查找的大致流程如下:

image.png

從自定義緩存中查找(ViewCacheExtension)

當(dāng)前面的查找都沒有找到時(shí)尺迂,程序就來到了這里笤妙,從我們的自定義緩存(ViewCacheExtension)中來查找。

if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
    //從ViewCacheExtension中獲取view
    final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
    if (view != null) {
        //根據(jù)view獲取viewholder
        holder = getChildViewHolder(view);
        if (holder == null) {
            throw new IllegalArgumentException("getViewForPositionAndType returned"
            + " a view which does not have a ViewHolder"+ exceptionLabel());
        } else if (holder.shouldIgnore()) {
            throw new IllegalArgumentException("getViewForPositionAndType returned"
            + " a view that is ignored. You must call stopIgnoring before"
            + " returning this view." + exceptionLabel());
        }
    }
}

代碼很簡單噪裕,主要是看下這個(gè)ViewCacheExtension到底是啥:

/**
 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
 * be controlled by the developer.
 */
public abstract static class ViewCacheExtension {
    /**
     * Returns a View that can be binded to the given Adapter position.
     */
    public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}

這里就一個(gè)抽象類蹲盘,內(nèi)部一個(gè)抽象方法。對于這個(gè)自定義緩存的介紹膳音,直接引用了【進(jìn)階】RecyclerView源碼解析(二)——緩存機(jī)制里面的評價(jià):

1召衔、首先這個(gè)類基本上沒有什么限制,也就是說無論是緩存使用的數(shù)據(jù)結(jié)構(gòu)還有緩存算法(LRU還是什么)完全自定義祭陷,都由開發(fā)者自己決定苍凛,這一點(diǎn)可以說既給了開發(fā)者很大的便利趣席,也給開發(fā)者帶來了很大的隱患。
2醇蝴、對于平常的緩存吩坝,我們的理解在怎么說至少get-add|push-pop都是成對出現(xiàn),為什么這樣說的哑蔫,也就是緩存至少有進(jìn)也有出钉寝。而這里可以看到這里的抽象類只定義了出的方法,也就是只出不進(jìn)闸迷,進(jìn)的時(shí)機(jī)嵌纲,大小,時(shí)效等完全沒有規(guī)定腥沽。

在日常開發(fā)中逮走,我們也很少去自定義這一級的緩存,因?yàn)樗鼛缀跤貌簧稀?/p>

從RecyclerViewPool中查找

RecyclerView與ListView的另一個(gè)不同之處是RecyclerView提供了RecyclerViewPool今阳,這是一個(gè)在多個(gè)RecyclerView之間提供共享view的緩存师溅。比較常見的場景是RecyclerView和RecyclerView之間的嵌套,RecyclerViewPool為這些個(gè)嵌套的RecyclerView內(nèi)view的整體復(fù)用提供了便利盾舌。

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

對于RecyclerViewPool在本文的前面已經(jīng)有介紹墓臭,這里就不再說了,我們繼續(xù)往下看妖谴。

調(diào)用adapter的createViewHolder創(chuàng)建ViewHolder

如果經(jīng)過上面的多級緩存查找仍然沒有查找到的話窿锉,程序最終會調(diào)用調(diào)用adapter的createViewHolder方法來創(chuàng)建一個(gè)viewholder。

if (holder == null) {
    long start = getNanoTime();
    //代碼省略......
    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);
        }
    }
    //代碼省略......
}

沒什么好說的膝舅,直接調(diào)用mAdapter.createViewHolder(RecyclerView.this, type)創(chuàng)建嗡载,在這個(gè)方法里面最終調(diào)用了我們繼承并實(shí)現(xiàn)的onCreateViewHolder方法。

至此仍稀,RecyclerView的緩存取出過程總算是分析完了洼滚,不過這里也只是簡要的介紹了下,還有很多細(xì)節(jié)由于水平限制沒有詳細(xì)介紹技潘。最后再總結(jié)下:

  • RecyclerView一共有三級緩存(也有人說是四級緩存遥巴,這個(gè)每個(gè)人的角度不一樣,因此會有一些區(qū)別)崭篡。第一級緩存由mAttachedScrap挪哄,mChangedScrap,mCachedViews共同組成琉闪;第二級緩存是ViewCacheExtension迹炼;第三級緩存是RecyclerViewPool。
  • RecyclerView在第一級緩存中查找的時(shí)候一共查找了兩次。第一次是根據(jù)position來查找的斯入,調(diào)用方法為getScrapOrHiddenOrCachedHolderForPosition砂碉;第二次是根據(jù)itemId和viewType來查找的,調(diào)用方法為getScrapOrCachedViewForId刻两。
  • RecyclerView在根據(jù)itemId和viewType來查找緩存時(shí)增蹭,如果發(fā)現(xiàn)當(dāng)viewholder的itemId符合條件而viewType不符合條件時(shí),會將這個(gè)viewholder從mAttachedScrap或者mCachedViews中移動(dòng)到RecyclerViewPool中(也就是將這個(gè)viewholder從mAttachedScrap或者mCachedViews中刪除同時(shí)保存到RecyclerViewPool中)磅摹。

二滋迈、緩存存入

既然有緩存取出,也應(yīng)該有緩存的存入户誓。RecyclerView緩存的存入有一個(gè)很重要的方法:recycleView(View view)

/**
 * Recycle a detached view. The specified view will be added to a pool of views
 * for later rebinding and reuse.
 *
 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
 * View is scrapped, it will be removed from scrap list.</p>
 *
 * @param view Removed view for recycling
 * @see LayoutManager#removeAndRecycleView(View, Recycler)
 */
public void recycleView(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)
    //根據(jù)傳入的view獲取相關(guān)的viewholder
    ViewHolder holder = getChildViewHolderInt(view);
    //如果這個(gè)holder已經(jīng)被打上了清除的標(biāo)記饼灿,則將其移除
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
    if (holder.isScrap()) {
        //如果這個(gè)holder是來自緩存的可見viewholder數(shù)組,將其移除
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        //如果這個(gè)holder是來自緩存的不可見viewholder數(shù)組帝美,將其移除
        holder.clearReturnedFromScrapFlag();
    }
    //開始緩存
    recycleViewHolderInternal(holder);
}

該方法首先根據(jù)view獲取holder碍彭,然后判斷holder是否已經(jīng)在緩存中,如果在悼潭,將其從緩存中清除庇忌,最后調(diào)用recycleViewHolderInternal方法開始緩存。

/**
 * internal implementation checks if view is scrapped or attached and throws an exception
 * if so.
 * Public version un-scraps before calling recycle.
 */
void recycleViewHolderInternal(ViewHolder holder) {
    //代碼省略(這里主要是對holder進(jìn)行了一些判斷)......
    if (forceRecycle || holder.isRecyclable()) {
        //如果當(dāng)前有緩存舰褪,且緩存的數(shù)量大于0皆疹,并且viewholder的flag是有效的并且不是REMOVED和UPDATE,進(jìn)行緩存
        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();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                //移除緩存中的第一個(gè)view
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            //獲取要緩存的holder在緩存集合中的position
            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                //緩存的時(shí)候抵知,不能覆蓋最近經(jīng)常使用到的緩存
                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;
        }
        //如果沒有緩存的話墙基,則添加到RecyclerViewPool中
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        if (DEBUG) {
            Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                    + "re-visit here. We are still removing it from animation lists"
                    + exceptionLabel());
        }
    }
    // even if the holder is not removed, we still call this method so that it is removed
    // from view holder lists.
    //從mViewInfoStore中移除這個(gè)holder
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

在這里可以看出软族,RecyclerView的存緩存策略使用的最少使用策略刷喜。當(dāng)我們存儲viewholder的時(shí)候,會判斷這個(gè)viewholder是否來自緩存立砸,如果是的話掖疮,那么在存緩存的時(shí)候就不能覆蓋最近比較頻繁使用的緩存。
如何判斷這個(gè)viewholder緩存最近是否頻繁使用呢颗祝?官方定義了一個(gè)輔助類--GapWorker浊闪,它有一個(gè)內(nèi)部類LayoutPrefetchRegistryImpl,然后在這個(gè)內(nèi)部類有一個(gè)成員數(shù)組int[] mPrefetchArray螺戳,這個(gè)數(shù)組是用來記錄最近使用過的holder搁宾,所以RecyclerView在存緩存的時(shí)候會將要保存的viewholder與這個(gè)數(shù)組里面的viewholder進(jìn)行匹配,代碼如下:

boolean lastPrefetchIncludedPosition(int position) {
    if (mPrefetchArray != null) {
        final int count = mCount * 2;
        for (int i = 0; i < count; i += 2) {
            //如果viewholder是最近使用的viewholder倔幼,則返回true
            if (mPrefetchArray[i] == position) return true;
        }
    }
    return false;
}

recycleAndClearCachedViews:將CacheViews中的ViewHolder添加進(jìn)RecyclerViewHolder盖腿,然后清空CacheViews。

void recycleAndClearCachedViews() {
    final int count = mCachedViews.size();
    for (int i = count - 1; i >= 0; i--) {
        //將mCachedViews中的viewholder存儲到RecyclerViewPool中
        recycleCachedViewAt(i);
    }
    //清空mCachedViews集合
    mCachedViews.clear();
    if (ALLOW_THREAD_GAP_WORK) {
        mPrefetchRegistry.clearPrefetchPositions();
    }
}

recycleCachedViewAt

void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    //從mCachedViews中取出viewholder
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    //將viewholder添加到RecyclerViewPool中
    addViewHolderToRecycledViewPool(viewHolder, true);
    //從mCachedViews中移除這個(gè)viewholder
    mCachedViews.remove(cachedViewIndex);
}

addViewHolderToRecycledViewPool

void addViewHolderToRecycledViewPool(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;
    //將viewholder存儲到RecyclerViewPool中
    getRecycledViewPool().putRecycledView(holder);
}

參考文章:
【進(jìn)階】RecyclerView源碼解析(二)——緩存機(jī)制
深入淺出 RecyclerView
Android中的緩存藝術(shù),對比RecyclerView與ListView的緩存機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翩腐,一起剝皮案震驚了整個(gè)濱河市鸟款,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茂卦,老刑警劉巖何什,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異等龙,居然都是意外死亡处渣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛛砰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霍比,“玉大人,你說我怎么就攤上這事暴备∮扑玻” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵涯捻,是天一觀的道長浅妆。 經(jīng)常有香客問我,道長障癌,這世上最難降的妖魔是什么凌外? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮涛浙,結(jié)果婚禮上康辑,老公的妹妹穿的比我還像新娘。我一直安慰自己轿亮,他們只是感情好疮薇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著我注,像睡著了一般按咒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上但骨,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天励七,我揣著相機(jī)與錄音,去河邊找鬼奔缠。 笑死掠抬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的校哎。 我是一名探鬼主播两波,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雨女?” 一聲冷哼從身側(cè)響起谚攒,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氛堕,沒想到半個(gè)月后馏臭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讼稚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年括儒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锐想。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帮寻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赠摇,到底是詐尸還是另有隱情固逗,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布藕帜,位于F島的核電站烫罩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洽故。R本人自食惡果不足惜贝攒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望时甚。 院中可真熱鬧隘弊,春花似錦、人聲如沸荒适。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吻贿。三九已至串结,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舅列,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工卧蜓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帐要,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓弥奸,卻偏偏與公主長得像榨惠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354