深入理解Android中的緩存機(jī)制(二)RecyclerView跟ListView緩存機(jī)制對(duì)比

概述

前面介紹過(guò)了临庇,內(nèi)存緩存主要是指在內(nèi)部存儲(chǔ)器存儲(chǔ)數(shù)據(jù)零截,可能大家聽得比較多的就是LruCache算法麸塞,里面會(huì)涉及到內(nèi)存緩存,下面以就以Android中比較常見的兩個(gè)控件涧衙,ListView/GridView跟RecyclerView來(lái)分析一下他們是如何通過(guò)緩存復(fù)用Item哪工,來(lái)展示大量數(shù)據(jù),由于ListView已經(jīng)有很多人分析過(guò)弧哎,其中郭霖早期寫了一篇文章Android ListView工作原理完全解析雁比,帶你從源碼的角度徹底理解,已經(jīng)分析的很到位了撤嫩,所以就不再說(shuō)ListView/GridView的繪制流程了偎捎,下面會(huì)重點(diǎn)分析一些ListView/GridView跟GridView的緩存原理,由于他們都是繼承自AbsListView序攘,所以緩存邏輯很自然地是在AbsListView中實(shí)現(xiàn)的鸭限,下面就來(lái)對(duì)一下AbsListView的緩存原理:

  • AbsListView的緩存機(jī)制:如何復(fù)用Item
  • RecyclerView的緩存機(jī)制:如何復(fù)用Item
  • 定向刷新:RecyclerView如何定向刷新Item
  • 局部刷新:RecyclerView如何實(shí)現(xiàn)局部刷新Item
  • DiffUtil:如何找出新舊集合的差異

AbsListView

注釋

AbsListView是一個(gè)抽象類,官方注釋是這么寫的

Base class that can be used to implement virtualized lists of items. A list does not have a spatial definition here. For instance, subclases of this class can display the content of the list in a grid, in a carousel, as stack, etc.

用于展示大量數(shù)據(jù)的item的基類两踏。在這里并沒有指定List的展現(xiàn)方式败京。舉例來(lái)說(shuō),這個(gè)類的子類可以在網(wǎng)格梦染,列表中展示大量的數(shù)據(jù)

通過(guò)注釋可以很明顯的知道AbsListView作為GridView以及ListView的基類赡麦,沒有固定展示數(shù)據(jù)的形式朴皆,這個(gè)是交由他的子類來(lái)實(shí)現(xiàn)的,只是ListView是列表泛粹,GridView是網(wǎng)格遂铡,下面開始從源碼的角度來(lái)分析一下AbsListView的緩存機(jī)制。

繼承關(guān)系

AdapterView

GridView跟ListView并列晶姊,繼承自AbsListView扒接,然而AbsListView又是跟AdapterViewAnimator,AbsSpinner是同類的们衙,從這里可以看出钾怔,緩存的邏輯應(yīng)該為GridView跟ListView共有,所以應(yīng)該是在AbsListView中進(jìn)行整合的蒙挑,所以我們重點(diǎn)關(guān)注AbsListView這個(gè)類就可以了宗侦。AbsListView繼承自ViewGroup,那么很自然地就會(huì)進(jìn)行onMeasure忆蚀,onLayout方法矾利,下面就跟著這兩個(gè)方法來(lái)進(jìn)行 分析。

onMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mSelector == null) {
        useDefaultSelector();
    }
   //計(jì)算padding
    final Rect listPadding = mListPadding;
    listPadding.left = mSelectionLeftPadding + mPaddingLeft;
    listPadding.top = mSelectionTopPadding + mPaddingTop;
    listPadding.right = mSelectionRightPadding + mPaddingRight;
    listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
    // 如果ranscriptMode是TRANSCRIPT_MODE_NORMAL馋袜,
    //當(dāng)Adapter中的數(shù)據(jù)集改變之后男旗,其子類會(huì)自動(dòng)滾動(dòng)到底部
    if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
        final int childCount = getChildCount();
        final int listBottom = getHeight() - getPaddingBottom();
        final View lastChild = getChildAt(childCount - 1);
        final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
        mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
                lastBottom <= listBottom;
    }
}

onLayout

子類不能覆蓋onLayout方法,需要重寫layoutChildren方法

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    mInLayout = true;
    final int childCount = getChildCount();
    if (changed) {
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
      //重新測(cè)量Child,mRecycler其實(shí)就是RecycleBin欣鳖,
      //這個(gè)是一個(gè)用于管理回收的View的回收類,一會(huì)兒?jiǎn)为?dú)分析
        mRecycler.markChildrenDirty();
    }
   //給Child布局察皇,一會(huì)兒會(huì)單獨(dú)分析
    layoutChildren();
    mInLayout = false;
    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    // TODO: Move somewhere sane. This doesn't belong in onLayout().
    if (mFastScroll != null) {
        mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
    }
}

RecycleBin

在看RecyclerBin源碼之前,我們可以感性地分析一下观堂,一般的緩存分為初始化让网,呀忧,以及清空緩存师痕,實(shí)際上RecycleBin實(shí)際上也大體分這幾個(gè)步驟。

成員變量

private RecyclerListener mRecyclerListener;
//第一個(gè)可見的View存儲(chǔ)的位置
private int mFirstActivePosition;
//可見的View數(shù)組
private View[] mActiveViews = new View[0];
//不可見的的View數(shù)組,是一個(gè)集合數(shù)組而账,每一種type的item都有一個(gè)集合來(lái)緩存
private ArrayList<View>[] mScrapViews;
//View的Type的數(shù)量
private int mViewTypeCount;
//viewType為1的集合或者說(shuō)mScrapViews的第一個(gè)元素
private ArrayList<View> mCurrentScrap;

還有三個(gè)成員變量胰坟,沒有給出,因?yàn)樯婕暗?strong>StableId以及Transient State這兩種屬性泞辐,下面解釋一下

  • StableId:就是所有的Item具有相同的ID笔横,也就是所有的Item都相同,通過(guò)復(fù)寫B(tài)aseAdapter中的hasStableIds可以進(jìn)行設(shè)置咐吼,默認(rèn)為false
  • Transient State:在這里面有一個(gè)Transient State吹缔,是View的一個(gè)屬性,說(shuō)的是View伴隨有動(dòng)畫之類的效果锯茄,對(duì)于這種狀態(tài)的View只有跟Adapter綁定的數(shù)據(jù)源沒有發(fā)生變化或者View有相同的ID的時(shí)候才能進(jìn)行緩存復(fù)用厢塘,因?yàn)檫@兩種情況下Item要么數(shù)據(jù)不變茶没,不用重新綁定數(shù)據(jù),要么View不變晚碾,不需要重新創(chuàng)建
//數(shù)據(jù)集不變的具有TransientState的View數(shù)組
private SparseArray<View> mTransientStateViews;
//具有固定ID的具有TransientState的View數(shù)組
private LongSparseArray<View> mTransientStateViewsById;
//具有TransientState狀態(tài)但是不滿足以上任意一種狀態(tài)的View數(shù)組抓半,不予緩存
private ArrayList<View> mSkippedScrap;

初始化

setViewTypeCount

public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //根據(jù)viewTypeCount初始化數(shù)組
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
        scrapViews[i] = new ArrayList<View>();
    }
    //初始化RecycleBin的數(shù)組
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;

fillActiveViews

存放屏幕上活躍的View數(shù)組

void fillActiveViews(int childCount, int firstActivePosition) {
   //檢查ActiveView是否需要擴(kuò)容
    if (mActiveViews.length < childCount) {
        mActiveViews = new View[childCount];
    }
    mFirstActivePosition = firstActivePosition;
    final View[] activeViews = mActiveViews;
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
        // Don't put header or footer views into the scrap heap
        if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
           //對(duì)用非Header以及Footer的child給activeViews數(shù)組
            activeViews[i] = child;
            // 設(shè)置position的偏移量,方便接下來(lái)的布局
            lp.scrappedFromPosition = firstActivePosition + i;
        }
    }
}

addScrapView

對(duì)不在屏幕中的View進(jìn)行緩存

void addScrapView(View scrap, int position) {
   final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
    if (lp == null) {
        // Can't recycle, but we don't know anything about the view.
        // Ignore it completely.
        return;
    }
    //設(shè)置View的位置偏移量
    lp.scrappedFromPosition = position;
    final int viewType = lp.viewType;
    //只要Type大于0就會(huì)進(jìn)行緩存
    if (!shouldRecycleViewType(viewType)) {
      //嘗試對(duì)非Header以及Footer的View進(jìn)行緩存
        if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            getSkippedScrap().add(scrap);
        }
        return;
    }
    scrap.dispatchStartTemporaryDetach();
    notifyViewAccessibilityStateChangedIfNeeded(
            AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
    // 不直接緩存具有transient state的View格嘁,用transient數(shù)組進(jìn)行緩存
    final boolean scrapHasTransientState = scrap.hasTransientState();
    if (scrapHasTransientState) {
        if (mAdapter != null && mAdapterHasStableIds) {
           //具有stableId
            if (mTransientStateViewsById == null) {
                mTransientStateViewsById = new LongSparseArray<>();
            }
           //存放進(jìn)mTransientStateViewsById數(shù)組
            mTransientStateViewsById.put(lp.itemId, scrap);
        } else if (!mDataChanged) {
           //數(shù)據(jù)集合沒有改變笛求,暫時(shí)沒想到使用場(chǎng)景
            if (mTransientStateViews == null) {
                mTransientStateViews = new SparseArray<>();
            }
          //存放進(jìn)mTransientStateViews數(shù)組
            mTransientStateViews.put(position, scrap);
        } else {
            // 除此之外,不予緩存糕簿,放入mSkippedScrap過(guò)濾數(shù)組
            getSkippedScrap().add(scrap);
        }
    } else {
      //非transient state數(shù)組探入,
        if (mViewTypeCount == 1) {
          //只有一種type,直接添加進(jìn)mCurrentScrap
            mCurrentScrap.add(scrap);
        } else {
          //多type冶伞,則存放進(jìn)對(duì)應(yīng)type的數(shù)組
            mScrapViews[viewType].add(scrap);
        }
        //回調(diào)函數(shù)新症,通知View已經(jīng)移動(dòng)到Scrap進(jìn)行緩存
        if (mRecyclerListener != null) {
            mRecyclerListener.onMovedToScrapHeap(scrap);
        }
    }
}

reclaimViews

開辟新集合,存儲(chǔ)所有的View包含ActivieView以及ScrapView

public void reclaimViews(List<View> views) {
    int childCount = getChildCount();
    RecyclerListener listener = mRecycler.mRecyclerListener;
    // Reclaim views on screen
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
        // Don't reclaim header or footer views, or views that should be ignored
        if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
            views.add(child);
            child.setAccessibilityDelegate(null);
            if (listener != null) {
                // Pretend they went through the scrap heap
                listener.onMovedToScrapHeap(child);
            }
        }
    }
   //添加緩存的View
    mRecycler.reclaimScrapViews(views);
    removeAllViewsInLayout();
}

getScrapView

獲取緩存的View

View getScrapView(int position) {
   //拿到當(dāng)前位置View的type
    final int whichScrap = mAdapter.getItemViewType(position);
    if (whichScrap < 0) {
        return null;
    }
    if (mViewTypeCount == 1) {
       //type的種類為1响禽,直接從第一個(gè)數(shù)組中取
        return retrieveFromScrap(mCurrentScrap, position);
    } else if (whichScrap < mScrapViews.length) {
      //直接從對(duì)應(yīng)的type數(shù)組中取
        return retrieveFromScrap(mScrapViews[whichScrap], position);
    }
    return null;
}

getActiveView

根據(jù)位置獲取屏幕中顯示的某一個(gè)數(shù)組

   View getActiveView(int position) {
            int index = position - mFirstActivePosition;
            final View[] activeViews = mActiveViews;
            if (index >=0 && index < activeViews.length) {
                final View match = activeViews[index];
              //獲取之后將數(shù)組置空徒爹,便于虛擬機(jī)回收
                activeViews[index] = null;
                return match;
            }
            return null;
        }

getTransientStateView

根據(jù)position獲取TransientStateView,獲取后會(huì)移除相應(yīng)的View

View getTransientStateView(int position) {
    if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
        long id = mAdapter.getItemId(position);
        View result = mTransientStateViewsById.get(id);
        mTransientStateViewsById.remove(id);
        return result;
    }
    if (mTransientStateViews != null) {
        final int index = mTransientStateViews.indexOfKey(position);
        if (index >= 0) {
            View result = mTransientStateViews.valueAt(index);
            mTransientStateViews.removeAt(index);
            return result;
        }
    }
    return null;
}

getSkippedScrap

獲取過(guò)濾掉也是不緩存的數(shù)組

private ArrayList<View> getSkippedScrap() {
    if (mSkippedScrap == null) {
        mSkippedScrap = new ArrayList<>();
    }
    return mSkippedScrap;
}

retrieveFromScrap

從一個(gè)type對(duì)應(yīng)的緩存集合中尋找指定位置的View

private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
    final int size = scrapViews.size();
    if (size > 0) {
        // See if we still have a view for this position or ID.
        for (int i = 0; i < size; i++) {
            final View view = scrapViews.get(i);
            final AbsListView.LayoutParams params =
             (AbsListView.LayoutParams) view.getLayoutParams();
            if (mAdapterHasStableIds) {
                final long id = mAdapter.getItemId(position);
                if (id == params.itemId) {
                    return scrapViews.remove(i);
                }
            } else if (params.scrappedFromPosition == position) {
                final View scrap = scrapViews.remove(i);
                clearAccessibilityFromScrap(scrap);
                return scrap;
            }
        }
        final View scrap = scrapViews.remove(size - 1);
        clearAccessibilityFromScrap(scrap);
        return scrap;
    } else {
        return null;
    }
}

清除

clearTransientStateViews

清空TransientStateViews

void clearTransientStateViews() {
    final SparseArray<View> viewsByPos = mTransientStateViews;
    if (viewsByPos != null) {
        final int N = viewsByPos.size();
        for (int i = 0; i < N; i++) {
            removeDetachedView(viewsByPos.valueAt(i), false);
        }
        viewsByPos.clear();
    }

    final LongSparseArray<View> viewsById = mTransientStateViewsById;
    if (viewsById != null) {
        final int N = viewsById.size();
        for (int i = 0; i < N; i++) {
            removeDetachedView(viewsById.valueAt(i), false);
        }
        viewsById.clear();
    }
}

removeSkippedScrap

刪除過(guò)濾掉的數(shù)組

void removeSkippedScrap() {
    if (mSkippedScrap == null) {
        return;
    }
    final int count = mSkippedScrap.size();
    for (int i = 0; i < count; i++) {
        removeDetachedView(mSkippedScrap.get(i), false);
    }
    mSkippedScrap.clear();
}

clearScrap

清空指定的Scrap集合

private void clearScrap(final ArrayList<View> scrap) {
    final int scrapCount = scrap.size();
    for (int j = 0; j < scrapCount; j++) {
        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
    }
}

clear

清空所有緩存數(shù)組

void clear() {
    if (mViewTypeCount == 1) {
        final ArrayList<View> scrap = mCurrentScrap;
        clearScrap(scrap);
    } else {
        final int typeCount = mViewTypeCount;
        for (int i = 0; i < typeCount; i++) {
            final ArrayList<View> scrap = mScrapViews[i];
            clearScrap(scrap);
        }
    }
    clearTransientStateViews();
}

pruneScrapViews

當(dāng)緩存的數(shù)組大于Activite數(shù)組之后就需要進(jìn)行清理了芋类,主要是因?yàn)閠ransient state View數(shù)組中View的transient屬性已經(jīng)消失了隆嗅,所以相當(dāng)于會(huì)被緩存兩邊,一遍是具有transient state的View侯繁,一邊是這些View不再具有transient state的時(shí)候又會(huì)進(jìn)行緩存一次胖喳,這樣就會(huì)導(dǎo)致緩存中的數(shù)組大于Active數(shù)組,所以進(jìn)行處理贮竟。

private void pruneScrapViews() {
    final int maxViews = mActiveViews.length;
    final int viewTypeCount = mViewTypeCount;
    final ArrayList<View>[] scrapViews = mScrapViews;
   //Scrap數(shù)組遍歷
    for (int i = 0; i < viewTypeCount; ++i) {
        final ArrayList<View> scrapPile = scrapViews[i];
        int size = scrapPile.size();
        while (size > maxViews) {
            scrapPile.remove(--size);
        }
    }
    //transViewsByPos遍歷
    final SparseArray<View> transViewsByPos = mTransientStateViews;
    if (transViewsByPos != null) {
        for (int i = 0; i < transViewsByPos.size(); i++) {
            final View v = transViewsByPos.valueAt(i);
            if (!v.hasTransientState()) {
                removeDetachedView(v, false);
                transViewsByPos.removeAt(i);
                i--;
            }
        }
    }
    //mTransientStateViewsById遍歷
    final LongSparseArray<View> transViewsById = mTransientStateViewsById;
    if (transViewsById != null) {
        for (int i = 0; i < transViewsById.size(); i++) {
            final View v = transViewsById.valueAt(i);
            if (!v.hasTransientState()) {
                removeDetachedView(v, false);
                transViewsById.removeAt(i);
                i--;
            }
        }
    }
}

markChildrenDirty

此方法會(huì)重新調(diào)用緩存的child的forcelayout,forcelayout跟requestLayout的區(qū)別在于前者只會(huì)重新執(zhí)行自己的onMeasure跟onLayout丽焊,后者不僅僅會(huì)執(zhí)行前者,還會(huì)調(diào)用parentView的requestLayout咕别,此方法會(huì)在ListView的size改變的時(shí)候進(jìn)行調(diào)用技健。

public void markChildrenDirty() {
    if (mViewTypeCount == 1) {
        final ArrayList<View> scrap = mCurrentScrap;
        final int scrapCount = scrap.size();
        for (int i = 0; i < scrapCount; i++) {
            scrap.get(i).forceLayout();
        }
    } else {
        final int typeCount = mViewTypeCount;
        for (int i = 0; i < typeCount; i++) {
            final ArrayList<View> scrap = mScrapViews[i];
            final int scrapCount = scrap.size();
            for (int j = 0; j < scrapCount; j++) {
                scrap.get(j).forceLayout();
            }
        }
    }
    if (mTransientStateViews != null) {
        final int count = mTransientStateViews.size();
        for (int i = 0; i < count; i++) {
            mTransientStateViews.valueAt(i).forceLayout();
        }
    }
    if (mTransientStateViewsById != null) {
        final int count = mTransientStateViewsById.size();
        for (int i = 0; i < count; i++) {
            mTransientStateViewsById.valueAt(i).forceLayout();
        }
    }
}

?

上面詳細(xì)分了RecyclerBin的成員變量以及生命周期,內(nèi)部通過(guò)定義了多個(gè)數(shù)組來(lái)對(duì)不同類型的View進(jìn)行緩存惰拱,那么AbsListView的子類也就是ListView以及GridView滾動(dòng)雌贱,以及調(diào)用notifyDataSetChanged的時(shí)候,然后進(jìn)行重新繪制偿短,先從緩存中取如果沒有取到則會(huì)重新創(chuàng)建一個(gè)View欣孤,關(guān)于這方面的分析網(wǎng)上已經(jīng)有很多文章了,只是很多文章沒有對(duì)RecycleBin的TranslateStateView數(shù)組進(jìn)行分析昔逗,自己之前也是一直沒有完全理解降传,所以重點(diǎn)分析了RecycleBin這個(gè)類,至于ListView以及GridView的繪制以及刷新機(jī)制勾怒,其實(shí)相對(duì)于RecyclerView來(lái)講其實(shí)是比較簡(jiǎn)單的婆排,所以大部分精力也將用來(lái)分析RecyclerView的緩存機(jī)制

RecyclerView

注釋

A flexible view for providing a limited window into a large data set

能夠在有限的窗口內(nèi)展示大量數(shù)據(jù)集合的一個(gè)靈活的View款票。

相對(duì)于AbsListView的兩個(gè)子類ListView以及GridView來(lái)講,RecyclerView最大一個(gè)特性就是靈活泽论,主要體現(xiàn)在以下兩個(gè)方面

  • 多樣式:可以對(duì)數(shù)據(jù)的展示進(jìn)行自有定制艾少,可以是列表,網(wǎng)格甚至是瀑布流翼悴,除此之外你還可以自定義樣式
  • 定向刷新:可以對(duì)指定的Item數(shù)據(jù)進(jìn)行刷新
  • 刷新動(dòng)畫:RecyclerView支持對(duì)Item的刷新添加動(dòng)畫
  • 添加裝飾:相對(duì)于ListView以及GridView的單一的分割線缚够,RecyclerView可以自定義添加分割樣式

今天我們的重點(diǎn)在于緩存,所以重點(diǎn)研究定向刷新鹦赎,除了對(duì)整體數(shù)據(jù)進(jìn)行刷新之外谍椅,RecyclerView還提供了很多定向刷新的方法。

notifData

由于RecyclerView的很多核心功能都是通過(guò)內(nèi)部類來(lái)實(shí)現(xiàn)的古话,而且作者又是把內(nèi)部類寫在一個(gè)類里面雏吭,所以還是要先對(duì)每個(gè)類的功能進(jìn)行簡(jiǎn)單介紹一下,整個(gè)RecyclerView的源代碼有1萬(wàn)多行陪踩,不可能也沒必要面面俱到杖们,還是有針對(duì)性地進(jìn)行分析,不然看源碼絕對(duì)會(huì)走火入魔肩狂。

RecyclerView
內(nèi)部類
RecyclerView.LayoutManager 負(fù)責(zé)Item視圖的布局的顯示管理
RecyclerView.ItemDecoration 給每一項(xiàng)Item視圖添加修飾的View,
RecyclerView.Adapter 為每一項(xiàng)Item創(chuàng)建視圖
RecyclerView.ViewHolder 承載Item視圖的子布局
RecyclerView.ItemAnimator 負(fù)責(zé)處理數(shù)據(jù)添加或者刪除時(shí)候的動(dòng)畫效果
RecyclerView.Cache Recycler/RecycledViewPool/ViewCacheExtension

他們的作用如下表

內(nèi)部類
RecyclerView.LayoutManager 負(fù)責(zé)Item視圖的布局的顯示管理
RecyclerView.ItemDecoration 給每一項(xiàng)Item視圖添加修飾的View,
RecyclerView.Adapter 為每一項(xiàng)Item創(chuàng)建視圖
RecyclerView.ViewHolder 承載Item視圖的子布局
RecyclerView.ItemAnimator 負(fù)責(zé)處理數(shù)據(jù)添加或者刪除時(shí)候的動(dòng)畫效果
RecyclerView.Cache Recycler/RecycledViewPool/ViewCacheExtension

最后一個(gè)Cache是我加上去的摘完,實(shí)際上并沒有這個(gè)類,主要是為了說(shuō)明RecyclerView對(duì)Item強(qiáng)大的緩存傻谁,也是接下來(lái)重點(diǎn)分析的對(duì)象

Observer&Observable

Observer

Recycler沒有采用系統(tǒng)的Observer孝治,因?yàn)樽约盒枰邮胀ㄖ姆椒ㄟ^(guò)多,所以自己設(shè)計(jì)了一個(gè)觀察者AdapterDataObserver审磁,先看一下繼承關(guān)系

AdapterDataObserver

AdapterDataObserver

public static abstract class AdapterDataObserver {
    public void onChanged() {
        // Do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        onItemRangeChanged(positionStart, itemCount);
    }

    public void onItemRangeInserted(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeRemoved(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        // do nothing
    }
}

RecyclerViewDataObserver

private class RecyclerViewDataObserver extends AdapterDataObserver {
    RecyclerViewDataObserver() {
    }
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;

        setDataSetChangedAfterLayout();
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }

    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
}

這里面在notify的方法里面谈飒,提到了一個(gè)類mAdapterHelper,顧名思義是Adapter的幫助類态蒂,他是AdapterHelper的實(shí)例杭措,用來(lái)幫助Adapter進(jìn)行數(shù)據(jù)更新,除此之外還有幾個(gè)類也是相對(duì)比較重要的,下面會(huì)簡(jiǎn)單介紹一下吃媒,因?yàn)镽ecyclerView的源碼有1萬(wàn)多行瓤介,作者幾乎是把所有與RecyclerView相關(guān)的類搞成了內(nèi)部類吕喘,可能作者對(duì)內(nèi)部類有一種特殊的感情赘那,問(wèn)題是你可以這樣子搞,但是代碼能不能多加點(diǎn)注釋氯质,很多方法完全沒注釋募舟,看起來(lái)真心一臉懵逼的類。

Observable

RecyclerView實(shí)際采用了系統(tǒng)的Observable闻察,看一下繼承關(guān)系

AdapterDataObservable

Observable

代碼比較簡(jiǎn)單拱礁,不忍注釋

public abstract class Observable<T> {
    //注冊(cè)的觀察者
    protected final ArrayList<T> mObservers = new ArrayList<T>();
  //注冊(cè)單個(gè)觀察者
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }
  //解綁單個(gè)觀察者
    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
      throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }

    //移除所有的觀察者
    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }
    }
}

AdapterDataObservable

比較簡(jiǎn)單琢锋,懶得注釋,寫出來(lái)是便于梳理整個(gè)流程

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public boolean hasObservers() {
        return !mObservers.isEmpty();
    }

    public void notifyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount) {
        notifyItemRangeChanged(positionStart, itemCount, null);
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }

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

    public void notifyItemRangeRemoved(int positionStart, int itemCount) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
        }
    }
    public void notifyItemMoved(int fromPosition, int toPosition) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
        }
    }
}

看完了RecyclerView的觀察者呢灶,感覺實(shí)際上根本不需要繼承系統(tǒng)的被觀察者以及自定義觀察者吴超,因?yàn)橹挥幸粋€(gè)觀察者,所以干脆使用接口回調(diào)鸯乃,口味更佳鲸阻,下面繼續(xù)分析RecyclerView緩存的核心類。

Recycler

在Recycler中實(shí)際上緩存VieHolder的有2類集合缨睡,一類是可見的ViewHolder數(shù)組鸟悴,一類是不可見的ViewHolder數(shù)組咪橙,其中可見的數(shù)組中又分為數(shù)據(jù)改變跟沒有改變的盆繁,理解了這些,其實(shí)去看

注釋

 A Recycler is responsible for managing scrapped or detached item views for reuse.
 A "scrapped" view is a view that is still attached to its parent RecyclerView but
 that has been marked for removal or reuse.
 

Recycler是用于管理廢棄的item或者從RecyclerView中移除的View便于復(fù)用怕午,廢棄的View是指仍然依附在RecyclerView中陋守,但是已經(jīng)被標(biāo)記為移除的或者可以復(fù)用的震贵。

跟ListView的RecycleBin一樣,Recycler也是RecyclerView設(shè)計(jì)的一個(gè)專門用于回收ViewHolder的類水评,其實(shí)RecyclerView的緩存機(jī)制是在ListView的緩存機(jī)制的基礎(chǔ)上進(jìn)一步的完善屏歹,所以在Recycler中能看到很多跟RecycleBin一樣的設(shè)計(jì)思想,在緩存這個(gè)層面上之碗,RecyclerView實(shí)際上并沒有做出太大的創(chuàng)新蝙眶,最大的創(chuàng)新來(lái)源于給每一個(gè)ViewHolder增加了一個(gè)UpdateOp,通過(guò)這個(gè)標(biāo)志可以進(jìn)行定向刷新指定的Item褪那,并且通過(guò)Payload參數(shù)可以對(duì)Item進(jìn)行局部刷新幽纷,我覺得這個(gè)是RecyclerView最厲害的地方,大大提高了刷新時(shí)候的性能博敬,如果數(shù)據(jù)源需要經(jīng)常變動(dòng)友浸,那么RecyclerView是你最好的選擇,沒有之一偏窝,下面看一下Recycler是如何進(jìn)行緩存的收恢。

成員變量

static final int DEFAULT_CACHE_SIZE = 2;//默認(rèn)緩存的數(shù)量
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;//設(shè)置的緩存最大數(shù)量,默認(rèn)為2
int mViewCacheMax = DEFAULT_CACHE_SIZE;//View的緩存的最大數(shù)量祭往,默認(rèn)為2
RecycledViewPool mRecyclerPool;//RecycledView伦意,用來(lái)公用RecyclerView
//緩存的擴(kuò)展,可以用來(lái)對(duì)指定的position跟Type進(jìn)行緩存
private ViewCacheExtension mViewCacheExtension;
ArrayList<ViewHolder> mChangedScrap = null;//數(shù)據(jù)源更改過(guò)的AttachedScrap
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//依附的緩存View
//緩存的全部View硼补,包含可見跟不可見的ViewHolder
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder> mUnmodifiableAttachedScrap  = Collections.unmodifiableList(mAttachedScrap);//mAttachedScrap的不可變集合

初始化

我們發(fā)現(xiàn)在成員變量中沒有初始化的只有兩個(gè)變量mChangedScrap驮肉,mRecyclerPool,mChangedScrap是可見的數(shù)據(jù)源改變的ViewHolder集合已骇,mRecyclerPool是RecycledViewPool的集合离钝,用來(lái)緩存RecyclerView的集合票编,可以實(shí)現(xiàn)RecyclerView的復(fù)用。

mChangedScrap是在scrapView這個(gè)方法進(jìn)行初始化的

 if (mChangedScrap == null) {
 mChangedScrap = new ArrayList<ViewHolder>();
        }

RecycledViewPool初始化

void setRecycledViewPool(RecycledViewPool pool) {
    if (mRecyclerPool != null) {
        mRecyclerPool.detach();
    }
    mRecyclerPool = pool;
    if (pool != null) {
        mRecyclerPool.attach(getAdapter());
    }
}

recycleView

回收不可見的View卵渴,對(duì)于某些特定的View會(huì)放進(jìn)RecyclerViewPool慧域,如果這個(gè)ViewHolder是從緩存中取的,那么就會(huì)清空緩存中View

public void recycleView(View view) {
   //這邊傳遞過(guò)來(lái)的是一個(gè)View浪读,然后通過(guò)View獲取ViewHolder
    ViewHolder holder = getChildViewHolderInt(view);
   //如果Holder被已經(jīng)被打上了移除的標(biāo)記吊趾,那么就從移除此View
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
  
    if (holder.isScrap()) {
      //如果此Holder是來(lái)自緩存的可見的ViewHolder數(shù)組,清楚緩存
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
      //如果此Holder是來(lái)自緩存的不可見的ViewHolder數(shù)組瑟啃,清除緩存
        holder.clearReturnedFromScrapFlag();
    }
   //開始緩存论泛,繼續(xù)追源碼
    recycleViewHolderInternal(holder);
}

recycleViewHolderInternal

void recycleViewHolderInternal(ViewHolder holder) {
    //此處省略若干行條件判斷代碼
    if (forceRecycle || holder.isRecyclable()) {
        //如果緩存數(shù)量大于0,并且ViewHolder的Flag標(biāo)志是有效的
       //且非REMOVED跟UPDATE蛹屿,進(jìn)行緩存屁奏,
        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) {
              // 移除cachedViews中的第一個(gè)View,也就是第一個(gè)
                recycleCachedViewAt(0);
                cachedViewSize--;
            }
            //獲取添加元素的在緩存集合中的下標(biāo)
            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;
                    //緩存的時(shí)候不能覆蓋最近經(jīng)常被使用到緩存
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            //添加緩存
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
      //如果沒有緩存的話就添加進(jìn)RecycledViewPool
        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());
        }
    }
   //mViewInfoStore中移除這條記錄
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

Recycler的緩存做了很多優(yōu)化错负,實(shí)際上采用了LFU算法坟瓢,也就是最少使用策略,當(dāng)我們存儲(chǔ)ViewHolder的時(shí)候犹撒,會(huì)去判斷這個(gè)ViewHolder是否是來(lái)自緩存折联,如果是的話,那么在此緩存的時(shí)候不能覆蓋最近使用比較頻繁的緩存识颊,而是自己定義了一個(gè)類GapWorker,他有一個(gè)內(nèi)部類LayoutPrefetchRegistryImpl诚镰,然后定義了一個(gè)數(shù)組

int[] mPrefetchArray;

這個(gè)數(shù)組是用來(lái)記錄最近使用過(guò)的緩存Holder,所以我們?cè)诖娴臅r(shí)候會(huì)跟這里面存儲(chǔ)過(guò)的ViewHolder進(jìn)行匹配祥款,上面代碼中已經(jīng)進(jìn)行了注釋清笨,比較好理解。

recycleAndClearCachedViews

將CacheViews中的ViewHolder添加進(jìn)RecyclerViewHolder刃跛,然后清空CacheViews

void recycleAndClearCachedViews() {
    final int count = mCachedViews.size();
    for (int i = count - 1; i >= 0; i--) {
    //將CacheView中的添加進(jìn)RecyclerViewPool
        recycleCachedViewAt(i);
    }
    //清空集合
    mCachedViews.clear();
    if (ALLOW_THREAD_GAP_WORK) {
        mPrefetchRegistry.clearPrefetchPositions();
    }
}

recycleCachedViewAt

void recycleCachedViewAt(int cachedViewIndex) {
    //省略一下判斷代碼
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    //添加進(jìn)ToRecycledViewPool
    addViewHolderToRecycledViewPool(viewHolder, true);
    //從mCachedViews移除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;
   //放入RecyclerViewPool
    getRecycledViewPool().putRecycledView(holder);
}

取緩存有很多入口抠艾,但是最終都是調(diào)用了tryGetViewHolderForPositionByDeadline,下面重點(diǎn)分析一下此方法

tryGetViewHolderForPositionByDeadline

   /**
     * @param position Position of ViewHolder to be returned.
     * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
     * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work             should complete. If FOREVER_NS is passed, this method will not fail to
        create/bind the holder if needed.
    */
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(
  int position,boolean dryRun, long deadlineNs) {
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) 首先從Attached中的Changed數(shù)組中取
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) 分別從AttachedScrap,Hidden桨昙,Cached中獲取ViewHolder
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                if (!dryRun) {
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2)通過(guò)StableId進(jìn)行獲取检号,針對(duì)復(fù)寫了BaseAdapter的StableId
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
       //  3)通過(guò)mViewCacheExtension進(jìn)行獲取
        if (holder == null && mViewCacheExtension != null) {
            final View view = mViewCacheExtension
                   .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
        //省略Holder的判斷邏輯
        }
        //  4)無(wú)路可退,通過(guò)RecyclerViewPool進(jìn)行獲取
        if (holder == null) { 
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
       //  5)如果上面都沒有獲取到蛙酪,那么就說(shuō)明是第一屏齐苛,所以就得重新創(chuàng)建
        if (holder == null) {
            long start = getNanoTime();
          //省略判空代碼
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            if (ALLOW_THREAD_GAP_WORK) {
                // 放入最近最少使用的隊(duì)列中
                RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                if (innerView != null) {
                    holder.mNestedRecyclerView = new WeakReference<>(innerView);
                }
            }
            long end = getNanoTime();
            mRecyclerPool.factorInCreateTime(type, end - start);
        }
    }

    //在返回給LayoutManager重新繪制前,需要更新一下ViewHolder的相關(guān)信息
    if (fromScrapOrHiddenOrCache && !mState.isPreLayout() 
        && holder.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
        holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
      //記錄動(dòng)畫信息
        if (mState.mRunSimpleAnimations) {
            int changeFlags = ItemAnimator.buildAdapterChangeFlagsForAnimations(holder);
            changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
            final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(
              mState, holder, changeFlags, holder.getUnmodifiedPayloads());
          //如果當(dāng)前的ViewHolder已經(jīng)綁定過(guò)數(shù)據(jù)滤否,那么記錄一下動(dòng)畫信息
            recordAnimationInfoIfBouncedHiddenView(holder, info);
        }
    }

    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
      //綁定過(guò)數(shù)據(jù)的ViewHolder
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
      //未綁定數(shù)據(jù)的ViewHolder需要進(jìn)行數(shù)據(jù)綁定
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    // 將ViewHolder設(shè)置給ViewGroup.LayoutParams
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
    return holder;
}

將AttachedView進(jìn)行緩存

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
      //數(shù)據(jù)源未改變脸狸,放入mAttachedScrap
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
     //省略部分邏輯判斷代碼
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
       //數(shù)據(jù)源未改變最仑,放入mAttachedScrap
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

清空

clear

將CachedView添加進(jìn)ViewHolder藐俺,并且清空

public void clear() {
    mAttachedScrap.clear();
    recycleAndClearCachedViews();
}

clearScrap

清空緩存中可見的Scrap數(shù)組

void clearScrap() {
    mAttachedScrap.clear();
    if (mChangedScrap != null) {
        mChangedScrap.clear();
    }
}

定向刷新

可以對(duì)指定的Item進(jìn)行刷新炊甲,是RecyclerView的又一特點(diǎn),RecyclerView在觀察者模式中的Observer中新增了很多方法欲芹,用于定向刷新卿啡,這個(gè)在前面的觀察者模式中已經(jīng)提到過(guò),下面從源碼的角度分析一下原理菱父,我們拿adapter.notifyItemChanged(0),最終會(huì)RecyclerViewDataObserver的方法

onItemRangeChanged

@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    //繼續(xù)追蹤mAdapterHelper
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

首先調(diào)用了AdapterHelper的onItemRangeChanged方法颈娜,這里簡(jiǎn)單說(shuō)一下AdapterHelper,用來(lái)幫助Adapter更新數(shù)組的浙宜,類似于ChildHelper幫助LayoutManager進(jìn)行布局

boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    if (itemCount < 1) {
        return false;
    }
  //將需要更新的Item的范圍記錄下來(lái)官辽,并添加更新標(biāo)識(shí)
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
    mExistingUpdateTypes |= UpdateOp.UPDATE;
    return mPendingUpdates.size() == 1;
}

然后接著調(diào)用了triggerUpdateProcessor方法

void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
      //如果有動(dòng)畫,先執(zhí)行動(dòng)畫粟瞬,然后再進(jìn)行測(cè)量
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
       //重新測(cè)量
        requestLayout();
    }
}

其實(shí)看到這里同仆,發(fā)現(xiàn)AdapterHelper只是記錄了需要刷新的Item的范圍,然后就開始進(jìn)行重新測(cè)量了裙品,那么很明顯俗批,不管是全部刷新還是指定范圍的定向刷新,RecyclerView都是需要重新測(cè)量的市怎,所以岁忘,定向刷新的真正語(yǔ)義是對(duì)指定范圍的ViewHolder進(jìn)行數(shù)據(jù)刷新,不會(huì)像ListView一樣区匠,刷新所有的干像,所以我們只能從布局的時(shí)候來(lái)查找,RecyclerView的布局是交給LayoutMananger來(lái)進(jìn)行布局的驰弄,那么根據(jù)AbsListView一樣蝠筑,LayoutMananger一定會(huì)在自身當(dāng)中定義一些公共方法給子類實(shí)現(xiàn),這個(gè)方法實(shí)際上就是onLayoutChildren揩懒,還有onLayoutCompleted什乙,是在繪制完成的時(shí)候進(jìn)行調(diào)用,進(jìn)行資源清理

public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
//做一些清理工作
 public void onLayoutCompleted(State state) {
        }

我們繼續(xù)查看子類中的實(shí)現(xiàn)

onLayoutChildren

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
  // layout algorithm:
  // 1) by checking children and other variables, find an anchor coordinate and an anchor
  //  item position.
  // 2) fill towards start, stacking from bottom
  // 3) fill towards end, stacking from top
  // 4) scroll to fulfill requirements like stack from bottom.
   //開始布局
   fill(recycler, mLayoutState, state, false);
}
  

繼續(xù)追蹤fill方法

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        if (VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        //循環(huán)中調(diào)用了此方法
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (VERBOSE_TRACING) {
            TraceCompat.endSection();
        }
        if (layoutChunkResult.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
   
        if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResult.mConsumed;
        }

        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;
}

繼續(xù)追蹤layoutChunk

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    if (view == null) {
        if (DEBUG && layoutState.mScrapList == null) {
            throw new RuntimeException("received null view when unexpected");
        }
        result.mFinished = true;
        return;
    }
    LayoutParams params = (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;
        }
    }
 
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

我看了一遍已球,這里面并沒有給出是否BindViewHolder的方法臣镣,所以最開始的思路錯(cuò)了,應(yīng)該是在layoutChildren調(diào)用之前就已經(jīng)確定是否綁定智亮,然后我們發(fā)現(xiàn)layoutChildren是在dispatchLayoutStep1/2/3中都有調(diào)用忆某,這個(gè)就尷尬了,不過(guò)我們繼續(xù)查看此方法阔蛉,發(fā)現(xiàn)不僅僅這三個(gè)方法在會(huì)在dispatchLayout中被調(diào)用到弃舒,

void dispatchLayout() {
    if (mAdapter == null) {
        Log.e(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "No layout manager attached; skipping layout");
        // leave the state in START
        return;
    }
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
            mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

而dispatchLayout這個(gè)方法在onLayout中單獨(dú)進(jìn)行調(diào)用,而且只調(diào)用了這一個(gè)方法,那么沒有疑問(wèn)聋呢,應(yīng)該是在布局的時(shí)候來(lái)判斷苗踪,所以先查看一下dispatchLayoutStep這三兄弟

dispatchLayout

根據(jù)注釋,我們是在第一個(gè)方法里面進(jìn)行ViewHolder的更新削锰,所以我們重點(diǎn)查看一下dispatchLayoutStep1這個(gè)方法

dispatchLayoutStep1

private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    mState.mIsMeasuring = false;
    eatRequestLayout();
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    saveFocusInfo();
    //命名太規(guī)范了通铲,一下子就找到了,繼續(xù)追蹤
    processAdapterUpdatesAndSetAnimationFlags(); 
}

processAdapterUpdatesAndSetAnimationFlags

private void processAdapterUpdatesAndSetAnimationFlags() {
    if (mDataSetHasChangedAfterLayout) {
        // Processing these items have no value since data set changed unexpectedly.
        // Instead, we just reset it.
        mAdapterHelper.reset();
        markKnownViewsInvalid();
      //最開始以為是在這個(gè)方法中進(jìn)行回調(diào)的器贩,后來(lái)發(fā)現(xiàn)在子類是空實(shí)現(xiàn)颅夺,
      //也就是說(shuō)這個(gè)方法供使用著自己定義,通知回調(diào)
        mLayout.onItemsChanged(this);
    }
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
      //更新ViewHolder
        mAdapterHelper.consumeUpdatesInOnePass();
    }
    //省略動(dòng)畫相關(guān)操作
}

consumeUpdatesInOnePass

void consumeUpdatesInOnePass() {
    consumePostponedUpdates();
    final int count = mPendingUpdates.size();
    for (int i = 0; i < count; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
         case UpdateOp.ADD:
          mCallback.onDispatchSecondPass(op);
          mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                break;
         case UpdateOp.REMOVE:
           mCallback.onDispatchSecondPass(op);
           mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
                break;
          case UpdateOp.UPDATE:
              //接口回調(diào)
           mCallback.onDispatchSecondPass(op);
            mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                break;
            case UpdateOp.MOVE:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                break;
        }
        if (mOnItemProcessedCallback != null) {
            mOnItemProcessedCallback.run();
        }
    }
  //這里也是回收蛹稍,實(shí)際上這個(gè)回收不是循環(huán)利用吧黄,就是清空的意思
    recycleUpdateOpsAndClearList(mPendingUpdates);
    mExistingUpdateTypes = 0;
}

這個(gè)接口是初始化的時(shí)候傳入的,我們追蹤此接口

@Override
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
  //這個(gè)方法才是真正的更新Adapter的ViewHolder唆姐,繼續(xù)跟進(jìn)
    viewRangeUpdate(positionStart, itemCount, payload);
    mItemsChanged = true;
}

@Override
public void onDispatchFirstPass(UpdateOp op) {
  //他的方法都是空實(shí)現(xiàn)稚字,子類也沒有實(shí)現(xiàn),我們可以復(fù)寫來(lái)接收通知
    dispatchUpdate(op);
}

void dispatchUpdate(UpdateOp op) {
    switch (op.cmd) {
        case UpdateOp.ADD:
            mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case UpdateOp.REMOVE:
            mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case UpdateOp.UPDATE:
            mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
                    op.payload);
            break;
        case UpdateOp.MOVE:
            mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
            break;
    }
}

viewRangeUpdate

通過(guò)名字也能很好的查看出厦酬,進(jìn)行范圍內(nèi)的更新

void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    final int positionEnd = positionStart + itemCount;

    for (int i = 0; i < childCount; i++) {
        final View child = mChildHelper.getUnfilteredChildAt(i);
        final ViewHolder holder = getChildViewHolderInt(child);
        if (holder == null || holder.shouldIgnore()) {
            continue;
        }
        if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
           //添加Flag,這個(gè)是從緩存中獲取到ViewHolder的時(shí)候胆描,需不需要刷新的唯一標(biāo)識(shí)
            holder.addFlags(ViewHolder.FLAG_UPDATE);
           //添加payLoad參數(shù),用于ViewHolder的局部刷新
            holder.addChangePayload(payload);
            // lp cannot be null since we get ViewHolder from it.
            ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
        }
    }
  //更新緩存中的標(biāo)記位仗阅,便于數(shù)據(jù)源不改變的時(shí)候直接復(fù)用
    mRecycler.viewRangeUpdate(positionStart, itemCount);
}

局部刷新

在上面提到過(guò)昌讲,我們刷新的時(shí)候會(huì)調(diào)用一個(gè)方法,叫做notifyItemChanged减噪,通常會(huì)傳一個(gè)起始位置以及范圍短绸,其實(shí)我們還可以傳入一個(gè)Object類型的參數(shù),

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

如何獲取呢筹裕,也很簡(jiǎn)單醋闭,復(fù)寫onBindViewHolder這個(gè)方法,

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
  //點(diǎn)進(jìn)去會(huì)進(jìn)入下面的方法
    super.onBindViewHolder(holder, position, payloads);
}

默認(rèn)的實(shí)現(xiàn)是onBindViewHolder朝卒,也就是我們經(jīng)常復(fù)寫的方法

public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
    onBindViewHolder(holder, position);
}

這個(gè)參數(shù)有什么用证逻,就是用于局部刷新的,比如一個(gè)ViewHolder展示了很多關(guān)于學(xué)生的數(shù)據(jù)抗斤,姓名囚企,年齡,愛好瑞眼,學(xué)習(xí)成績(jī)龙宏,家庭住址等,但是我們只需要刷新一下學(xué)習(xí)成績(jī)伤疙,我們?cè)谡{(diào)用notifyItemChanged的時(shí)候只需要調(diào)用一下otifyItemChanged(0, score),然后在Adapter中進(jìn)行獲取就好了刷新這一項(xiàng)就好了银酗。

DiffUtil

當(dāng)我們知道我們要刷新的Item的范圍的時(shí)候,可以直接調(diào)用notifyItemChanged(position,range),當(dāng)我們更新前后的集合數(shù)據(jù)差不多黍特,只有部分差異的時(shí)候蛙讥,我們都還是調(diào)用的是notifyDataSetChanged,即全量刷新衅澈,其實(shí)谷歌已經(jīng)意識(shí)到這個(gè)問(wèn)題键菱,已經(jīng)在support包中提供了一個(gè)工具類DiffUtil谬墙,可以幫助我們進(jìn)行分析對(duì)比前后的數(shù)據(jù)差異今布,從而進(jìn)行定向刷新,我們來(lái)分析一下這個(gè)工具類:

注釋

DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one.

Diffutil是一個(gè)可以計(jì)算出兩個(gè)集合之間的差別并且輸出從一個(gè)集合到另外一個(gè)集合之間的變化的工具類

從注釋可以看出拭抬,DifUtil的主要操作主要是用來(lái)比對(duì)兩個(gè)新舊集合之間產(chǎn)生的差異部默,同時(shí)DiffUtil底層采用的是Myers差分算法,大家可以先看看這篇文章了解一下Myers差分算法造虎,不然下面的肯定很不好理解傅蹂,說(shuō)通俗一點(diǎn)就現(xiàn)在有新舊兩個(gè)集合,長(zhǎng)度分別為M算凿、N份蝴,是通過(guò)構(gòu)造一個(gè)平面直角坐標(biāo)系,然后X軸向右氓轰,用原數(shù)據(jù)集合中的元素填充橫坐標(biāo)婚夫,Y軸向下,新數(shù)據(jù)集合中的元素填充縱坐標(biāo)署鸡,

diff

然后需要計(jì)算從坐標(biāo)(0,0)到(M,N)的最短路徑案糙,圖中的對(duì)角線表示元素相同,如果走對(duì)角線的話不算在總長(zhǎng)度內(nèi)靴庆,因?yàn)闆]有出現(xiàn)差分时捌,說(shuō)白了也就是只要兩個(gè)集合中有相同的元素,那么就會(huì)在舊集合中進(jìn)行刪除或者添加操作炉抒,從而最大限度的保證從舊集合到新集合的定向刷新奢讨。

成員變量

private static final Comparator<Snake> SNAKE_COMPARATOR = new Comparator<Snake>() {
    @Override
    public int compare(Snake o1, Snake o2) {
        int cmpX = o1.x - o2.x;
        return cmpX == 0 ? o1.y - o2.y : cmpX;
    }
};

定義了一個(gè)比較器,傳入了snake(下面分析)泛型焰薄,其實(shí)Diffutil只有這一個(gè)成員變量禽笑,然后就是內(nèi)部類,下面先分析一下內(nèi)部類:

Callback


public abstract static class Callback {

    public abstract int getOldListSize();
    public abstract int getNewListSize();
   //Item是否一致
    public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
   //Item的內(nèi)容是否一致
    public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
   //看到Payload應(yīng)該很容易之前研究RecyclerView的局部刷新中的payLoad蛤奥,其實(shí)是一個(gè)意思
   //這個(gè)會(huì)傳到onBindHolder中去佳镜,作為局部刷新的標(biāo)志
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        return null;
    }
}

Snake

這個(gè)直接翻譯過(guò)來(lái)是一條蛇,通過(guò)注釋來(lái)看凡桥,DiffUtil是采用了Myers差分算法蟀伸,所以Snake實(shí)際上代表的是蛇形路徑,就是將新舊集合的差異進(jìn)一步描述出來(lái)


static class Snake {
    int x;//Position in the old list
    int y;//Position in the new list
    int size;// Number of matches. Might be 0
   
    boolean removal;//true代表移除,false代表新增
     //true, 表示在尾部添加或者移除
     //false,表示在頭部進(jìn)行添加或刪除
    boolean reverse;
}

如果你稍微了解一下Myers差分算法啊掏,就比較好理解蠢络,因?yàn)樾录鲜窃谂f集合的基礎(chǔ)上進(jìn)行變化得到的,通過(guò)增加迟蜜,刪除乃至移動(dòng)變換刹孔,所以需要知道是在當(dāng)前元素的前面或者后面進(jìn)行操作,并且需要知道是什么操作娜睛,增加或者刪除等

Range

static class Range {
    int oldListStart, oldListEnd;
    int newListStart, newListEnd;
    public Range() {
    }
    public Range(int oldListStart, int oldListEnd, int newListStart, int newListEnd) {
        this.oldListStart = oldListStart;
        this.oldListEnd = oldListEnd;
        this.newListStart = newListStart;
        this.newListEnd = newListEnd;
    }
}

貌似不需要注釋髓霞,很好理解,主要是記錄Snake的全局坐標(biāo)

DiffResult

這個(gè)是DiffResult的核心方法

public static DiffResult calculateDiff(Callback cb, boolean detectMoves) {
    final int oldSize = cb.getOldListSize();
    final int newSize = cb.getNewListSize();
    //Snake集合
    final List<Snake> snakes = new ArrayList<>();
    //Range集合
    final List<Range> stack = new ArrayList<>();
    stack.add(new Range(0, oldSize, 0, newSize));
    final int max = oldSize + newSize + Math.abs(oldSize - newSize);
    final int[] forward = new int[max * 2];
    final int[] backward = new int[max * 2];
    // We pool the ranges to avoid allocations for each recursive call.
    final List<Range> rangePool = new ArrayList<>();
    while (!stack.isEmpty()) {
        final Range range = stack.remove(stack.size() - 1);
        //采用Myers差分算法計(jì)算差分結(jié)果
        final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd,
                range.newListStart, range.newListEnd, forward, backward, max);
       //省略了一些算法畦戒,感興趣的可以自己去看源碼
    }
    // 對(duì)差分結(jié)果進(jìn)行排序
    Collections.sort(snakes, SNAKE_COMPARATOR);
    return new DiffResult(cb, snakes, forward, backward, detectMoves);
}

dispatchUpdatesTo

調(diào)用此方法方库,可以進(jìn)行刷新操作

public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
  //繼續(xù)跟蹤dispatchUpdatesTo方法
    dispatchUpdatesTo(new ListUpdateCallback() {
        @Override
        public void onInserted(int position, int count) {
            adapter.notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            adapter.notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            adapter.notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count, Object payload) {
            adapter.notifyItemRangeChanged(position, count, payload);
        }
    });
}

看完你會(huì)發(fā)現(xiàn),就是一個(gè)回調(diào)接口障斋,來(lái)回調(diào)adapter而已

dispatchUpdatesTo

根據(jù)計(jì)算得到的路徑纵潦,來(lái)對(duì)舊集合定向刷新

public void dispatchUpdatesTo(ListUpdateCallback updateCallback) {
    final BatchingListUpdateCallback batchingCallback;
    if (updateCallback instanceof BatchingListUpdateCallback) {
        batchingCallback = (BatchingListUpdateCallback) updateCallback;
    } else {
        batchingCallback = new BatchingListUpdateCallback(updateCallback);
        updateCallback = batchingCallback;
    }
    final List<PostponedUpdate> postponedUpdates = new ArrayList<>();
    int posOld = mOldListSize;
    int posNew = mNewListSize;
    //遍歷通過(guò)Myers差分算法生成最短路徑,由于是集合所以要從頭開始計(jì)算的話需要倒序遍歷
    for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) {
        final Snake snake = mSnakes.get(snakeIndex);
        final int snakeSize = snake.size;
        final int endX = snake.x + snakeSize;
        final int endY = snake.y + snakeSize;
        if (endX < posOld) {
           //右移垃环,說(shuō)明是刪除操作邀层,進(jìn)行相應(yīng)處理
            dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
        }

        if (endY < posNew) {
           //下移,說(shuō)明是新增操作遂庄,進(jìn)行相應(yīng)處理
            dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
                    endY);
        }
      //如果復(fù)寫了DiffUtil.Callback的getChangePayload方法寥院,將會(huì)支持RecyclerView的局部刷新
        for (int i = snakeSize - 1; i >= 0; i--) {
            if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) {
       //分發(fā)DiffUtil.Callback的getChangePayload方法,最終會(huì)抵達(dá)adater
                batchingCallback.onChanged(snake.x + i, 1,
                        mCallback.getChangePayload(snake.x + i, snake.y + i));
            }
        }
        posOld = snake.x;
        posNew = snake.y;
    }
    //回調(diào)Adapter,跟一下源碼
    batchingCallback.dispatchLastEvent();
}

dispatchLastEvent

public void dispatchLastEvent() {
    if (mLastEventType == TYPE_NONE) {
        return;
    }
    switch (mLastEventType) {
        case TYPE_ADD:
            mWrapped.onInserted(mLastEventPosition, mLastEventCount);
            break;
        case TYPE_REMOVE:
            mWrapped.onRemoved(mLastEventPosition, mLastEventCount);
            break;
        case TYPE_CHANGE:
            mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload);
            break;
    }
    mLastEventPayload = null;
    mLastEventType = TYPE_NONE;
}

mWrapped實(shí)際上就是DiffUtil.Callback的實(shí)例涧团,然后我們調(diào)用的時(shí)候傳入自定義的Callback接口只磷,就好

用法簡(jiǎn)介

上面說(shuō)了很多原理,下面簡(jiǎn)單介紹一下用法

  • 自定義DiffCallback繼承自DiffUtil.Callback,復(fù)寫相關(guān)方法
  • 調(diào)用DiffUtil.calculateDiff(new DiffCallBack(oldData, newData), true)泌绣,生成diffResult;
  • 調(diào)用diffResult.dispatchUpdatesTo(mAdapter);

如果還有不熟悉的話可以直接在網(wǎng)上搜相關(guān)博客钮追,用法還是比較簡(jiǎn)單的,不過(guò)有一點(diǎn)需要注意的是就是阿迈,在DiffUtil的注釋當(dāng)中元媚,說(shuō)明Myers差分算法本身是不支持item移動(dòng)的,然后谷歌自己實(shí)現(xiàn)了一套算法苗沧,支持item的移動(dòng)刊棕,但是當(dāng)數(shù)據(jù)量比較大的時(shí)候比較耗時(shí),需要在子線程中進(jìn)行計(jì)算待逞,我個(gè)人覺得只要當(dāng)前后兩個(gè)數(shù)據(jù)集合相似度較高的時(shí)候DiffUtil的效果會(huì)比較明顯甥角,這種情況下的定向刷新比較有意義。

總結(jié)

上面分析了一下ListView跟RecyclerView的緩存原理识樱,下面簡(jiǎn)單對(duì)比分析一下

AbsListView RecyclerView
緩存 View ViewHolder
定向刷新 不支持 支持
局部刷新 不支持 支持
刷新動(dòng)畫 不支持 支持
Item點(diǎn)擊 支持 不支持
分隔線 樣式單一 自定義樣式
布局方式 列表/網(wǎng)格 自定義樣式
頭尾添加 支持 不支持

通過(guò)分析可能大家已經(jīng)知道了RecyclerView相對(duì)于ListView的區(qū)別嗤无,如果我們需要頻繁的刷新列表數(shù)據(jù)以及添加動(dòng)畫的話震束,我們還是采用RecyclerView,對(duì)于前后數(shù)據(jù)量變化不大的新舊集合当犯,還可以通過(guò)DiffUtil來(lái)進(jìn)行差分垢村,這樣就能夠?qū)崿F(xiàn)定向刷新以及局部刷新,否則建議使用ListView嚎卫,因?yàn)長(zhǎng)istView內(nèi)置了很多Adapter嘉栓,類似ArrayAdapter,SimpleAdapter,CursorAdapter拓诸。

還有一種情況就是宮格列切換的功能侵佃,如下圖

switch

那個(gè)時(shí)候RecyclerView剛出來(lái),最開始是用ListView跟GridView實(shí)現(xiàn)的,就是搞了兩個(gè)恰响,一個(gè)顯示趣钱,另外一個(gè)隱藏涌献,由于當(dāng)時(shí)是采用的SwipeRefreshLayout實(shí)現(xiàn)的胚宦,所以自定義的上拉加載,在ListView跟GridView之間切換的時(shí)候特別痛苦燕垃,后來(lái)采用了RecyclerView枢劝,真的是不要太簡(jiǎn)單,動(dòng)態(tài)設(shè)置一下LayoutManager就行卜壕,底部的加載進(jìn)度條也可以通過(guò)設(shè)置GridLayoutManager.SpanSizeLookup可以很好的進(jìn)行處理您旁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市轴捎,隨后出現(xiàn)的幾起案子鹤盒,更是在濱河造成了極大的恐慌,老刑警劉巖侦副,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侦锯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡秦驯,警方通過(guò)查閱死者的電腦和手機(jī)尺碰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)译隘,“玉大人亲桥,你說(shuō)我怎么就攤上這事」淘牛” “怎么了题篷?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)厅目。 經(jīng)常有香客問(wèn)我番枚,道長(zhǎng)偿枕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任户辫,我火速辦了婚禮渐夸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渔欢。我一直安慰自己墓塌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布奥额。 她就那樣靜靜地躺著苫幢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垫挨。 梳的紋絲不亂的頭發(fā)上韩肝,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音九榔,去河邊找鬼哀峻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哲泊,可吹牛的內(nèi)容都是我干的剩蟀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼切威,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼育特!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起先朦,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缰冤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后喳魏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棉浸,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年截酷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涮拗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迂苛,死狀恐怖三热,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情三幻,我是刑警寧澤就漾,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站念搬,受9級(jí)特大地震影響抑堡,放射性物質(zhì)發(fā)生泄漏摆出。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一首妖、第九天 我趴在偏房一處隱蔽的房頂上張望偎漫。 院中可真熱鬧,春花似錦有缆、人聲如沸象踊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至袖外,卻和暖如春史隆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曼验。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工泌射, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚣驼。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓魄幕,卻偏偏與公主長(zhǎng)得像相艇,于是被迫代替她去往敵國(guó)和親颖杏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評(píng)論 25 707
  • afinalAfinal是一個(gè)android的ioc坛芽,orm框架 https://github.com/yangf...
    passiontim閱讀 15,429評(píng)論 2 45
  • 每個(gè)人的生命中都會(huì)有一段難熬的時(shí)光留储,只有自己走過(guò)來(lái),才會(huì)明白咙轩,這些都不是虛設(shè)获讳,經(jīng)歷過(guò)的都將變成財(cái)富! 01 每逢畢...
    圓不溜秋i閱讀 285評(píng)論 0 3
  • 一定要好好吃飯活喊!坦坦大爐太棒啦丐膝!
    帝國(guó)的精英閱讀 356評(píng)論 0 0
  • 優(yōu)衣庫(kù)是一家日本簡(jiǎn)約、快時(shí)尚服飾公司钾菊,優(yōu)衣庫(kù)的理念就是'Life wear'即'舒適人生'帅矗。 我開始關(guān)注優(yōu)衣庫(kù)純屬...
    若水Dewlight閱讀 144評(píng)論 0 1