目录:
1.RecyclerView缓存原理
2.讲一下RecyclerView的缓存机制,滑动10个,再滑回去,会有几个执行onBindView
- ListView和RecyclerView区别?缓存区别
4.如何让两个 RecyclerView 共用一个缓存,今日头条页面实例
- RecyclerView如何优化
5.1 RecyclerView预加载机制源码分析,RecyclerView数据预取
5.2.如何实现RecyclerView的局部更新,用过payload吗,notifyItemChange方法中的参数?
6.为什么RecyclerView加载首屏会慢一些
- 列表滑动卡顿该如何定位问题? 滑动过程卡顿,刷新率太低,怎么排查?
8. 快速滑动RecycleView卡顿解决办法
9 .RecyclerView使用时多重type场景下怎么避免滑动卡顿?怎么优化滑动体验?
10 . 比如嵌套,为什么嵌套会卡?
10.RecyclerView刷新闪烁的真凶
1.RecyclerView缓存原理
回收的类在LayoutManager
RecyclerView
滑动时会触发onTouchEvent#onMove
,回收及复用ViewHolder
在这里就会开始
注:官网上貌似把mAttachedScrap、mCachedViews当成一级了,为了方便区分,本文还是把他们当成两级缓存。
1.1 缓存查找的顺序:
mAttachedScrap(屏幕内,可见)----mCachedViews(屏幕外,刚刚移除的)--------mRecyclerPool(缓存池,总的)
1).它会先在mAttachedScrap中找,看要的View是不是刚刚剥离的,如果是就直接返回使用,
2).如果不是,先在mCachedViews中查找,因为在mCachedViews中精确匹配,如果匹配到,就说明这个HolderView是刚刚被移除的,也直接返回,
3).如果匹配不到就会最终到mRecyclerPool找,如果mRecyclerPool有现成的holderView实例,这时候就不再是精确匹配了,只要有现成的holderView实例就返回给我们使用,只有在mRecyclerPool为空时,才会调用onCreateViewHolder新建。
问题: 为什么这么设计多个缓存?
1). mAttachedScrap到底有什么用?
(第一屏,可见),第一次存放。用于插入一个数据进去的时候用到。滑动的时候不用到
mAttachedScrap(屏幕内),用于屏幕内itemview快速重用,不需要重新createView和onBindViewHolder=====(这个哪里用到,没啥用)
插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中。(AttachedScrap 在布局后是没有用的。 )
mAttachedScrap 针对的是屏幕可见itemView信息发生变化时的回收与复用.(是不是当前页面刷新数据的时候用到)
- .mCachedViews (容量是2)它的作用就是保存最新被移除的HolderView
mCacheViews(屏幕外,复用位置 ,个数2个),保存最近移出屏幕的ViewHolder,包含数据和position信息,会不停的更新!!
复用时必须是相同位置的ViewHolder才能复用,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
3). 自定义ViewCacheExtension缓存作用,适用场景:ViewHolder位置固定、内容固定、数量有限时使用
需要用户自定义实现,默认不实现。
4). mRecyclerPool
(缓存池,复用type ,个数5个),当cacheView满了后将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。也会不停的更
面试官: 几个缓存直接的区别:
1).注意的是,在mAttachedScrap和mCachedViews中拿到的HolderView,因为都是精确匹配的,所以都是直接使用,不会调用onBindViewHolder重新绑定数据,
只有在mRecyclerPool中拿到的HolderView才会重新绑定数据。正是有mCachedViews的存在
所以只有在RecyclerView来回滚动时,mCachedViews的使用效率最高,因为凡是从mCachedViews中取的HolderView是直接使用的,不需要重新绑定数据。
mCachedViews中获取的holder是不需要重新bind数据的。mRecyclerPool取出的holder会被重置信息,重新bind数据的。
2). 缓存的值不一样,有的是position。有的是viewType
1.2 存和取的原则
取的原则:mCachedViews > mRecyclerPool
mAttachedScrap不参与回收复用,只保存从在重新布局时,从RecyclerView中剥离的当前在显示的HolderView列表。
当RecyclerView要拿一个复用的HolderView时,获取优先级是mCachedViews > mRecyclerPool
由于一般而言我们是不会自定义mViewCacheExtension的。所以获取顺序其实就是mCachedViews > mRecyclerPool
存放过程:mCachedViews------****mRecyclerPool(一个静态类)
在我们标记为Removed以为,会把这个HolderView移到mCachedViews中,如果mCachedViews已满,就利用先进先出原则,将mCachedViews中老的holderView移到mRecyclerPool中,然后再把新的HolderView加入到mCachedViews中。
1.3. 举例:
上滑动:上面不可见的移动到mCachedViews然后是mRecyclerPool=========调用的方法RecyclerView. getViewForPosition()
下面新的可见, 会从到mCachedViews找然后是mRecyclerPool============调用的方法removeAndRecycleView(child, recycler)
最开始的入口:LinearLayoutManager. onLayoutChildren()
2.讲一下RecyclerView的缓存机制,滑动10个,再滑回去,会有几个执行onBindView
由之前的缓存结构可知,需要重新执行onBindView的只有一种缓存区,就是缓存池mRecyclerPool。
所以我们假设从加载RecyclView开始盘的话(页面假设可以容纳7条数据):
首先,7条数据会依次调用onCreateViewHolder和onBindViewHolder。
往下滑一条(position=7),那么会把position=0的数据放到mCacheViews中。此时mCacheViews缓存区数量为1,mRecyclerPool数量为0。然后新出现的position=7的数据通过postion在mCacheViews中找不到对应的ViewHolder,通过itemtype也在mRecyclerPool中找不到对应的数据,所以会调用onCreateViewHolder和onBindViewHolder方法。
再往下滑一条数据(position=8),如上。
再往下滑一条数据(position=9),position=2的数据会放到mCacheViews中,但是由于mCacheViews缓存区默认容量为2,所以position=0的数据会被清空数据然后放到mRecyclerPool缓存池中。而新出现的position=9数据由于在mRecyclerPool中还是找不到相应type的ViewHolder,所以还是会走onCreateViewHolder和onBindViewHolder方法。所以此时mCacheViews缓存区数量为2,mRecyclerPool数量为1。
再往下滑一条数据(position=10),这时候由于可以在mRecyclerPool中找到相同viewtype的ViewHolder了。所以就直接复用了,并调用onBindViewHolder方法绑定数据。
后面依次类推,刚消失的两条数据会被放到mCacheViews中,再出现的时候是不会调用onBindViewHolder方法,而复用的第三条数据是从mRecyclerPool中取得,就会调用onBindViewHolder方法了。
1)所以这个问题就得出结论了(假设mCacheViews容量为默认值2):
如果一开始滑动的是新数据,那么滑动10个,就会走10个bindview方法。然后滑回去,会走10-2个bindview方法。一共18次调用。
如果一开始滑动的是老数据,那么滑动10-2个,就会走8个bindview方法。然后滑回去,会走10-2个bindview方法。一共16次调用。
但是但是,实际情况又有点不一样。因为Recycleview在v25版本引入了一个新的机制,预取机制。
预取机制,就是在滑动过程中,会把将要展示的一个元素提前缓存到mCachedViews中,所以滑动10个元素的时候,第11个元素也会被创建,也就多走了一次bindview方法。但是滑回去的时候不影响,因为就算提前取了一个缓存数据,只是把bindview方法提前了,并不影响总的绑定item数量。
所以滑动的是新数据的情况下就会多一次调用bindview方法。
2)总结,问题怎么答呢?
四级缓存和流程说一下。
滑动10个,再滑回去,bindview可以是19次调用,可以是16次调用。
缓存的其实就是缓存item的view,在Recycleview中就是viewholder。
cachedView就是mCacheViews缓存区中的view,是不需要重新绑定数据的。
https://www.cnblogs.com/jimuzz/p/14040674.html
问题:这么说不是第一次移除屏幕就复用了?而且总共是5+2=7.
3. ListView和RecyclerView区别?缓存区别
1).缓存机制不一样
RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView,而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)
ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
listview的缓存
- mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的时候将会返回null,所以mActiveViews不能被重复利用。
2).Listview支持,HeaderView 和 FooterView 。源码里面有个headViewAdapter!
而RecyclerView支持横竖滑动LayoutManager
3). RecyclerView支持动画
4).局部刷新方式或者用dif算法,减少刷新
4.如何让两个 RecyclerView 共用一个缓存,今日头条页面实例
通过RecyclewView直接获回收池RecycledViewPool
RecyclerView.RecycledViewPool recycledViewPool=mRecyclerView.getRecycledViewPool();
使用多个RecyclerView,并且里面有相同item布局时,这时就可以通过setRecycledViewPool()设置同一个RecycledViewPool;
5.为什么RecyclerView加载首屏会慢一些
第一次要createview和bindview()。没有任何缓存
6. RecyclerView如何优化
第一点:从缓存方法考虑
1)、根据需求修改RecyclerView默认的绘制缓存选项
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
2).使用多个RecyclerView,并且里面有相同item布局时,这时就可以通过setRecycledViewPool()设置同一个RecycledViewPool;
因为,RecycleViewPool用来存放 mCachedViews 移除的ViewHolder。按照 Type 类型,默认对每个Type最多缓存 5 个。重点源码中它是被 public static 修饰,表示可以被其他RecyclerView 共享。
3).当是网格布局的时候,如果一行的item超过五个,需要通过setMaxRecycledViews()去重新设置缓存的最大个数;
4).可以使用setHasStableIds(true)进行设置(同时重写Adapter的getItemID()方法),这时会复用到scrap缓存;
`5).还有一个方法RecyclerView.setHasFixedSize(true)可以避免数据改变时重新计算RecyclerView的大小
6). 使用getExtraLayoutSpace为LayoutManager设置更多的预留空间
在RecyclerView的元素比较高,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿。`
7).滑动监听------->主要监听快速滑动
主要就是对onScrollStateChanged方法进行监听,然后通知adapter是否加载图片或复杂布局
8).局部刷新替代全局刷新。避免整个列表的数据更新,只更新受影响的布局。例如,加载更多时,不使用notifyDataSetChanged()
,而是使用notifyItemRangeInserted(rangeStart, rangeEnd)
9).减少onclick的多个对象,比如在bindview,创建大量的onClick
解决办法: 重用OnClickListener
通常我们为Button设置点击事件的时候都是直接创建一个匿名内部类的对象(new OnClickListener{}),习惯了这种绑定事件的方式后我们可能在列表中也这么做。在列表滑动的时候会不停的重复创建新的OnClickListener的操作,旧的OnClickListener会被标记为需要垃圾回收,当需要回收的对象过多的时候会引起GC,导致列表卡顿。可以创建一个通用的OnClickListener,把数据放入Button的Tag中,根据id来判断是哪个Button执行了点击,来取出数据、执行不同的逻辑。
第二点: 耗时任务
根本原因:不要在oncreate和onbinderview里面做耗时。【onbindView里面不要做太多的耗时,或者拼接字符串】
1). 比如字符串拼接、时间格式化等操作都是比较耗时的操作
2). 测量的层面:布局的重绘, 刷新的层面: 优化子View的层级--->层级优化
https://blog.csdn.net/cym492224103/article/details/106374847
6.1 RecyclerView预加载机制源码分析,RecyclerView数据预取
6.2.如何实现RecyclerView的局部更新,用过payload吗,notifyItemChange方法中的参数?
7. 列表滑动卡顿该如何定位问题? 滑动过程卡顿,刷新率太低,怎么排查?
7. 1.卡顿监控,doFrame()方法或 ASM插桩, 和一般的列表是一样的
卡顿的几个原因
1).内存抖动 ------>GC频繁------>线程停止工作
2).耗时任务
7.2 卡顿原因
`1).有时候我们会根据数据类型来控制一个View的显隐,当给一个View设置setVisibility(View.GONE)的时候,会触发布局的重新测量、布局、绘制等操作,若itemView的布局比较复杂,重新测量绘制会很耗时间,引起列表卡顿
2).如果布局很复杂,可以考虑自定义布局能不能实现。
3). measure()优化和减少requestLayout()调用`
当RecyclerView宽高的测量模式都是EXACTLY时,onMeasure()方法不需要执行dispatchLayoutStep1()等方法来进行测量。而当RecyclerView的宽高不确定并且至少一个child的宽高不确定时,要measure两遍。
因此将RecyclerView的宽高模式都设置为EXACTLY有助于优化性能。
protected void onMeasure(int widthSpec, int heightSpec) {
// ......
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
// ......
}
8. 快速滑动RecycleView卡顿解决办法
8.1. 快速滑动RecycleView卡顿原因:
因为,列表上下滑动的时候,RecycleView会在执行复用策略,onCreateViewHolder和onBindViewHolder会执行。item视图创建或数据绑定的方法会随着滑动被多次执行,容易造成卡顿。
8.2 解决快速滑动造成的卡顿
一般都采用滑动关闭数据加载优化:主要是设置RecyclerView.addOnScrollListener();通过自定义一个滑动监听类继承onScrollListener抽象类,实现滑动状态改变的方法onScrollStateChanged(recycleview,state),从而实现在滑动过程中不加载,当滚动静止时,刷新界面,实现加载。
9 .RecyclerView使用时多重type场景下怎么避免滑动卡顿?怎么优化滑动体验?
10 . 比如嵌套,为什么嵌套会卡?
https://juejin.cn/post/6844903442574475277?searchId=20240422174546273335B676E2A52CFA67
11.RecyclerView刷新闪烁的真凶
notifyDataSetChanged 导致图片闪烁的真凶