3.Android 高级UI<三>之 RecyclerView 的缓存机制 RecyclerView卡顿优化(2024精华版)

目录:

1.RecyclerView缓存原理

2.讲一下RecyclerView的缓存机制,滑动10个,再滑回去,会有几个执行onBindView

  1. ListView和RecyclerView区别?缓存区别

4.如何让两个 RecyclerView 共用一个缓存,今日头条页面实例

  1. RecyclerView如何优化

5.1 RecyclerView预加载机制源码分析,RecyclerView数据预取

5.2.如何实现RecyclerView的局部更新,用过payload吗,notifyItemChange方法中的参数?

6.为什么RecyclerView加载首屏会慢一些

  1. 列表滑动卡顿该如何定位问题? 滑动过程卡顿,刷新率太低,怎么排查?

8. 快速滑动RecycleView卡顿解决办法

9 .RecyclerView使用时多重type场景下怎么避免滑动卡顿?怎么优化滑动体验?

10 . 比如嵌套,为什么嵌套会卡?

10.RecyclerView刷新闪烁的真凶

1.RecyclerView缓存原理

回收的类在LayoutManager

RecyclerView滑动时会触发onTouchEvent#onMove,回收及复用ViewHolder在这里就会开始

注:官网上貌似把mAttachedScrap、mCachedViews当成一级了,为了方便区分,本文还是把他们当成两级缓存。

jietu-1713773195428.jpg

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信息发生变化时的回收与复用.(是不是当前页面刷新数据的时候用到)

  1. .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

设计方案.jpg

由之前的缓存结构可知,需要重新执行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 导致图片闪烁的真凶

https://www.jianshu.com/p/29352def27e6

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容