讓你徹底掌握RecyclerView的緩存機(jī)制

前言

RecyclerView這個控件幾乎所有的Android開發(fā)者都使用過(甚至不用加幾乎),它是真的很好用,完美取代了ListView和GridView,而RecyclerView之所以好用,得益于它優(yōu)秀的緩存機(jī)制。關(guān)于RecyclerView緩存機(jī)制,更是需要我們開發(fā)者來掌握的。本文就將先從整體流程看RecyclerView的緩存,再帶你從源碼角度分析,跳過讀源碼的坑,最后用一個簡單的demo的形式展示出來。在開始RecyclerView的緩存機(jī)制之前我們先學(xué)習(xí)關(guān)于ViewHolder的知識。

RecyclerView為什么強(qiáng)制我們實(shí)現(xiàn)ViewHolder模式?

關(guān)于這個問題,我們首先看一下ListView。ListView是不強(qiáng)制我們實(shí)現(xiàn)ViewHolder的,但是后來google建議我們實(shí)現(xiàn)ViewHolder模式。我們先分別看一下這兩種不同的方式。

不使用ViewHolder的模式

使用ViewHolder的模式

其實(shí)這里我已經(jīng)用紅框標(biāo)出來了,ListView使用ViewHolder的好處就在于可以避免每次getView都進(jìn)行findViewById()操作,因?yàn)閒indViewById()利用的是DFS算法(深度優(yōu)化搜索),是非常耗性能的。而對于RecyclerView來說,強(qiáng)制實(shí)現(xiàn)ViewHolder的其中一個原因就是避免多次進(jìn)行findViewById()的處理,另一個原因就是因?yàn)镮temView和ViewHolder的關(guān)系是一對一,也就是說一個ViewHolder對應(yīng)一個ItemView。這個ViewHolder當(dāng)中持有對應(yīng)的ItemView的所有信息,比如說:position;view;width等等,拿到了ViewHolder基本就拿到了ItemView的所有信息,而ViewHolder使用起來相比itemView更加方便。RecyclerView緩存機(jī)制緩存的就是ViewHolder(ListView緩存的是ItemView),這也是為什么RecyclerView為什么強(qiáng)制我們實(shí)現(xiàn)ViewHolder的原因。

Listview的緩存機(jī)制

在正式講RecyclerView的緩存機(jī)制之前還需要提一嘴ListView的緩存機(jī)制,不多BB,先上圖


listview緩存頁面.png

ListView的緩存有兩級,在ListView里面有一個內(nèi)部類 RecycleBin,RecycleBin有兩個對象Active View和Scrap View來管理緩存,Active View是第一級,Scrap View是第二級。

  • Active View:是緩存在屏幕內(nèi)的ItemView,當(dāng)列表數(shù)據(jù)發(fā)生變化時,屏幕內(nèi)的數(shù)據(jù)可以直接拿來復(fù)用,無須進(jìn)行數(shù)據(jù)綁定。
  • Scrap view:緩存屏幕外的ItemView,這里所有的緩存的數(shù)據(jù)都是"臟的",也就是數(shù)據(jù)需要重新綁定,也就是說屏幕外的所有數(shù)據(jù)在進(jìn)入屏幕的時候都要走一遍getView()方法。
    再來一張圖,看看ListView的緩存流程
    lisview.png

    當(dāng)Active View和Scrap View中都沒有緩存的時候就會直接create view。

小結(jié)

ListView的緩存機(jī)制相對比較好理解,它只有兩級緩存,一級緩存Active View是負(fù)責(zé)屏幕內(nèi)的ItemView快速復(fù)用,而Scrap View是緩存屏幕外的數(shù)據(jù),當(dāng)該數(shù)據(jù)從屏幕外滑動到屏幕內(nèi)的時候需要走一遍getView()方法。

RecyclerView的緩存機(jī)制

先上圖


RecyclerView緩存.png

RecyclerView的緩存分為四級

  • Scrap
  • Cache
  • ViewCacheExtension
  • RecycledViewPool

Scrap對應(yīng)ListView 的Active View,就是屏幕內(nèi)的緩存數(shù)據(jù),就是相當(dāng)于換了個名字,可以直接拿來復(fù)用。

Cache 剛剛移出屏幕的緩存數(shù)據(jù),默認(rèn)大小是2個,當(dāng)其容量被充滿同時又有新的數(shù)據(jù)添加的時候,會根據(jù)FIFO原則,把優(yōu)先進(jìn)入的緩存數(shù)據(jù)移出并放到下一級緩存中,然后再把新的數(shù)據(jù)添加進(jìn)來。Cache里面的數(shù)據(jù)是干凈的,也就是攜帶了原來的ViewHolder的所有數(shù)據(jù)信息,數(shù)據(jù)可以直接來拿來復(fù)用。需要注意的是,cache是根據(jù)position來尋找數(shù)據(jù)的,這個postion是根據(jù)第一個或者最后一個可見的item的position以及用戶操作行為(上拉還是下拉)。
舉個栗子:當(dāng)前屏幕內(nèi)第一個可見的item的position是1,用戶進(jìn)行了一個下拉操作,那么當(dāng)前預(yù)測的position就相當(dāng)于(1-1=0),也就是position=0的那個item要被拉回到屏幕,此時RecyclerView就從Cache里面找position=0的數(shù)據(jù),如果找到了就直接拿來復(fù)用。

ViewCacheExtension是google留給開發(fā)者自己來自定義緩存的,這個ViewCacheExtension我個人建議還是要慎用,因?yàn)槲野抢抢W(wǎng)上其他的博客,沒有找到對應(yīng)的使用場景,而且這個類的api設(shè)計(jì)的也有些奇怪,只有一個public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);讓開發(fā)者重寫通過position和type拿到ViewHolder的方法,卻沒有提供如何產(chǎn)生ViewHolder或者管理ViewHolder的方法,給人一種只出不進(jìn)的趕腳,還是那句話慎用。

RecycledViewPool剛才說了Cache默認(rèn)的緩存數(shù)量是2個,當(dāng)Cache緩存滿了以后會根據(jù)FIFO(先進(jìn)先出)的規(guī)則把Cache先緩存進(jìn)去的ViewHolder移出并緩存到RecycledViewPool中,RecycledViewPool默認(rèn)的緩存數(shù)量是5個。RecycledViewPool與Cache相比不同的是,從Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的數(shù)據(jù)會被全部重置,相當(dāng)于一個新的ViewHolder,而且Cache是根據(jù)position來獲取ViewHolder,而RecycledViewPool是根據(jù)itemType獲取的,如果沒有重寫getItemType()方法,itemType就是默認(rèn)的。因?yàn)镽ecycledViewPool緩存的ViewHolder是全新的,所以取出來的時候需要走onBindViewHolder()方法。
再來張圖看看整體流程

RecyclerView流程.png

這里大家先記住主要流程,并且記住各級緩存是根據(jù)什么拿到ViewHolder以及ViewHolder能否直接拿來復(fù)用,先有一個整體的認(rèn)識,下面我會帶著大家再簡單分析一下RecyclerView緩存機(jī)制的源碼。

閱讀RecyclerView的緩存機(jī)制源碼

由于篇幅和內(nèi)容的關(guān)系,我不可能帶大家一行一行讀,這里我只列出關(guān)鍵點(diǎn),還有哪些需要重點(diǎn)看,哪些可以直接略過,避免大家陷入讀源碼一個勁兒鉆進(jìn)去出不來的誤區(qū)。
當(dāng)RecyclerView繪制的時候,會走到LayoutManager里面的next()方法,在next()里面是正式開始使用緩存機(jī)制,這里以LinearLayoutManager為例子


        /**
         * Gets the view for the next element that we should layout.
         * Also updates current item index to the next item, based on {@link #mItemDirection}
         *
         * @return The next element that we should layout.
         */
        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }

在next方法里傳入了Recycler對象,這個對象是RecyclerView的內(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;
}

再看一眼RecycledViewPool的源碼

public static class RecycledViewPool {
        private static final int DEFAULT_MAX_SCRAP = 5;
        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<>();

其中mAttachedScrap對應(yīng)Scrap;mCachedViews對應(yīng)Cache;mViewCacheExtension對應(yīng)ViewCacheExtension;mRecyclerPool對應(yīng)RecycledViewPool。
注意:mAttachedScrap、mCachedViews和RecycledViewPool里面的mScrapHeap都是ArrayList,緩存被加入到這三個對象里面實(shí)際上就是調(diào)用的ArrayList.add()方法,復(fù)用緩存呢,這里要注意一下不是調(diào)用的ArrayList.get()而是ArrayList.remove(),其實(shí)這里也很好理解,因?yàn)楫?dāng)緩存數(shù)據(jù)被取出來展示到了屏幕內(nèi),自然就應(yīng)該被移除。
我們現(xiàn)在回到剛才的next()方法里,recycler.getViewForPosition(mCurrentPosition); 直接去看getViewForPosition這個方法,接著跟到了這里

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

接著跟進(jìn)去

  ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount()
                        + exceptionLabel());
            }
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            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) {
                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;
                    }
                }
                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);
                      
                    }
                }
                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);
                        }
                    }
                }
                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);
                        }
                    }
                }
            } 

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }  
            return holder;
        }

終于到了緩存機(jī)制最核心的地方,為了方便大家閱讀,我對這部分源碼進(jìn)行了刪減,直接從官方給的注釋里面看。

// (0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }

這里面只有設(shè)置動畫以后才會為true,跟咱們講的緩存也沒有多大關(guān)系,直接略過。

 // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            }

這里就開始拿第一級和第二級緩存了getScrapOrHiddenOrCachedHolderForPosition()這個方法可以深入去看以下,注意這里傳的參數(shù)是position(dryRun這個參數(shù)不用管),就跟我之前說的,Scrap和Cache是根據(jù)position拿到緩存。

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就不會進(jìn)入判斷條件,還是那句話慎用。

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

這里到了第四級緩存RecycledViewPool,getRecycledViewPool().getRecycledView(type);通過type拿到ViewHolder,接著holder.resetInternal();重置ViewHolder,讓其變成一個全新的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);
                        }
                    }
                }

到這里如果ViewHolder還為null的話,就會create view了,創(chuàng)建一個新的ViewHolder

boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }  

這里else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid())是判斷這個ViewHolder是不是有效的,也就是可不可以復(fù)用,如果不可以復(fù)用就會進(jìn)入tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);這個方法,在這里面調(diào)用了bindViewHolder()方法。
點(diǎn)進(jìn)去看一眼

 private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
                int position, long deadlineNs) {
            ....................
            mAdapter.bindViewHolder(holder, offsetPosition);
            ....................
            return true;
        }

在點(diǎn)進(jìn)去就到了我們熟悉的onBindViewHolder()

  public final void bindViewHolder(@NonNull VH holder, int position) {
            .......................
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
           .........................
        }

至此,緩存機(jī)制的整體流程就全部分析完畢了。

小結(jié)

ListView有兩級緩存,分別是Active View和Scrap View,緩存的對象是ItemView;而RecyclerView有四級緩存,分別是Scrap、Cache、ViewCacheExtension和RecycledViewPool,緩存的對象是ViewHolder。Scrap和Cache分別是通過position去找ViewHolder可以直接復(fù)用;ViewCacheExtension自定義緩存,目前來說應(yīng)用場景比較少卻需慎用;RecycledViewPool通過type來獲取ViewHolder,獲取的ViewHolder是個全新,需要重新綁定數(shù)據(jù)。當(dāng)你看到這里的時候,面試官再問RecyclerView的性能比ListView優(yōu)化在哪里,我想你已經(jīng)有答案。

通過Demo再理解一遍

擔(dān)心你看完上面的內(nèi)容,倒頭就忘,我們寫個簡單的demo通過打印log的方式來鞏固一下學(xué)到的知識。
簡單說一下Demo里面需要注意的代碼,下面是對RecyclerView的一個包裝

public class RecyclerViewWrapper extends RecyclerView {
    private  LayoutListener layoutListener;

    public RecyclerViewWrapper(@NonNull Context context) {
        super(context);
    }

    public RecyclerViewWrapper(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RecyclerViewWrapper(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setLayoutListener(LayoutListener layoutListener) {
        this.layoutListener = layoutListener;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (layoutListener != null) {
            layoutListener.onBeforeLayout();
        }
        super.onLayout(changed, l, t, r, b);

        if (layoutListener != null) {
            layoutListener.onAfterLayout();
        }
    }

    public interface LayoutListener {

        void onBeforeLayout();

        void onAfterLayout();
    }

}

其實(shí)很簡單,在RecyclerView執(zhí)行onLayout()方法前后執(zhí)行一下咱們打印緩存變化的方法

再看一眼打印緩存變化的方法,利用反射的技術(shù)

/**
     * 利用java反射機(jī)制拿到RecyclerView內(nèi)的緩存并打印出來
     * */
    private void showMessage(RecyclerViewWrapper rv) {
        try {
            Field mRecycler =
                    Class.forName("androidx.recyclerview.widget.RecyclerView").getDeclaredField("mRecycler");
            mRecycler.setAccessible(true);
            RecyclerView.Recycler recyclerInstance = (RecyclerView.Recycler) mRecycler.get(rv);

            Class<?> recyclerClass = Class.forName(mRecycler.getType().getName());
            Field mViewCacheMax = recyclerClass.getDeclaredField("mViewCacheMax");
            Field mAttachedScrap = recyclerClass.getDeclaredField("mAttachedScrap");
            Field mChangedScrap = recyclerClass.getDeclaredField("mChangedScrap");
            Field mCachedViews = recyclerClass.getDeclaredField("mCachedViews");
            Field mRecyclerPool = recyclerClass.getDeclaredField("mRecyclerPool");
            mViewCacheMax.setAccessible(true);
            mAttachedScrap.setAccessible(true);
            mChangedScrap.setAccessible(true);
            mCachedViews.setAccessible(true);
            mRecyclerPool.setAccessible(true);


            int mViewCacheSize = (int) mViewCacheMax.get(recyclerInstance);
            ArrayListWrapper<RecyclerView.ViewHolder> mAttached =
                    (ArrayListWrapper<RecyclerView.ViewHolder>) mAttachedScrap.get(recyclerInstance);
            ArrayList<RecyclerView.ViewHolder> mChanged =
                    (ArrayList<RecyclerView.ViewHolder>) mChangedScrap.get(recyclerInstance);
            ArrayList<RecyclerView.ViewHolder> mCached =
                    (ArrayList<RecyclerView.ViewHolder>) mCachedViews.get(recyclerInstance);
            RecyclerView.RecycledViewPool recycledViewPool =
                    (RecyclerView.RecycledViewPool) mRecyclerPool.get(recyclerInstance);

            Class<?> recyclerPoolClass = Class.forName(mRecyclerPool.getType().getName());

            Log.e(TAG, "mAttachedScrap(一緩) size is:" + mAttached.maxSize + ", \n" + "mCachedViews(二緩) max size is:" + mViewCacheSize + ","
                    + getMCachedViewsInfo(mCached) + getRVPoolInfo(recyclerPoolClass, recycledViewPool));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

核心的代碼呢就這兩塊,文章的最后我會把我的demo上傳到github上。
注意:本文使用的RecyclerView的版本是androidx,在調(diào)onAttachedToWindow()方法的時候會進(jìn)行版本判斷,如果是5.0以及以上的系統(tǒng)(即大于等于21),GapWorker會把RecyclerView自己加入到GapWorker。在RenderThread線程執(zhí)行預(yù)取操作的時候會mPrefetchMaxCountObserved = 1,這就會導(dǎo)致你使用5.0以及以上系統(tǒng)的手機(jī)打印緩存數(shù)量的時候會比你預(yù)想的多一個。這里為了不造成這種問題,本文使用4.4系統(tǒng)的Android模擬器來演示Demo。

Demo演示效果截圖

  • 啟動App,第一次加載的情況


    第一次啟動app加載

    初始化加載只有屏幕內(nèi)的一級緩存7個

  • 把position = 0 和position=1 兩個item移除屏幕


    前兩個item移除屏幕

    看藍(lán)色框出來的,position = 0 和position = 1的item被加入到了Cache緩存中,Cache的緩存數(shù)量我沒有修改,默認(rèn)2個,也就說現(xiàn)在已經(jīng)滿了

  • 再把position = 2的item也移除屏幕


    position = 2的item也移除屏幕

因?yàn)樯弦徊紺ache里面的緩存已經(jīng)慢了,此時position = 2又被加入緩存,根據(jù)FIFO的原則,cache里面position = 0 被remove掉并加入到了四級緩存RecycledView里面,此時RecycledView也有了緩存并且該緩存沒有任何有效數(shù)據(jù)信息。

  • 再上一步的基礎(chǔ)上下拉一下,把position = 2的item顯示出來


    把position = 2的item顯示出來

    此時position = 2的item將要被顯示出來,會先從cache里面找,發(fā)現(xiàn)Cache正好有position = 2的緩存就直接拿出來復(fù)用了,并且原來在屏幕里的position= 9 的item被移除了,就會加入到Cache的緩存里
    -----------------------------------分割線-------------------------------------------
    現(xiàn)在看一下onCreateViewHolder()和onBindViewHolder()的情況

  • 還是啟動App,第一次加載后,再把position = 0和position =1的item移除屏幕再移回來


    顯示效果

    onBindViewHolder()方法沒有被重復(fù)執(zhí)行(靜態(tài)圖顯示的效果不是很好,gif錄制的質(zhì)量太差了,還是建議下載demo自己嘗試一下)

  • 最后留一個問題給大家

    onCreateViewHolder不再執(zhí)行

    為什么在第10次onCreateViewHolder()執(zhí)行以后就再也沒有執(zhí)行過onCreateViewHolder()方法了?

總結(jié)

關(guān)于RecyclerView的緩存分為四級,Scrap、Cache、ViewCacheExtension和RecycledViewPool。Scrap是屏幕內(nèi)的緩存一般我們不怎么需要特別注意;Cache可直接拿來復(fù)用的緩存,性能高效;ViewCacheExtension需要開發(fā)者自定義的緩存,API設(shè)計(jì)比較奇怪,慎用;RecycledViewPool四級緩存,可以避免用戶調(diào)用onCreateViewHolder()方法,提高性能,在ViewPager+RecyclerView的應(yīng)用場景下可以大有作為。以上就是關(guān)于RecyclerView緩存的所有內(nèi)容,另外要備注一下,就是文章的圖片上有些單詞打錯了,實(shí)在是懶得重畫了,以文本的內(nèi)容為準(zhǔn),請大家見諒。

最后是github的地址:RecyclerViewCacheDemo

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

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

  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,717評論 0 27
  • Recycler類是RecyclerView內(nèi)部final類,它管理scrapped(廢棄)或detached(獨(dú)...
    gczxbb閱讀 3,885評論 0 4
  • RecyclerView的緩存主要體現(xiàn)在RecyclerView的內(nèi)部類Recycler 重要的成員變量 四級緩存...
    CP9閱讀 8,014評論 2 5
  • 第一篇文章就是關(guān)于減肥的呢,哈哈。這一次下一個死目標(biāo)80斤,1米5幾的女生80斤應(yīng)該可以了吧。 從以往的經(jīng)...
    望燈里閱讀 322評論 0 0
  • 從小我就和我的爺爺奶奶住在一起,奶奶對我可好了,我在看選幾件事說說吧! 奶奶的愛像遮雨的傘。小時候,我放學(xué)后,天下...
    56369e731cad閱讀 247評論 0 1

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