RecyclerView顯示及緩存機(jī)制

一.RecyclerView顯示

??????在使用RecyclerView時(shí)义锥,需要結(jié)合Adapter來(lái)使用,一個(gè)RecyclerView需要一個(gè)Adapter蕉朵,一個(gè)Adapter中對(duì)應(yīng)著指定數(shù)量及指定type的item闻丑,即ViewHolder十兢,那ViewHolder時(shí)如何創(chuàng)建及如何顯示的?
??????在平時(shí)使用中耘擂,通過(guò)調(diào)用RecycerView.setAdapter()來(lái)顯示內(nèi)容胆剧,一起看一下執(zhí)行流程:

a.RecyclerView
public void setAdapter(@Nullable Adapter adapter) {
    ......
    setAdapterInternal(adapter, false, true);
    requestLayout();
}

??????先執(zhí)行了setAdapterInternal(adapter, false, true),看一下這個(gè)方法主要做了什么:

private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
         Layout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
}

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

??????可以看到在setAdapterInternal()里面先調(diào)用了unRegister和unDetached操作醉冤,接著執(zhí)行了register和detached操作秩霍,register里面會(huì)執(zhí)行adapter里面對(duì)應(yīng)的register方法:

private final AdapterDataObservable mObservable = new AdapterDataObservable();
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
    mObservable.registerObserver(observer);
}

public final void notifyDataSetChanged() {
    mObservable.notifyChanged();
}

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

??????在registerAdapterDataObserver()里面執(zhí)行的是AdapterDataObservable的registerObserver()方法,當(dāng)我們?cè)诒镜貙?shí)現(xiàn)的adapter內(nèi)部執(zhí)行notifyDataSetChanged()及notifyItemChanged()等操作時(shí)蚁阳,先會(huì)調(diào)用到AdapterDataObservable內(nèi)部對(duì)應(yīng)的方法:

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public void notifyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }

    public void notifyItemRangeInserted(int positionStart, int itemCount) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
        }
    }
    ........
    ........
}

??????在AdapterDataObservable里面最終會(huì)調(diào)用到RecyclerViewDataObserver里面對(duì)應(yīng)的方法铃绒,執(zhí)行view刷新的操作。

private class RecyclerViewDataObserver extends AdapterDataObserver {

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

    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        ......
    }
    ......
    ......
    void triggerUpdateProcessor() {
        ......
    }
}

??????接著上面setAdapter()內(nèi)部邏輯講韵吨,然后調(diào)用了requestLayout()匿垄,調(diào)用該方法后移宅,會(huì)執(zhí)行onLayout():

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    dispatchLayout();
}

??????在dispatchLayout()內(nèi)部會(huì)調(diào)用到dispatchLayoutStep2():

private void dispatchLayoutStep2() {
    ......
    mState.mItemCount = mAdapter.getItemCount();
    // Step 2: Run layout
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);
    ....
}

??????在該方法內(nèi)部,先通過(guò)adapter的getItemCount()來(lái)獲取到item的數(shù)量椿疗,賦值給State()的mItemCount漏峰,后續(xù)在顯示item時(shí)會(huì)使用到;然后調(diào)用mLayout的onLayoutChildren()方法届榄,mLayout是在初始化RecyclerView時(shí)通過(guò)setLayoutManager()傳入的LayoutManager浅乔,此處分析LinerLayoutManager。

b.LinerLayoutManager

??????LayoutManager是用來(lái)決定RecyclerView內(nèi)部item顯示的布局铝条,LinerLayoutManager線(xiàn)性布局靖苇,GridLayoutManager是網(wǎng)格布局,本文主要分析LinerLayoutManager:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    .......
    ......
    fill(recycler, mLayoutState, state, false);
    ......
    ......
}

??????在onLayoutChildren()內(nèi)調(diào)用了fill()方法:

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
    ......
    ......
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            .......
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            .......
    }
}

??????在fill()內(nèi)部通過(guò)while()來(lái)不斷的執(zhí)行l(wèi)ayoutChunk()班缰,先看一下while()循環(huán)的條件之一:layoutState.hasMore(state)贤壁,LayoutState是LinerLayoutManager內(nèi)部的一個(gè)靜態(tài)類(lèi),State是RecyclerView內(nèi)部的一個(gè)類(lèi)埠忘,注意別搞混了脾拆;

boolean hasMore(RecyclerView.State state) {
    return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}

??????通過(guò)以上看到,用到了getItemCount()莹妒,通過(guò)position跟item的數(shù)量來(lái)判斷是否需要繼續(xù)循環(huán)執(zhí)行l(wèi)ayoutChunk():

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        ......
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

??????在layoutChunk()內(nèi)部會(huì)通過(guò)layoutState.next(recycler)來(lái)獲取當(dāng)前position對(duì)應(yīng)的view名船,然后通過(guò)addView(),最后執(zhí)行l(wèi)ayout來(lái)進(jìn)行放置旨怠,這里我們不去關(guān)心是如何放置的渠驼,只關(guān)心是如何獲取的item對(duì)應(yīng)的view,主要看一下layoutState.next(recycler):

View next(RecyclerView.Recycler recycler) {
    ........
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

??????next()內(nèi)部最終是通過(guò)recycler的getViewForPosition來(lái)獲取view鉴腻。

c.RecyclerView.Recycler
public View getViewForPosition(int position) {
     return getViewForPosition(position, false);
}

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

??????從getViewForPosition()內(nèi)部邏輯來(lái)看迷扇,先調(diào)用tryGetViewHolderForPositionByDeadline()來(lái)獲取到ViewHolder,然后獲取到ViewHolder里面的itemView拘哨,先看一下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()) {
            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) {
            //獲取到position對(duì)應(yīng)的type谋梭,需要本地實(shí)現(xiàn),不實(shí)現(xiàn)的話(huà)倦青,默認(rèn)返回0瓮床,即一個(gè)type
            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 && mViewCacheExtension != null) {
                final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
                ......
            }
            if (holder == null) { // fallback to pool
                holder = getRecycledViewPool().getRecycledView(type);
                .....
            }
            if (holder == null) {
                .......
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
                .......
            }
        }

            ......
            ......
               
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            ........
            return holder;
}

??????從上面可以看到,去獲取holder時(shí)产镐,會(huì)經(jīng)歷四步判斷隘庄,即對(duì)應(yīng)著四級(jí)緩存,下一節(jié)會(huì)詳細(xì)介紹癣亚,如果從四級(jí)緩存中都沒(méi)有獲取到時(shí)丑掺,會(huì)執(zhí)行createViewHolder()來(lái)進(jìn)行創(chuàng)建:
??????注意一下:viewType是從本地實(shí)現(xiàn)的getItemType(postion)來(lái)獲取的,如果沒(méi)有實(shí)現(xiàn)述雾,則默認(rèn)為0街州,即都是統(tǒng)一類(lèi)型兼丰;如果實(shí)現(xiàn)了,用途有二:1.本地根據(jù)對(duì)應(yīng)的type來(lái)創(chuàng)建不同的view唆缴;2.RecyclerViewPool根據(jù)不同的type緩存對(duì)應(yīng)數(shù)量為5的viewHolder鳍征。

public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
    try {
        final VH holder = onCreateViewHolder(parent, viewType);
        holder.mItemViewType = viewType;
        return holder;
     } finally {
        TraceCompat.endSection();
     }
}

??????最終會(huì)回調(diào)本地adapter的onCreateViewHolder()來(lái)創(chuàng)建對(duì)應(yīng)的viewHolder,看一下ViewHolder這個(gè)類(lèi):

public abstract static class ViewHolder {
    ......
    ......
    public ViewHolder(@NonNull View itemView) {
        if (itemView == null) {
            throw new IllegalArgumentException("itemView may not be null");
        }
        this.itemView = itemView;
    }
    ......
    ......
}

??????通過(guò)以上可以看到面徽,itemView是在構(gòu)造方法中作為參數(shù)傳入的艳丛,在創(chuàng)建本地ViewHolder時(shí),我們會(huì)先通過(guò)LayoutInflater來(lái)inflate()對(duì)應(yīng)position及itemtype的view趟紊,然后執(zhí)行new ViewHolder(view)返回氮双,此處itemView就是我們創(chuàng)建的view。
??????接著上面分析霎匈,接下來(lái)會(huì)調(diào)用tryBindViewHolderByDeadline()戴差,先設(shè)置flag,最終會(huì)回調(diào)本地adapter的onBindViewHolder():

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

public final void bindViewHolder(@NonNull VH holder, int position) {
    holder.mPosition = position;
    holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            
    onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
}

??????以上就是在初始化RecyclerView唧躲,然后調(diào)用setAdapter()后的整個(gè)執(zhí)行流程造挽,用流程圖總結(jié)一下:


recycleview顯示流程.png

二.四級(jí)緩存機(jī)制

??????RecyclerView緩存機(jī)制分為四級(jí),分別為:Scrap(mAttachedScrap)弄痹、Cache(mCachedViews)、ViewCacheExtension(mViewCacheExtension)嵌器、RecycledViewPool(mRecyclerPool)肛真。
??????Scrap
??????就是屏幕內(nèi)的緩存數(shù)據(jù),可以直接拿來(lái)復(fù)用爽航。
??????Cache
??????剛剛移出屏幕的緩存數(shù)據(jù)蚓让,默認(rèn)大小是2個(gè),當(dāng)其容量被充滿(mǎn)同時(shí)又有新的數(shù)據(jù)添加的時(shí)候讥珍,會(huì)根據(jù)FIFO原則历极,把先進(jìn)入的緩存數(shù)據(jù)移出并放到下一級(jí)緩存中,然后再把新的數(shù)據(jù)添加進(jìn)來(lái)衷佃。Cache里面的數(shù)據(jù)是干凈的趟卸,即攜帶了原來(lái)的ViewHolder的所有數(shù)據(jù)信息,數(shù)據(jù)可以直接來(lái)拿來(lái)復(fù)用氏义。需要注意的是锄列,cache是根據(jù)position來(lái)尋找數(shù)據(jù)的,這個(gè)postion是根據(jù)第一個(gè)或者最后一個(gè)可見(jiàn)的item的position以及用戶(hù)操作行為(上拉還是下拉)惯悠。
??????舉個(gè)例子:當(dāng)前屏幕內(nèi)第一個(gè)可見(jiàn)的item的position是1邻邮,用戶(hù)進(jìn)行了一個(gè)下拉操作,那么當(dāng)前預(yù)測(cè)的position就相當(dāng)于(1-1=0)克婶,也就是position=0的那個(gè)item要被拉回到屏幕筒严,此時(shí)RecyclerView就從Cache里面找position=0的數(shù)據(jù)丹泉,如果找到了就直接拿來(lái)復(fù)用。
??????ViewCacheExtension
??????google留給開(kāi)發(fā)者自己來(lái)自定義緩存的鸭蛙,ViewCacheExtension適用場(chǎng)景:ViewHolder位置固定摹恨、內(nèi)容固定、數(shù)量有限時(shí)使用规惰。
??????使用方式如下:

//viewType類(lèi)型為T(mén)YPE_SPECIAL時(shí)睬塌,設(shè)置四級(jí)緩存池RecyclerPool不存儲(chǔ)對(duì)應(yīng)類(lèi)型的數(shù)據(jù) 因?yàn)樾枰_(kāi)發(fā)者自行緩存
recyclerView.getRecycledViewPool().setMaxRecycledViews(DemoAdapter.TYPE_SPECIAL, 0);
//設(shè)置ViewCacheExtension緩存
recyclerView.setViewCacheExtension(new MyViewCacheExtension());
//實(shí)現(xiàn)自定義緩存ViewCacheExtension
class MyViewCacheExtension extends RecyclerView.ViewCacheExtension {
    @Nullable
    @Override
    public View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position, int viewType) {
        //如果viewType為T(mén)YPE_SPECIAL,使用自己緩存的View去構(gòu)建ViewHolder
        // 否則返回null,會(huì)使用系統(tǒng)RecyclerPool緩存或者從新通過(guò)onCreateViewHolder構(gòu)建View及ViewHolder
        return viewType == DemoAdapter.TYPE_SPECIAL ? adapter.caches.get(position) : null;
    }
}

Adapter.java
public SparseArray<View> caches = new SparseArray<>();//開(kāi)發(fā)者自行維護(hù)的緩存
Adapter#onBindViewHolder中根據(jù)position設(shè)置的緩存:caches.put(position, sHolder.itemView);

三.源碼分析

??????主要分析一下RecycledViewPool的流程:
??????RecycledViewPool
??????Cache默認(rèn)的緩存數(shù)量是2個(gè)歇万,當(dāng)Cache緩存滿(mǎn)了以后會(huì)根據(jù)FIFO(先進(jìn)先出)的規(guī)則把Cache先緩存進(jìn)去的ViewHolder移出并緩存到RecycledViewPool中揩晴,RecycledViewPool存儲(chǔ)時(shí)是根據(jù)itemType來(lái)存儲(chǔ)的,每個(gè)itemType對(duì)應(yīng)的緩存數(shù)量是5個(gè)贪磺;看一下主要實(shí)現(xiàn)邏輯:

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

??????RecycledViewPool內(nèi)部維護(hù)一個(gè)SparseArray<ScrapData>mScrap硫兰,ScrapData內(nèi)部維護(hù)一個(gè)ArrayList<ViewHolder>mScrapHeap,最大值為5寒锚;

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;
    }
    ......
    scrap.resetInternal();
    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;
}

??????在執(zhí)行putRecycledView(vh)時(shí)劫映,先判斷vh的type,根據(jù)type去獲取ScrapData刹前,如果獲取到就返回泳赋,否則就創(chuàng)建一個(gè)新的ScrapData,然后加入到mScrap內(nèi)喇喉,key為對(duì)應(yīng)的type祖今,再獲取到ScrapData內(nèi)部的mScrapHeap,判斷size()拣技,如果已經(jīng)等于5千诬,就不再存儲(chǔ)了,否則先執(zhí)行resetInternal()來(lái)清空數(shù)據(jù)膏斤,然后再加入到mScrapHeap里面徐绑;

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

??????在執(zhí)行g(shù)etRecycledView(type)時(shí),先根據(jù)type從mScrap內(nèi)獲取對(duì)應(yīng)的ScrapData莫辨,如果不為空傲茄,則獲取到ScrapData內(nèi)部的mScrapHeap,再?gòu)膆eap中獲取到ViewHolder衔掸,然后將對(duì)應(yīng)位置的ViewHolder刪除烫幕,后續(xù)put時(shí)再進(jìn)行添加;
??????直觀一點(diǎn)如下:

image.png

??????RecycledViewPool與Cache相比不同的是敞映,從Cache里面移出的ViewHolder在存入RecycledViewPool之前ViewHolder的數(shù)據(jù)會(huì)被全部重置(resetInternal())较曼,相當(dāng)于一個(gè)新的ViewHolder,而且Cache是根據(jù)position來(lái)獲取ViewHolder振愿,而RecycledViewPool是根據(jù)itemType獲取的捷犹,如果沒(méi)有重寫(xiě)getItemType()方法弛饭,itemType就是默認(rèn)的。因?yàn)镽ecycledViewPool緩存的ViewHolder是全新的萍歉,所以取出來(lái)的時(shí)候需要走onBindViewHolder()方法侣颂。

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()) {
        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) {
        .........
        // 2) Find from scrap/cache via stable ids, if exists
        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) { // fallback to pool
            holder = getRecycledViewPool().getRecycledView(type);
            ........
         }
         if (holder == null) {
             //create view holder
             holder = mAdapter.createViewHolder(RecyclerView.this, type);
             .......
         }
        ......
    }

    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()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ......
    return holder;
}

??????直觀一點(diǎn)如下:


RecyclerView緩存機(jī)制.png

四.ReceyclerView與ListView的區(qū)別

??????1)ListView布局單一,RecycleView可以根據(jù)LayoutManger有橫向枪孩,瀑布和表格布局憔晒;
??????2)自定義適配器中,ListView的適配器繼承ArrayAdapter蔑舞;RecycleView的適配器繼承RecyclerAdapter拒担,并將范類(lèi)指定為子項(xiàng)對(duì)象類(lèi)ViewHolder(內(nèi)部類(lèi))。
??????3)ListView優(yōu)化需要自定義ViewHolder和判斷convertView是否為null(不用每次都創(chuàng)建convertView和調(diào)用findViewById()攻询,findViewById()這個(gè)方法是比較耗性能的操作从撼,因?yàn)檫@個(gè)方法要找到指定的布局文件,進(jìn)行不斷地解析每個(gè)節(jié)點(diǎn):從最頂端的節(jié)點(diǎn)進(jìn)行一層一層的解析查詢(xún)钧栖,找到后在一層一層的返回低零,如果在左邊沒(méi)找到,就會(huì)接著解析右邊拯杠,并進(jìn)行相應(yīng)的查詢(xún)掏婶,直到找到位置。特點(diǎn):xml文件被解析的時(shí)候潭陪,只要被創(chuàng)建出來(lái)了气堕,其孩子的id就不會(huì)改變了。根據(jù)這個(gè)特點(diǎn)畔咧,可以將孩子id存入到指定的集合中,每次就可以直接取出集合中對(duì)應(yīng)的元素就可以了)揖膜。 而RecyclerView是強(qiáng)制存在規(guī)定好的ViewHolder誓沸。
??????4)綁定事件的方式不同,ListView是在主方法中ListView對(duì)象的setOnItemClickListener方法壹粟;RecyclerView則是在子項(xiàng)具體的View中去注冊(cè)事件拜隧。
??????5) 緩存方式有區(qū)別:RecyclerView中mCacheViews(屏幕外)獲取緩存時(shí),是通過(guò)匹配pos獲取目標(biāo)位置的緩存趁仙,這樣做的好處是洪添,當(dāng)數(shù)據(jù)源數(shù)據(jù)不變的情況下,可以做到屏幕外的列表項(xiàng)ItemView進(jìn)入屏幕內(nèi)時(shí)也無(wú)須bindView快速重用雀费;ListView緩存View干奢,同樣是離屏緩存,ListView從mScrapViews根據(jù)pos獲取相應(yīng)的緩存盏袄,但是并沒(méi)有直接使用忿峻,而是重新getView(即必定會(huì)重新bindView)薄啥。
??????6) 刷新方式:RecyclerView更大的亮點(diǎn)在于提供了局部刷新的接口,通過(guò)局部刷新逛尚,就能避免調(diào)用許多無(wú)用的bindView垄惧;ListView和RecyclerView最大的區(qū)別在于數(shù)據(jù)源改變時(shí)的緩存的處理邏輯,ListView是"一鍋端"绰寞,將所有的mActiveViews都移入了二級(jí)緩存mScrapViews到逊,而RecyclerView則是更加靈活地對(duì)每個(gè)View修改標(biāo)志位,區(qū)分是否重新bindView滤钱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末觉壶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菩暗,更是在濱河造成了極大的恐慌掰曾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件停团,死亡現(xiàn)場(chǎng)離奇詭異旷坦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)佑稠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)秒梅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舌胶,你說(shuō)我怎么就攤上這事捆蜀。” “怎么了幔嫂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵辆它,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我履恩,道長(zhǎng)锰茉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任切心,我火速辦了婚禮飒筑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绽昏。我一直安慰自己协屡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布全谤。 她就那樣靜靜地躺著肤晓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上材原,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天沸久,我揣著相機(jī)與錄音,去河邊找鬼余蟹。 笑死卷胯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的威酒。 我是一名探鬼主播窑睁,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼葵孤!你這毒婦竟也來(lái)了担钮?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尤仍,失蹤者是張志新(化名)和其女友劉穎箫津,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宰啦,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苏遥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赡模。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片田炭。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漓柑,靈堂內(nèi)的尸體忽然破棺而出教硫,到底是詐尸還是另有隱情,我是刑警寧澤辆布,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布瞬矩,位于F島的核電站,受9級(jí)特大地震影響锋玲,放射性物質(zhì)發(fā)生泄漏丧鸯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一嫩絮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧围肥,春花似錦剿干、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至氢伟,卻和暖如春榜轿,著一層夾襖步出監(jiān)牢的瞬間幽歼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谬盐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甸私,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓飞傀,卻偏偏與公主長(zhǎng)得像皇型,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砸烦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容