RecyclerView缓存介绍

转载请以连接形式标明出处: 本文出自:103style的博客android

源码 base on androidx.recyclerview:recyclerview:1.1.0-alpha05git

代码说明中已省略了暂不须要关注的代码.github

测试demo地址缓存

更新于:2019/10/29 15.04 以前因为用Android P模拟器测试因此一直显示 mAttachedScrap 中的数据一直为空。真机显示正常,为屏幕内当前显示item的条数。bash


目录

  • 获取 RecyclerView 子项的流程
  • 缓存相关的数据变量的介绍
  • 获取缓存的逻辑介绍
  • 经过demo测试缓存
  • 小结

获取 RecyclerView 子项的流程

获取的流程大体为: RecyclerView.onLayout(...)LayoutManager.onLayoutChildren(...)LayoutState.next(...)ide

源代码调用以下:布局

RecyclerViewonLayout(...)方法:测试

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    dispatchLayout();
}
void dispatchLayout() {
        dispatchLayoutStep2();
}
private void dispatchLayoutStep2() {
    mLayout.onLayoutChildren(mRecycler, mState);
}
复制代码

RecyclerView 设置的 LayoutManageronLayoutChildren(...)方法: 以 LinearLayoutManager 为例:动画

public void onLayoutChildren(...) {
        fill(recycler, mLayoutState, state, false);
}
int fill(...) {
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
void layoutChunk(...) {
    View view = layoutState.next(recycler);
}
List<RecyclerView.ViewHolder> mScrapList;
static class LayoutState {
    View next(RecyclerView.Recycler recycler) {
        if (mScrapList != null) {
            return nextViewFromScrapList();
        }
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }
}
复制代码

这里主要看 recycler.getViewForPosition(mCurrentPosition)mScrapList 是有动画效果时预布局保存的ViewHolderui


缓存相关的数据变量的介绍

Recycler中的 mAttachedScrap、mChangedScrap、mCachedViews

ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
static final int DEFAULT_CACHE_SIZE = 2;
复制代码

mAttachedScrap:用于缓存显示在屏幕上并符合对应规则的 ViewHolder,模拟器的话会出现 里面的数据时空的 。 mChangedScrap:用于缓存显示在屏幕上不符合 mAttachedScrap 规则的 ViewHolder

Recycler.scrapView(View view), 在 调用 attchViewdetachView的时候调用。

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        mChangedScrap.add(holder);
    }
}
复制代码

mCachedViews:保存滑动过程当中从可见到不可见的ViewHolder,大小为DEFAULT_CACHE_SIZE = 2,而且本来数据信息都在,因此能够直接添加到 RecyclerView 中显示,不须要再次从新 onBindViewHolder(),在数据多时滑动过程当中会变成 3

private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
        new ViewInfoStore.ProcessCallback() {
            @Override
            public void unused(ViewHolder viewHolder) {
                mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
            }
        };
void process(ProcessCallback callback) {
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
            //从可见变成不可见
            callback.unused(viewHolder);
        } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
            if (record.preInfo == null) {
                // 相似出现消失但发生在不一样的布局通道之间。 
                // 当布局管理器使用自动测量时,可能会发生这种状况
                callback.unused(viewHolder);
            }
        }
    }
}
复制代码

ChildHelper.mHiddenViews

mHiddenViews 中保留在添加或删除数据或其余操做,保存在屏幕内可见的 带动画子视图

List<View> mHiddenViews = new ArrayList<View>();
复制代码

mHiddenViews 是在 RecycleViewaddAnimatingView(ViewHolder viewHolder)的时候,根据特定状况添加的。

private void addAnimatingView(ViewHolder viewHolder) {
    final boolean alreadyParented = view.getParent() == this;
    if (viewHolder.isTmpDetached()) {
        mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
    } else if (!alreadyParented) {
        mChildHelper.addView(view, true);
    } else {
        mChildHelper.hide(view);
    }
}
复制代码

RecycledViewPool.mScrap

每一个 viewType 的缓存最可能是 DEFAULT_MAX_SCRAP = 5 个,viewTypeRecyclerView子view 的样式,即 adapter 中的 getItemViewType获取的值。

private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
复制代码

添加和获取的方法:

  • 咱们能够看到每获取一个值,则会从缓存中删掉。取数据即从缓存中取数据时调用。
  • 添加的时候,当数量超过DEFAULT_MAX_SCRAP = 5时,则丢弃。 存数据即为 释放 mCachedViews 的数据则会放入缓存池。
public ViewHolder getRecycledView(int viewType) {
    final ScrapData scrapData = mScrap.get(viewType);
    if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        for (int i = scrapHeap.size() - 1; i >= 0; i--) {
            if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                return scrapHeap.remove(i);
            }
        }
    }
    return null;
}
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        return;
    }
    scrapHeap.add(scrap);
}
复制代码

获取缓存的逻辑介绍

RecyclerView.RecyclergetViewForPosition(int position)方法:

public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
复制代码

RecyclerView.RecyclertryGetViewHolderForPositionByDeadline(...) 方法获取 holder也就是对应的缓存机制的主要逻辑,找到即返回

  • 首先判断mState.isPreLayout(),是否是执行动画的预布局,是的话则尝试从 mChangedScrap 中获取。
  • 而后尝试从mAttachedScrapmHiddenViewsmCachedViews获取。
  • 而后根据 mAdapter.hasStableIds() 以及 mViewCacheExtension!=null 判断是否从用户设置的相关逻辑中获取。
  • 而后再尝试从缓存池 RecycledViewPool 中的 mScrapScrapData 中获取。
  • 还没找到则经过 mAdapter.createViewHolder 从新建立。
ViewHolder tryGetViewHolderForPositionByDeadline(...) {
    ...
    if (mState.isPreLayout()) {
        //若是是预布局,即执行简单的动画 就会尝试这里
        holder = getChangedScrapViewForPosition(position);
    }
    // 从 scrap/hidden list/cache 尝试获取
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        ...
        final int type = mAdapter.getItemViewType(offsetPosition);
        //经过 adapter.setHasStableIds设置 默认为false
        if (mAdapter.hasStableIds()) {}
        //mViewCacheExtension 经过 RecyclerView.setViewCacheExtension设置 默认为null
        if (holder == null && mViewCacheExtension != null) {
            //经过自定义的缓存机制区获取
        }
        if (holder == null) {
            // 尝试从RecycledViewPool中去获取holder
            holder = getRecycledViewPool().getRecycledView(type);
        }
        if (holder == null) {
            //调用Adapter.createViewHolder建立holder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }
    ...
    return holder;
}
复制代码

若是是预布局,则尝试从 mChangedScrap 中获取

ViewHolder getChangedScrapViewForPosition(int position) {
    // 若是是预布局,请检查 mChangedScrap 是否彻底匹配。
    for (int i = 0; i < mChangedScrap.size(); i++) {
        final ViewHolder holder = mChangedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    // 默认 false
    if (mAdapter.hasStableIds()) {
        ...
    }
    return null;
}
复制代码

尝试从缓存中 mAttachedScrap、mHiddenViews、mCachedViews获取RecyclerView.RecyclergetScrapOrHiddenOrCachedHolderForPosition(...) 获取 holder.

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

    // 尝试从mAttachedScrap获取
    for (int i = 0; i < scrapCount; i++) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    //dryRun: getViewForPosition(position, false)传入的是false
    if (!dryRun) {
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
        ...
            return vh;
        }
    }
    // 从一级缓存中尝试获取
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // 若是适配器具备稳定的ID,则无效的视图持有者可能在缓存中,
        // 由于能够经过getScrapOrCachedViewForId检索它们
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            return holder;
        }
    }
    return null;
}
复制代码

从缓存池RecycledViewPool中的 mScrap 的 ScrapData 中获取ViewHolder,并从缓存池中移除RecycledViewPoolgetRecycledView(int viewType):

SparseArray<ScrapData> mScrap = new SparseArray<>();

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

经过demo测试缓存

测试demo地址

demo主要是添加了 两种类型 的数据,默认添加 50 条数据,设置一屏幕最多显示 8 条数据, 而后经过 滑动添加删除 数据,再经过反射来获取对应缓存的数据。


  • 启动程序时
    启动
    咱们能够看到 调用了 8 次 onCreateViewHolder;mAttachedScrap 中的数据也是有8条。
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 1
TestAdapter: onBindViewHolder position = 0
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 2
TestAdapter: onBindViewHolder position = 1
...
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 8
TestAdapter: onBindViewHolder position = 7
MainActivity1572332870607: ChildHelper.mHiddenViews =0
MainActivity1572332870607: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332870607: Recycler.mViewCacheMax =2
MainActivity1572332870607: Recycler.mAttachedScrap.size() = 0
MainActivity1572332870607: Recycler.mChangedScrap.size() = 0
MainActivity1572332870607: Recycler.mCachedViews.size() = 0
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332870607: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
MainActivity1572332870611: ChildHelper.mHiddenViews =0
MainActivity1572332870611: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332870611: Recycler.mViewCacheMax =2
MainActivity1572332870611: Recycler.mAttachedScrap.size() = 8
MainActivity1572332870611: Recycler.mChangedScrap.size() = 0
MainActivity1572332870611: Recycler.mCachedViews.size() = 0
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332870611: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
复制代码

  • 把第一条数据彻底移出屏幕时:
    把第一条数据彻底移出屏幕
    咱们看到调用了两次 onCreateViewHolder, 而后 Recycler 中的 mViewCacheMax 变成了 3, Recycler.mCachedViews 缓存了 position09 对应位置的数据。即屏幕上下都有不可见的数据是,缓存的是上下各一条不可见视图的数据。
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 9
TestAdapter: onBindViewHolder position = 8
TestAdapter: onCreateViewHolder onCreateViewHolderCount = 10
TestAdapter: onBindViewHolder position = 9
MainActivity1572332997091: ChildHelper.mHiddenViews =0
MainActivity1572332997091: LayoutManager.LayoutState.mScrapList =0
MainActivity1572332997091: Recycler.mViewCacheMax =3
MainActivity1572332997091: Recycler.mAttachedScrap.size() = 8
MainActivity1572332997091: Recycler.mChangedScrap.size() = 0
MainActivity1572332997091: Recycler.mCachedViews.size() = 2
MainActivity: mCachedViews:---0 = ViewHolder{1bcb007 position=0 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{c735183 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572332997091: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
复制代码

  • 把第一条数据彻底拉进屏幕时:
    把第一条数据彻底拉进屏幕时

咱们看到没有调用 onCreateViewHolder, Recycler 中的 mViewCacheMax 仍是 3Recycler.mCachedViews 缓存了 position98 对应位置的数据。即滑动到顶端缓存的是 下方不可见的两条数据。

MainActivity1572333360514: ChildHelper.mHiddenViews =0
MainActivity1572333360514: LayoutManager.LayoutState.mScrapList =0
MainActivity1572333360514: Recycler.mViewCacheMax =3
MainActivity1572333360514: Recycler.mAttachedScrap.size() = 8
MainActivity1572333360514: Recycler.mChangedScrap.size() = 0
MainActivity1572333360514: Recycler.mCachedViews.size() = 2
MainActivity: mCachedViews:---0 = ViewHolder{c735183 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{9f16d94 position=8 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572333360514: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
复制代码

  • 把前面四条数据移出屏幕
    把前面四条数据移出屏幕

咱们看到调用了 2 次 onCreateViewHolder, Recycler 中的 mViewCacheMax 仍是 3, Recycler.mCachedViews 缓存了 position2312 对应位置的的 三条 数据。 RecycledViewPool中缓存了一条 viewType = 2 的数据,即上图有两列的 item.

TestAdapter: onBindViewHolder position = 1
MainActivity1572333690740: ChildHelper.mHiddenViews =1
MainActivity: mHiddenViews:---0 = android.widget.LinearLayout{9d33071 V.E...... ......I. 0,2268-1440,2588}
MainActivity1572333690740: LayoutManager.LayoutState.mScrapList =0
MainActivity1572333690740: Recycler.mViewCacheMax =3
MainActivity1572333690740: Recycler.mAttachedScrap.size() = 8
MainActivity1572333690740: Recycler.mChangedScrap.size() = 0
MainActivity1572333690740: Recycler.mCachedViews.size() = 3
MainActivity: mCachedViews:---0 = ViewHolder{15ffd7e position=11 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{c735183 position=10 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---2 = ViewHolder{9f16d94 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1572333690740: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 0
复制代码

  • 滑动到顶端点击添加或删除
    滑动到顶端点击添加或删除

咱们发现 ChildHelper.mHiddenViews中多了条数据。只有看到添加或删除动画时,mHiddenViews中才会有数据 。

TestAdapter: onBindViewHolder position = 1
MainActivity1571565149149: ChildHelper.mHiddenViews =1
MainActivity: mHiddenViews:---0 = android.widget.LinearLayout{c44a637 V.E...... ......I. 0,1589-1080,1813}
MainActivity1571565149149: LayoutManager.LayoutState.mScrapList =0
MainActivity1571565149149: Recycler.mViewCacheMax =3
MainActivity1571565149149: Recycler.mAttachedScrap.size() = 8
MainActivity1571565149149: Recycler.mChangedScrap.size() = 0
MainActivity1571565149149: Recycler.mCachedViews.size() = 3
MainActivity: mCachedViews:---0 = ViewHolder{3d52e3f position=11 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---1 = ViewHolder{e8dba5e position=10 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity: mCachedViews:---2 = ViewHolder{6647799 position=9 id=-1, oldPos=-1, pLpos:-1 no parent}
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 1).mMaxScrapField = 5
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 1).mScrapHeap.size() = 0
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 2).mMaxScrapField = 5
MainActivity1571565149149: RecycledViewPool.mScrap.get(viewType = 2).mScrapHeap.size() = 1
MainActivity: mScrapHeap:---0 = ViewHolder{996130c position=-1 id=-1, oldPos=-1, pLpos:-1 unbound no parent}
复制代码

小结

经过上文咱们能够了解到:

获取 子View 最终是调用了 LayoutManagerLayoutStatenext(RecyclerView.Recycler recycler)。 而后判断是不是显示动画效果,是的话则直接从 mScrapList 获取,不然经过缓存机制去获取。


缓存数据相关的变量分别表明的意思: mAttachedScrap:用于缓存显示在屏幕上并符合对应规则的 ViewHolder,测试发如今Android P 的模拟器上 里面的数据时空的。 mChangedScrap:用于缓存显示在屏幕上不符合 mAttachedScrap 规则的 ViewHolder。 mCachedViews:保存滑动过程当中从可见到不可见的 ViewHolder,大小为DEFAULT_CACHE_SIZE = 2,而且本来数据信息都在,因此能够直接添加到 RecyclerView 中显示,不须要再次从新 onBindViewHolder(),在数据多时,滑动过程当中 mCachedViews的大小会变成 3mHiddenViews 中保留在添加或删除数据或其余操做,保存在屏幕内可见的 带动画子视图


RecyclerView 的缓存机制大体是:

  • 首先判断是否是执行动画的预布局,是的话则尝试从 mChangedScrap 中获取。
  • 而后尝试从mAttachedScrapmHiddenViewsmCachedViews获取。
  • 而后根据 mAdapter.hasStableIds() 以及 mViewCacheExtension!=null 判断是否从用户自定义设置的相关逻辑中获取。
  • 而后再尝试从缓存池 RecycledViewPoolmScrapScrapData 中获取,每种样式最多缓存 5个
  • 还没找到则经过 mAdapter.createViewHolder 建立新的。

若是有写错的地方,请帮忙评论指正,感谢!

若是以为不错的话,请帮忙点个赞呗。

以上


扫描下面的二维码,关注个人公众号 Android1024, 点关注,不迷路。

Android1024
相关文章
相关标签/搜索