概述
前面介绍过了,内存缓存主要是指在内部存储器存储数据,可能大家听得比较多的就是LruCache算法,里面会涉及到内存缓存,下面以就以Android中比较常见的两个控件,ListView/GridView跟RecyclerView来分析一下他们是如何通过缓存复用Item,来展示大量数据,由于ListView已经有很多人分析过,其中郭霖早期写了一篇文章Android ListView工作原理完全解析,带你从源码的角度彻底理解,已经分析的很到位了,所以就不再说ListView/GridView的绘制流程了,下面会重点分析一些ListView/GridView跟GridView的缓存原理,由于他们都是继承自AbsListView,所以缓存逻辑很自然地是在AbsListView中实现的,下面就来对一下AbsListView的缓存原理:
- AbsListView的缓存机制:如何复用Item
- RecyclerView的缓存机制:如何复用Item
- 定向刷新:RecyclerView如何定向刷新Item
- 局部刷新:RecyclerView如何实现局部刷新Item
- DiffUtil:如何找出新旧集合的差异
AbsListView
注释
AbsListView是一个抽象类,官方注释是这么写的
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.
用于展示大量数据的item的基类。在这里并没有指定List的展现方式。举例来说,这个类的子类可以在网格,列表中展示大量的数据
通过注释可以很明显的知道AbsListView作为GridView以及ListView的基类,没有固定展示数据的形式,这个是交由他的子类来实现的,只是ListView是列表,GridView是网格,下面开始从源码的角度来分析一下AbsListView的缓存机制。
继承关系
GridView跟ListView并列,继承自AbsListView,然而AbsListView又是跟AdapterViewAnimator,AbsSpinner是同类的,从这里可以看出,缓存的逻辑应该为GridView跟ListView共有,所以应该是在AbsListView中进行整合的,所以我们重点关注AbsListView这个类就可以了。AbsListView继承自ViewGroup,那么很自然地就会进行onMeasure,onLayout方法,下面就跟着这两个方法来进行 分析。
onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSelector == null) {
useDefaultSelector();
}
//计算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,
//当Adapter中的数据集改变之后,其子类会自动滚动到底部
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();
}
//重新测量Child,mRecycler其实就是RecycleBin,
//这个是一个用于管理回收的View的回收类,一会儿单独分析
mRecycler.markChildrenDirty();
}
//给Child布局,一会儿会单独分析
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源码之前,我们可以感性地分析一下,一般的缓存分为初始化,存,取以及清空缓存,实际上RecycleBin实际上也大体分这几个步骤。
成员变量
private RecyclerListener mRecyclerListener;
//第一个可见的View存储的位置
private int mFirstActivePosition;
//可见的View数组
private View[] mActiveViews = new View[0];
//不可见的的View数组,是一个集合数组,每一种type的item都有一个集合来缓存
private ArrayList<View>[] mScrapViews;
//View的Type的数量
private int mViewTypeCount;
//viewType为1的集合或者说mScrapViews的第一个元素
private ArrayList<View> mCurrentScrap;
还有三个成员变量,没有给出,因为涉及到StableId以及Transient State这两种属性,下面解释一下
- StableId:就是所有的Item具有相同的ID,也就是所有的Item都相同,通过复写BaseAdapter中的hasStableIds可以进行设置,默认为false
- Transient State:在这里面有一个Transient State,是View的一个属性,说的是View伴随有动画之类的效果,对于这种状态的View只有跟Adapter绑定的数据源没有发生变化或者View有相同的ID的时候才能进行缓存复用,因为这两种情况下Item要么数据不变,不用重新绑定数据,要么View不变,不需要重新创建
//数据集不变的具有TransientState的View数组
private SparseArray<View> mTransientStateViews;
//具有固定ID的具有TransientState的View数组
private LongSparseArray<View> mTransientStateViewsById;
//具有TransientState状态但是不满足以上任意一种状态的View数组,不予缓存
private ArrayList<View> mSkippedScrap;
初始化
setViewTypeCount
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//根据viewTypeCount初始化数组
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
//初始化RecycleBin的数组
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
存
fillActiveViews
存放屏幕上活跃的View数组
void fillActiveViews(int childCount, int firstActivePosition) {
//检查ActiveView是否需要扩容
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) {
//对用非Header以及Footer的child给activeViews数组
activeViews[i] = child;
// 设置position的偏移量,方便接下来的布局
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
addScrapView
对不在屏幕中的View进行缓存
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;
}
//设置View的位置偏移量
lp.scrappedFromPosition = position;
final int viewType = lp.viewType;
//只要Type大于0就会进行缓存
if (!shouldRecycleViewType(viewType)) {
//尝试对非Header以及Footer的View进行缓存
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// 不直接缓存具有transient state的View,用transient数组进行缓存
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
if (mAdapter != null && mAdapterHasStableIds) {
//具有stableId
if (mTransientStateViewsById == null) {
mTransientStateViewsById = new LongSparseArray<>();
}
//存放进mTransientStateViewsById数组
mTransientStateViewsById.put(lp.itemId, scrap);
} else if (!mDataChanged) {
//数据集合没有改变,暂时没想到使用场景
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
//存放进mTransientStateViews数组
mTransientStateViews.put(position, scrap);
} else {
// 除此之外,不予缓存,放入mSkippedScrap过滤数组
getSkippedScrap().add(scrap);
}
} else {
//非transient state数组,
if (mViewTypeCount == 1) {
//只有一种type,直接添加进mCurrentScrap
mCurrentScrap.add(scrap);
} else {
//多type,则存放进对应type的数组
mScrapViews[viewType].add(scrap);
}
//回调函数,通知View已经移动到Scrap进行缓存
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
reclaimViews
开辟新集合,存储所有的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) {
//拿到当前位置View的type
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
//type的种类为1,直接从第一个数组中取
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
//直接从对应的type数组中取
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
getActiveView
根据位置获取屏幕中显示的某一个数组
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
//获取之后将数组置空,便于虚拟机回收
activeViews[index] = null;
return match;
}
return null;
}
getTransientStateView
根据position获取TransientStateView,获取后会移除相应的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
获取过滤掉也是不缓存的数组
private ArrayList<View> getSkippedScrap() {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<>();
}
return mSkippedScrap;
}
retrieveFromScrap
从一个type对应的缓存集合中寻找指定位置的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
删除过滤掉的数组
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
清空所有缓存数组
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
当缓存的数组大于Activite数组之后就需要进行清理了,主要是因为transient state View数组中View的transient属性已经消失了,所以相当于会被缓存两边,一遍是具有transient state的View,一边是这些View不再具有transient state的时候又会进行缓存一次,这样就会导致缓存中的数组大于Active数组,所以进行处理。
private void pruneScrapViews() {
final int maxViews = mActiveViews.length;
final int viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
//Scrap数组遍历
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
此方法会重新调用缓存的child的forcelayout,forcelayout跟requestLayout的区别在于前者只会重新执行自己的onMeasure跟onLayout,后者不仅仅会执行前者,还会调用parentView的requestLayout,此方法会在ListView的size改变的时候进行调用。
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();
}
}
}
上面详细分了RecyclerBin的成员变量以及生命周期,内部通过定义了多个数组来对不同类型的View进行缓存,那么AbsListView的子类也就是ListView以及GridView滚动,以及调用notifyDataSetChanged的时候,然后进行重新绘制,先从缓存中取如果没有取到则会重新创建一个View,关于这方面的分析网上已经有很多文章了,只是很多文章没有对RecycleBin的TranslateStateView数组进行分析,自己之前也是一直没有完全理解,所以重点分析了RecycleBin这个类,至于ListView以及GridView的绘制以及刷新机制,其实相对于RecyclerView来讲其实是比较简单的,所以大部分精力也将用来分析RecyclerView的缓存机制
RecyclerView
注释
A flexible view for providing a limited window into a large data set
能够在有限的窗口内展示大量数据集合的一个灵活的View。
相对于AbsListView的两个子类ListView以及GridView来讲,RecyclerView最大一个特性就是灵活,主要体现在以下两个方面
- 多样式:可以对数据的展示进行自有定制,可以是列表,网格甚至是瀑布流,除此之外你还可以自定义样式
- 定向刷新:可以对指定的Item数据进行刷新
- 刷新动画:RecyclerView支持对Item的刷新添加动画
- 添加装饰:相对于ListView以及GridView的单一的分割线,RecyclerView可以自定义添加分割样式
今天我们的重点在于缓存,所以重点研究定向刷新,除了对整体数据进行刷新之外,RecyclerView还提供了很多定向刷新的方法。
由于RecyclerView的很多核心功能都是通过内部类来实现的,而且作者又是把内部类写在一个类里面,所以还是要先对每个类的功能进行简单介绍一下,整个RecyclerView的源代码有1万多行,不可能也没必要面面俱到,还是有针对性地进行分析,不然看源码绝对会走火入魔。
他们的作用如下表
内部类 | |
---|---|
RecyclerView.LayoutManager | 负责Item视图的布局的显示管理 |
RecyclerView.ItemDecoration | 给每一项Item视图添加修饰的View, |
RecyclerView.Adapter | 为每一项Item创建视图 |
RecyclerView.ViewHolder | 承载Item视图的子布局 |
RecyclerView.ItemAnimator | 负责处理数据添加或者删除时候的动画效果 |
RecyclerView.Cache | Recycler/RecycledViewPool/ViewCacheExtension |
最后一个Cache是我加上去的,实际上并没有这个类,主要是为了说明RecyclerView对Item强大的缓存,也是接下来重点分析的对象
Observer&Observable
Observer
Recycler没有采用系统的Observer,因为自己需要接收通知的方法过多,所以自己设计了一个观察者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的方法里面,提到了一个类mAdapterHelper,顾名思义是Adapter的帮助类,他是AdapterHelper的实例,用来帮助Adapter进行数据更新,除此之外还有几个类也是相对比较重要的,下面会简单介绍一下,因为RecyclerView的源码有1万多行,作者几乎是把所有与RecyclerView相关的类搞成了内部类,可能作者对内部类有一种特殊的感情,问题是你可以这样子搞,但是代码能不能多加点注释,很多方法完全没注释,看起来真心一脸懵逼的类。
Observable
RecyclerView实际采用了系统的Observable,看一下继承关系
Observable
代码比较简单,不忍注释
public abstract class Observable<T> {
//注册的观察者
protected final ArrayList<T> mObservers = new ArrayList<T>();
//注册单个观察者
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);
}
}
//解绑单个观察者
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
比较简单,懒得注释,写出来是便于梳理整个流程
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的观察者,感觉实际上根本不需要继承系统的被观察者以及自定义观察者,因为只有一个观察者,所以干脆使用接口回调,口味更佳,下面继续分析RecyclerView缓存的核心类。
Recycler
在Recycler中实际上缓存VieHolder的有2类集合,一类是可见的ViewHolder数组,一类是不可见的ViewHolder数组,其中可见的数组中又分为数据改变跟没有改变的,理解了这些,其实去看
注释
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便于复用,废弃的View是指仍然依附在RecyclerView中,但是已经被标记为移除的或者可以复用的。
跟ListView的RecycleBin一样,Recycler也是RecyclerView设计的一个专门用于回收ViewHolder的类,其实RecyclerView的缓存机制是在ListView的缓存机制的基础上进一步的完善,所以在Recycler中能看到很多跟RecycleBin一样的设计思想,在缓存这个层面上,RecyclerView实际上并没有做出太大的创新,最大的创新来源于给每一个ViewHolder增加了一个UpdateOp,通过这个标志可以进行定向刷新指定的Item,并且通过Payload参数可以对Item进行局部刷新,我觉得这个是RecyclerView最厉害的地方,大大提高了刷新时候的性能,如果数据源需要经常变动,那么RecyclerView是你最好的选择,没有之一,下面看一下Recycler是如何进行缓存的。
成员变量
static final int DEFAULT_CACHE_SIZE = 2;//默认缓存的数量
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;//设置的缓存最大数量,默认为2
int mViewCacheMax = DEFAULT_CACHE_SIZE;//View的缓存的最大数量,默认为2
RecycledViewPool mRecyclerPool;//RecycledView,用来公用RecyclerView
//缓存的扩展,可以用来对指定的position跟Type进行缓存
private ViewCacheExtension mViewCacheExtension;
ArrayList<ViewHolder> mChangedScrap = null;//数据源更改过的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的不可变集合
初始化
我们发现在成员变量中没有初始化的只有两个变量mChangedScrap,mRecyclerPool,mChangedScrap是可见的数据源改变的ViewHolder集合,mRecyclerPool是RecycledViewPool的集合,用来缓存RecyclerView的集合,可以实现RecyclerView的复用。
mChangedScrap是在scrapView这个方法进行初始化的
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,对于某些特定的View会放进RecyclerViewPool,如果这个ViewHolder是从缓存中取的,那么就会清空缓存中View
public void recycleView(View view) {
//这边传递过来的是一个View,然后通过View获取ViewHolder
ViewHolder holder = getChildViewHolderInt(view);
//如果Holder被已经被打上了移除的标记,那么就从移除此View
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
//如果此Holder是来自缓存的可见的ViewHolder数组,清楚缓存
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
//如果此Holder是来自缓存的不可见的ViewHolder数组,清除缓存
holder.clearReturnedFromScrapFlag();
}
//开始缓存,继续追源码
recycleViewHolderInternal(holder);
}
recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
//此处省略若干行条件判断代码
if (forceRecycle || holder.isRecyclable()) {
//如果缓存数量大于0,并且ViewHolder的Flag标志是有效的
//且非REMOVED跟UPDATE,进行缓存,
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中的第一个View,也就是第一个
recycleCachedViewAt(0);
cachedViewSize--;
}
//获取添加元素的在缓存集合中的下标
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
//缓存的时候不能覆盖最近经常被使用到缓存
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
//添加缓存
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果没有缓存的话就添加进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的缓存做了很多优化,实际上也采用了LFU算法,也就是最少使用策略,当我们存储ViewHolder的时候,会去判断这个ViewHolder是否是来自缓存,如果是的话,那么在此缓存的时候不能覆盖最近使用比较频繁的缓存,而是自己定义了一个类GapWorker,他有一个内部类LayoutPrefetchRegistryImpl,然后定义了一个数组
int[] mPrefetchArray;
这个数组是用来记录最近使用过的缓存Holder,所以我们在存的时候会跟这里面存储过的ViewHolder进行匹配,上面代码中已经进行了注释,比较好理解。
recycleAndClearCachedViews
将CacheViews中的ViewHolder添加进RecyclerViewHolder,然后清空CacheViews
void recycleAndClearCachedViews() {
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
//将CacheView中的添加进RecyclerViewPool
recycleCachedViewAt(i);
}
//清空集合
mCachedViews.clear();
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
}
recycleCachedViewAt
void recycleCachedViewAt(int cachedViewIndex) {
//省略一下判断代码
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
//添加进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);
}
取
取缓存有很多入口,但是最终都是调用了tryGetViewHolderForPositionByDeadline,下面重点分析一下此方法
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数组中取
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)通过StableId进行获取,针对复写了BaseAdapter的StableId
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
// 3)通过mViewCacheExtension进行获取
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
//省略Holder的判断逻辑
}
// 4)无路可退,通过RecyclerViewPool进行获取
if (holder == null) {
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
// 5)如果上面都没有获取到,那么就说明是第一屏,所以就得重新创建
if (holder == null) {
long start = getNanoTime();
//省略判空代码
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// 放入最近最少使用的队列中
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
}
}
//在返回给LayoutManager重新绘制前,需要更新一下ViewHolder的相关信息
if (fromScrapOrHiddenOrCache && !mState.isPreLayout()
&& holder.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
//记录动画信息
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());
//如果当前的ViewHolder已经绑定过数据,那么记录一下动画信息
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
//绑定过数据的ViewHolder
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
//未绑定数据的ViewHolder需要进行数据绑定
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 将ViewHolder设置给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进行缓存
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
//数据源未改变,放入mAttachedScrap
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
//省略部分逻辑判断代码
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
//数据源未改变,放入mAttachedScrap
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
清空
clear
将CachedView添加进ViewHolder,并且清空
public void clear() {
mAttachedScrap.clear();
recycleAndClearCachedViews();
}
clearScrap
清空缓存中可见的Scrap数组
void clearScrap() {
mAttachedScrap.clear();
if (mChangedScrap != null) {
mChangedScrap.clear();
}
}
定向刷新
可以对指定的Item进行刷新,是RecyclerView的又一特点,RecyclerView在观察者模式中的Observer中新增了很多方法,用于定向刷新,这个在前面的观察者模式中已经提到过,下面从源码的角度分析一下原理,我们拿adapter.notifyItemChanged(0),最终会RecyclerViewDataObserver的方法
onItemRangeChanged
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
//继续追踪mAdapterHelper
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
首先调用了AdapterHelper的onItemRangeChanged方法,这里简单说一下AdapterHelper,用来帮助Adapter更新数组的,类似于ChildHelper帮助LayoutManager进行布局
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
}
//将需要更新的Item的范围记录下来,并添加更新标识
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
}
然后接着调用了triggerUpdateProcessor方法
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
//如果有动画,先执行动画,然后再进行测量
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
//重新测量
requestLayout();
}
}
其实看到这里,发现AdapterHelper只是记录了需要刷新的Item的范围,然后就开始进行重新测量了,那么很明显,不管是全部刷新还是指定范围的定向刷新,RecyclerView都是需要重新测量的,所以,定向刷新的真正语义是对指定范围的ViewHolder进行数据刷新,不会像ListView一样,刷新所有的,所以我们只能从布局的时候来查找,RecyclerView的布局是交给LayoutMananger来进行布局的,那么根据AbsListView一样,LayoutMananger一定会在自身当中定义一些公共方法给子类实现,这个方法实际上就是onLayoutChildren,还有onLayoutCompleted,是在绘制完成的时候进行调用,进行资源清理
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
//做一些清理工作
public void onLayoutCompleted(State state) {
}
我们继续查看子类中的实现
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);
}
继续追踪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");
}
//循环中调用了此方法
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;
}
继续追踪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的方法,所以最开始的思路错了,应该是在layoutChildren调用之前就已经确定是否绑定,然后我们发现layoutChildren是在dispatchLayoutStep1/2/3中都有调用,这个就尴尬了,不过我们继续查看此方法,发现不仅仅这三个方法在会在dispatchLayout中被调用到,
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这个方法在onLayout中单独进行调用,而且只调用了这一个方法,那么没有疑问,应该是在布局的时候来判断,所以先查看一下dispatchLayoutStep这三兄弟
根据注释,我们是在第一个方法里面进行ViewHolder的更新,所以我们重点查看一下dispatchLayoutStep1这个方法
dispatchLayoutStep1
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
saveFocusInfo();
//命名太规范了,一下子就找到了,继续追踪
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();
//最开始以为是在这个方法中进行回调的,后来发现在子类是空实现,
//也就是说这个方法供使用着自己定义,通知回调
mLayout.onItemsChanged(this);
}
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
//更新ViewHolder
mAdapterHelper.consumeUpdatesInOnePass();
}
//省略动画相关操作
}
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:
//接口回调
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();
}
}
//这里也是回收,实际上这个回收不是循环利用,就是清空的意思
recycleUpdateOpsAndClearList(mPendingUpdates);
mExistingUpdateTypes = 0;
}
这个接口是初始化的时候传入的,我们追踪此接口
@Override
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
//这个方法才是真正的更新Adapter的ViewHolder,继续跟进
viewRangeUpdate(positionStart, itemCount, payload);
mItemsChanged = true;
}
@Override
public void onDispatchFirstPass(UpdateOp op) {
//他的方法都是空实现,子类也没有实现,我们可以复写来接收通知
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
通过名字也能很好的查看出,进行范围内的更新
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,这个是从缓存中获取到ViewHolder的时候,需不需要刷新的唯一标识
holder.addFlags(ViewHolder.FLAG_UPDATE);
//添加payLoad参数,用于ViewHolder的局部刷新
holder.addChangePayload(payload);
// lp cannot be null since we get ViewHolder from it.
((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
}
}
//更新缓存中的标记位,便于数据源不改变的时候直接复用
mRecycler.viewRangeUpdate(positionStart, itemCount);
}
局部刷新
在上面提到过,我们刷新的时候会调用一个方法,叫做notifyItemChanged,通常会传一个起始位置以及范围,其实我们还可以传入一个Object类型的参数,
public final void notifyItemChanged(int position, Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
如何获取呢,也很简单,复写onBindViewHolder这个方法,
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
//点进去会进入下面的方法
super.onBindViewHolder(holder, position, payloads);
}
默认的实现是onBindViewHolder,也就是我们经常复写的方法
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
这个参数有什么用,就是用于局部刷新的,比如一个ViewHolder展示了很多关于学生的数据,姓名,年龄,爱好,学习成绩,家庭住址等,但是我们只需要刷新一下学习成绩,我们在调用notifyItemChanged的时候只需要调用一下otifyItemChanged(0, score),然后在Adapter中进行获取就好了刷新这一项就好了。
DiffUtil
当我们知道我们要刷新的Item的范围的时候,可以直接调用notifyItemChanged(position,range),当我们更新前后的集合数据差不多,只有部分差异的时候,我们都还是调用的是notifyDataSetChanged,即全量刷新,其实谷歌已经意识到这个问题,已经在support包中提供了一个工具类DiffUtil,可以帮助我们进行分析对比前后的数据差异,从而进行定向刷新,我们来分析一下这个工具类:
注释
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是一个可以计算出两个集合之间的差别并且输出从一个集合到另外一个集合之间的变化的工具类
从注释可以看出,DifUtil的主要操作主要是用来比对两个新旧集合之间产生的差异,同时DiffUtil底层采用的是Myers差分算法,大家可以先看看这篇文章了解一下Myers差分算法,不然下面的肯定很不好理解,说通俗一点就现在有新旧两个集合,长度分别为M、N,是通过构造一个平面直角坐标系,然后X轴向右,用原数据集合中的元素填充横坐标,Y轴向下,新数据集合中的元素填充纵坐标,
然后需要计算从坐标(0,0)到(M,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;
}
};
定义了一个比较器,传入了snake(下面分析)泛型,其实Diffutil只有这一个成员变量,然后就是内部类,下面先分析一下内部类:
Callback
public abstract static class Callback {
public abstract int getOldListSize();
public abstract int getNewListSize();
//Item是否一致
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
//Item的内容是否一致
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
//看到Payload应该很容易之前研究RecyclerView的局部刷新中的payLoad,其实是一个意思
//这个会传到onBindHolder中去,作为局部刷新的标志
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return null;
}
}
Snake
这个直接翻译过来是一条蛇,通过注释来看,DiffUtil是采用了Myers差分算法,所以Snake实际上代表的是蛇形路径,就是将新旧集合的差异进一步描述出来
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,表示在头部进行添加或删除
boolean reverse;
}
如果你稍微了解一下Myers差分算法,就比较好理解,因为新集合是在旧集合的基础上进行变化得到的,通过增加,删除乃至移动变换,所以需要知道是在当前元素的前面或者后面进行操作,并且需要知道是什么操作,增加或者删除等
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的全局坐标
DiffResult
这个是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差分算法计算差分结果
final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd,
range.newListStart, range.newListEnd, forward, backward, max);
//省略了一些算法,感兴趣的可以自己去看源码
}
// 对差分结果进行排序
Collections.sort(snakes, SNAKE_COMPARATOR);
return new DiffResult(cb, snakes, forward, backward, detectMoves);
}
dispatchUpdatesTo
调用此方法,可以进行刷新操作
public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
//继续跟踪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);
}
});
}
看完你会发现,就是一个回调接口,来回调adapter而已
dispatchUpdatesTo
根据计算得到的路径,来对旧集合定向刷新
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;
//遍历通过Myers差分算法生成最短路径,由于是集合所以要从头开始计算的话需要倒序遍历
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) {
//右移,说明是删除操作,进行相应处理
dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX);
}
if (endY < posNew) {
//下移,说明是新增操作,进行相应处理
dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY,
endY);
}
//如果复写了DiffUtil.Callback的getChangePayload方法,将会支持RecyclerView的局部刷新
for (int i = snakeSize - 1; i >= 0; i--) {
if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) {
//分发DiffUtil.Callback的getChangePayload方法,最终会抵达adater
batchingCallback.onChanged(snake.x + i, 1,
mCallback.getChangePayload(snake.x + i, snake.y + i));
}
}
posOld = snake.x;
posNew = snake.y;
}
//回调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实际上就是DiffUtil.Callback的实例,然后我们调用的时候传入自定义的Callback接口,就好
用法简介
上面说了很多原理,下面简单介绍一下用法
- 自定义DiffCallback继承自DiffUtil.Callback,复写相关方法
- 调用DiffUtil.calculateDiff(new DiffCallBack(oldData, newData), true),生成diffResult;
- 调用diffResult.dispatchUpdatesTo(mAdapter);
如果还有不熟悉的话可以直接在网上搜相关博客,用法还是比较简单的,不过有一点需要注意的是就是,在DiffUtil的注释当中,说明Myers差分算法本身是不支持item移动的,然后谷歌自己实现了一套算法,支持item的移动,但是当数据量比较大的时候比较耗时,需要在子线程中进行计算,我个人觉得只要当前后两个数据集合相似度较高的时候DiffUtil的效果会比较明显,这种情况下的定向刷新比较有意义。
总结
上面分析了一下ListView跟RecyclerView的缓存原理,下面简单对比分析一下
AbsListView | RecyclerView | |
---|---|---|
缓存 | View | ViewHolder |
定向刷新 | 不支持 | 支持 |
局部刷新 | 不支持 | 支持 |
刷新动画 | 不支持 | 支持 |
Item点击 | 支持 | 不支持 |
分隔线 | 样式单一 | 自定义样式 |
布局方式 | 列表/网格 | 自定义样式 |
头尾添加 | 支持 | 不支持 |
通过分析可能大家已经知道了RecyclerView相对于ListView的区别,如果我们需要频繁的刷新列表数据以及添加动画的话,我们还是采用RecyclerView,对于前后数据量变化不大的新旧集合,还可以通过DiffUtil来进行差分,这样就能够实现定向刷新以及局部刷新,否则建议使用ListView,因为ListView内置了很多Adapter,类似ArrayAdapter,SimpleAdapter,CursorAdapter。
还有一种情况就是宫格列切换的功能,如下图
那个时候RecyclerView刚出来,最开始是用ListView跟GridView实现的,就是搞了两个,一个显示,另外一个隐藏,由于当时是采用的SwipeRefreshLayout实现的,所以自定义的上拉加载,在ListView跟GridView之间切换的时候特别痛苦,后来采用了RecyclerView,真的是不要太简单,动态设置一下LayoutManager就行,底部的加载进度条也可以通过设置GridLayoutManager.SpanSizeLookup可以很好的进行处理。
作者:wustor
链接:https://juejin.im/post/6844903559280984072
更多系列教程GitHub白嫖入口:https://github.com/Timdk857/Android-Architecture-knowledge-2-
B站全套Android移动架构师进阶视频教程白嫖地址:https://space.bilibili.com/544650554
网友评论