Recycler缓存机制
文章目录
- Recycler
- 四级缓存
- 屏幕内缓存 mAttachedScrap 和 mChangedScrap
- 进入缓存
- 屏幕外缓存 mCachedViews
- 滚动
- 缓存池 RecycledViewPool
- 优化
- notifyDataSetChanged
- setHasFixedSize
Recycler承担了RecyclerView中的缓存功能,其中定义的5个集合代表了四个缓存层级。
Recycler
//一级缓存
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
//二级缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
///三级缓存
private ViewCacheExtension mViewCacheExtension;
//四级缓存
RecycledViewPool mRecyclerPool;
//默认mCachedViews的最大缓存数量
static final int DEFAULT_CACHE_SIZE = 2;
关于四级缓存的优先级,可以看Recycler中的以下方法:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {//省略其他代码....ViewHolder holder = null;// 1) Find by position from scrap/hidden list/cacheif (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);}if (holder == null) {// 2) Find from scrap/cache via stable ids, if existsif (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because LayoutManager does not// know it.final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);}}if (holder == null) { // fallback to poolholder = getRecycledViewPool().getRecycledView(type);}if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);}}return holder;}
用图片的形式更加直观一些:
缓存有两个边界条件:进和出。
四级缓存
屏幕内缓存 mAttachedScrap 和 mChangedScrap
scrap仅仅在layout期间不为空。当LayoutManager开始 layout 的时候(pre-layout 或 post-layout),会将所有的viewHolder都放到scrap中。然后一个个再取回来,除非有些view发生了变化。
进入缓存
LinearLayoutManager.onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
-> LayoutManager.detachAndScrapAttachedViews(Recycler recycler)
-> LayoutManager.scrapOrRecycleView(Recycler recycler, int index, View view)
-> Recycler.scrapView(View view)
void scrapView(View view) {final ViewHolder holder = getChildViewHolderInt(view);if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {// 标记为移除或失效的 || 没有改变 || item 无动画或动画不复用mAttachedScrap.add(holder);} else {// 放入 mChangedScrapmChangedScrap.add(holder);}
}
屏幕外缓存 mCachedViews
mCachedViews是一个 ArrayList 类型集合,不区分 viewHolder 的类型,大小限制为2,可以使用 setItemViewCacheSize()这个方法调整它的大小。
滚动
当发生滚动时,方法调用流程
RecyclerView
-> onTouchEvent(MotionEvent e)
-> scrollByInternal(int x, int y, MotionEvent ev)
-> scrollStep(int dx, int dy, @Nullable int[] consumed)
LinearLayoutManager
-> scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state)
-> scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state)
-> fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable)
-> recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState)
-> recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace)
-> recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex)
RecyclerView.LayoutManager
-> removeAndRecycleViewAt(int index, @NonNull Recycler recycler)
RecyclerView.Recycler
-> recycleView(@NonNull View view)
-> recycleViewHolderInternal(ViewHolder holder)
-> recycleCachedViewAt(int cachedViewIndex)
-> addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled)
RecyclerView.RecycledViewPool
-> putRecycledView(ViewHolder scrap)
在下面的方法中,判断了Holder是否需要回收
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace) {final int limit = scrollingOffset - noRecycleSpace;final int childCount = getChildCount();//依次判断childView是否已经离开屏幕可见区域,并进行回收for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (mOrientationHelper.getDecoratedEnd(child) > limit|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {// stop hererecycleChildren(recycler, 0, i);return;}}
}
回收时,如果mCachedViews已满,移除mCachedViews中的第一个元素,将其放入RecycledViewPool
void recycleViewHolderInternal(ViewHolder holder) {if (forceRecycle || holder.isRecyclable()) {if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {// mCachedViews的默认缓存数量是2,如果缓存已满,移除掉最老的Holer,为新的Holder让出空间int cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {//移除mCachedViews中的第一个元素,将其放入RecycledViewPoolrecycleCachedViewAt(0);cachedViewSize--;}int targetCacheIndex = cachedViewSize;mCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {// 不能放进 mCacheViews 的放 RecyclerViewPooladdViewHolderToRecycledViewPool(holder, true);recycled = true;}}
}void recycleCachedViewAt(int cachedViewIndex) {ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);addViewHolderToRecycledViewPool(viewHolder, true);mCachedViews.remove(cachedViewIndex);
}public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;//如果RecycledViewPool的该类型集合已满(默认最大值 = 5),不做任何处理if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {return;}scrap.resetInternal();scrapHeap.add(scrap);
}
缓存池 RecycledViewPool
RecycledViewPool,它储存了各个类型的 viewHolder。最大数量为5,可以通过 setMaxRecycledViews() 方法来设置每个类型储存的容量。可以多个列表公用一个 RecycledViewPool,使用 setRecycledViewPool() 方法。
优化
notifyDataSetChanged
当调用这个方法时,所有的ViewHolder被标记为无效,意味着全部将进入RecycledViewPool缓存,其他缓存为空,因为RecycledViewPool的默认缓存最大值只有5,当RecyclerView展示的item数量超过5时,无法取得缓存,会选择重新创建,执行onCreateViewHolder。考虑到此,刷新数据尽量不要调用notifyDataSetChanged,使用其他方法代替,或者,调整RecycledViewPool的最大缓存数量
setHasFixedSize
如果RecyclerView的尺寸不受adapter的影响,RecyclerView可以做一些优化。
RecyclerView仍可以基于其他因素更改其尺寸(例如,父级的大小),但大小的计算不依赖于子项的大小或adapter的内容(adapter中的数量除外)。
设置为true,可避免adapter内容更改时整个布局无效。
简而言之,当设置为true的时候,adapter将不再影响recyclerView的大小,由此可以避免刷新recyclerView的时候,重新布局计算大小。
#RecyclerView
public void setHasFixedSize(boolean hasFixedSize) {mHasFixedSize = hasFixedSize;
}
当调用adapter.notifyDataSetChanged
一类方法刷新界面时,会执行到下面的对应方法:
private class RecyclerViewDataObserver {@Overridepublic void onChanged() {processDataSetCompletelyChanged(true);if (!mAdapterHelper.hasPendingUpdates()) {requestLayout();}}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount, Object payload) {if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeInserted(int positionStart, int itemCount) {if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeRemoved(int positionStart, int itemCount) {if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {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();}}}
onChanged
和其他方法的区别在于,其他方法都调用了triggerUpdateProcessor
,triggerUpdateProcessor
使用mHasFixedSize
做了一些判断,适当的条件下才去调用requestLayout
,onChanged
则直接调用了requestLayout
。因此想要跳过requestLayout
,除了不调用onChanged
外,mHasFixedSize
必须设置为true。
mHasFixedSize
为true的情况下,依然可以通过调用onChanged
,也就是notifyDataSetChanged
,调整recyclerView的大小。
参考:
https://blog.csdn.net/weixin_43130724/article/details/90068112?spm=1001.2014.3001.5502
Recycler缓存机制相关推荐
- RecyclerView缓存机制(回收些啥?)
这是RecyclerView缓存机制系列文章的第二篇,系列文章的目录如下: RecyclerView缓存机制(咋复用?) RecyclerView缓存机制(回收些啥?) RecyclerView缓存机 ...
- recyclerview item点击无效_让你彻底掌握RecyclerView的缓存机制
点击上方蓝字关注 ?? 来源:肖邦kakahttps://www.jianshu.com/p/3e9aa4bdaefd 前言 RecyclerView这个控件几乎所有的Android开发者都使用过(甚 ...
- RecyclerView详解一,使用及缓存机制
本文大致会先讲解RecyclerView的基础知识及使用,最后会深入讲解一点原理.当然,本人知识水平有限哈,太深入的东西我现在还没接触到,还请大家包容,阿里嘎多~ 一.RecyclerView的历史与 ...
- RecyclerView缓存机制(scrap view)
这是RecyclerView缓存机制系列文章的第四篇,系列文章的目录如下: RecyclerView缓存机制(咋复用?) RecyclerView缓存机制(回收些啥?) RecyclerView缓存机 ...
- Android RecyclerView 绘制流程及Recycler缓存
前言 RecyclerView源码一万多行,想全部读懂学会挺麻烦的,感兴趣的可以自己去瞅瞅,这篇文章重点来看下 RecyclerView是如何一步步将每一个 ItemView 显示到屏幕上,然后再分析 ...
- Android RecyclerView 绘制流程及Recycler缓存,Android开发者必看避坑指南
- mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); ...
- Django缓存机制
Django缓存机制三个粒度:1 全站缓存 settings.py 全局配置文件用中间件:MIDDLEWARE = [# 'django.middleware.cache.UpdateCacheMid ...
- MyBatis复习笔记6:MyBatis缓存机制
MyBatis缓存机制 MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制.缓存可以极大的提升查询效率. MyBatis系统中默认定义了两级缓存. 一级缓存和二级缓存. 默认情 ...
- java设置缓存机制
2019独角兽企业重金招聘Python工程师标准>>> java设置缓存机制 所谓缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实 ...
最新文章
- Android 自定义Toast实现多次触发只会显示一次toast
- Atitit main函数的ast分析 数组参数调用的ast astview解析
- 他们爬了7000家创业公司数据,发现这些领域没那么容易应用AI
- Spark-ML-数据获取/处理/准备
- 分库分表技术演进最佳实践-修订篇
- 【Apache 】 遇到的问题
- python lib head,使用Python 2中的urllib2发出HTTP HEAD请求
- android/IOS SDK怎么判断用户是否安装了微信/QQ
- ITK:侵蚀二进制图像
- 为什么手机游戏手柄没有流行起来?
- 面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
- 关于对象的引用作为参数,可以直接访问私有成员的问题
- wpf使用入式mysql_c#之wpf:从mysql数据库中数据绑定到页面上
- Bootstrap采样方法的python实现
- oracle事件跟踪器使用,Oracle 10046跟踪事件操作步骤
- java接口测试工具_接口模拟测试利器,moco server工具的介绍
- 共享hp无线扫描到计算机,共享HP / HP扫描仪的OpenWrt路由器
- 三维模型格式转换神器-assimp
- RAID磁盘阵列与配置(详细)
- Python文本相似度识别(附图形化界面)