再也不用担心面试官问RecycleView了

简介: 关于RecyclerView,之前我写过一篇比较基础的文章,主要说的是缓存和优化等问题。但是有读者反映问题不够实际和深入。于是,我又去淘了一些关于RecyclerView的面试真题,大家一起看看吧,这次的问题如果都弄懂了,下次面试再遇到RecyclerView应该就没啥可担心的了。

关于RecyclerView,之前我写过一篇比较基础的文章,主要说的是缓存和优化等问题。但是有读者反映问题不够实际和深入。于是,我又去淘了一些关于RecyclerView的面试真题,大家一起看看吧,这次的问题如果都弄懂了,下次面试再遇到RecyclerView应该就没啥可担心的了。


  • 讲一下RecyclerView的缓存机制,滑动10个,再滑回去,会有几个执行onBindView。缓存的是什么?cachedView会执行onBindView吗?
  • RecyclerView预取机制
  • 如何实现RecyclerView的局部更新,用过payload吗,notifyItemChange方法中的参数?
  • RecyclerView嵌套RecyclerView滑动冲突,NestScrollView嵌套RecyclerView。


讲一下RecyclerView的缓存机制,滑动10个,再滑回去,会有几个执行onBindView。缓存的是什么?cachedView会执行onBindView吗?


RecyclerView预取机制


这两个问题都是关于缓存的,我就一起说了。


1)首先说下RecyclerView的缓存结构:


Recyclerview有四级缓存,分别是mAttachedScrap(屏幕内),mCacheViews(屏幕外),mViewCacheExtension(自定义缓存),mRecyclerPool(缓存池)


  • mAttachedScrap(屏幕内),用于屏幕内itemview快速重用,不需要重新createView和bindView
  • mCacheViews(屏幕外),保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
  • mViewCacheExtension(自定义缓存),不直接使用,需要用户自定义实现,默认不实现。
  • mRecyclerPool(缓存池),当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。


2)四级缓存按照顺序需要依次读取。所以「完整缓存流程」是:


  1. 保存缓存流程:


  • 插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap
  • 滑动屏幕的时候,先消失的itemview会保存到CacheView,CacheView大小默认是2,超过数量的话按照先入先出原则,移出头部的itemview保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype进行保存,每个itemType缓存个数为5个,超过就会被回收。


  1. 获取缓存流程:


  • AttachedScrap中获取,通过pos匹配holder——>获取失败,从CacheView中获取,也是通过pos获取holder缓存 ——>获取失败,从自定义缓存中获取缓存——>获取失败,从mRecyclerPool中获取 ——>获取失败,重新创建viewholder——createViewHolder并bindview。


3)了解了缓存结构和缓存流程,我们再来看看具体的问题 滑动10个,再滑回去,会有几个执行onBindView?


  • 由之前的缓存结构可知,需要重新执行onBindView的只有一种缓存区,就是缓存池mRecyclerPool


所以我们假设从加载RecyclView开始盘的话(页面假设可以容纳7条数据):


  • 首先,7条数据会依次调用onCreateViewHolderonBindViewHolder


  • 往下滑一条(position=7),那么会把position=0的数据放到mCacheViews中。此时mCacheViews缓存区数量为1,mRecyclerPool数量为0。然后新出现的position=7的数据通过postion在mCacheViews中找不到对应的ViewHolder,通过itemtype也在mRecyclerPool中找不到对应的数据,所以会调用onCreateViewHolderonBindViewHolder方法。


  • 再往下滑一条数据(position=8),如上。


  • 再往下滑一条数据(position=9),position=2的数据会放到mCacheViews中,但是由于mCacheViews缓存区默认容量为2,所以position=0的数据会被清空数据然后放到mRecyclerPool缓存池中。而新出现的position=9数据由于在mRecyclerPool中还是找不到相应type的ViewHolder,所以还是会走onCreateViewHolderonBindViewHolder方法。所以此时mCacheViews缓存区数量为2,mRecyclerPool数量为1。


  • 再往下滑一条数据(position=10),这时候由于可以在mRecyclerPool中找到相同viewtype的ViewHolder了。所以就直接复用了,并调用onBindViewHolder方法绑定数据。


  • 后面依次类推,刚消失的两条数据会被放到mCacheViews中,再出现的时候是不会调用onBindViewHolder方法,而复用的第三条数据是从mRecyclerPool中取得,就会调用onBindViewHolder方法了。


4)所以这个问题就得出结论了(假设mCacheViews容量为默认值2):


  • 如果一开始滑动的是新数据,那么滑动10个,就会走10个bindview方法。然后滑回去,会走10-2个bindview方法。一共18次调用。
  • 如果一开始滑动的是老数据,那么滑动10-2个,就会走8个bindview方法。然后滑回去,会走10-2个bindview方法。一共16次调用。


「但是但是」,实际情况又有点不一样。因为Recyclerview在v25版本引入了一个新的机制,预取机制


预取机制,就是在滑动过程中,会把将要展示的一个元素提前缓存到mCachedViews中,所以滑动10个元素的时候,第11个元素也会被创建,也就多走了一次bindview方法。但是滑回去的时候不影响,因为就算提前取了一个缓存数据,只是把bindview方法提前了,并不影响总的绑定item数量。


所以滑动的是新数据的情况下就会多一次调用bindview方法。


5)总结,问题怎么答呢?


  • 四级缓存和流程说一下。
  • 滑动10个,再滑回去,bindview可以是19次调用,可以是16次调用。
  • 缓存的其实就是缓存item的view,在Recyclerview中就是viewholder
  • cachedView就是mCacheViews缓存区中的view,是不需要重新绑定数据的。


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


关于RecyclerView的数据更新,主要有以下几个方法:


  • notifyDataSetChanged(),刷新全部可见的item。*notifyItemChanged(int),刷新指定item。
  • notifyItemRangeChanged(int,int),从指定位置开始刷新指定个item。
  • notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int)。插入、移动一个并自动刷新。
  • notifyItemChanged(int, Object),局部刷新。


可以看到,关于view的局部刷新就是notifyItemChanged(int, Object)方法,下面具体说说:


notifyItemChange有两个构造方法:


  • notifyItemChanged(int position, @Nullable Object payload)
  • notifyItemChanged(int position)


其中payload参数可以认为是你要刷新的一个标示,比如我有时候只想刷新itemView中的textview,有时候只想刷新imageview?又或者我只想某一个view的文字颜色进行高亮设置?那么我就可以通过payload参数来标示这个特殊的需求了。


具体怎么做呢?比如我调用了notifyItemChanged(14,"changeColor"),那么在onBindViewHolder回调方法中做下判断即可:


@Override
    public void onBindViewHolder(ViewHolderholder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            // payloads为空,说明是更新整个ViewHolder
            onBindViewHolder(holder, position);
        } else {
            // payloads不为空,这只更新需要更新的View即可。
            String payload = payloads.get(0).toString();
            if ("changeColor".equals(payload)) {
                holder.textView.setTextColor("");
            }
        }
    }


RecyclerView嵌套RecyclerView滑动冲突,NestScrollView嵌套RecyclerView。


1)RecyclerView嵌套RecyclerView的情况下,如果两者都要上下滑动,那么就会引起滑动冲突。默认情况下外层的RecyclerView可滑,内层不可滑。


之前说过解决滑动冲突的办法有两种:「内部拦截法和外部拦截法」。这里我提供一种内部拦截法,还有一些其他的办法大家可以自己思考下。


holder.recyclerView.setOnTouchListener { v, event ->
            when(event.action){
                //当按下操作的时候,就通知父view不要拦截,拿起操作就设置可以拦截,正常走父view的滑动。
                MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE -> v.parent.requestDisallowInterceptTouchEvent(true)
                MotionEvent.ACTION_UP -> v.parent.requestDisallowInterceptTouchEvent(false)
            }
            false}


2)关于ScrclerView的滑动冲突还是同样的解决办法,就是进行事件拦截。还有一个办法就是用Nestedscrollview代替ScrollViewNestedscrollview是官方为了解决滑动冲突问题而设计的新的View。它的定义就是支持嵌套滑动的ScrollView。


所以直接替换成Nestedscrollview就能保证两者都能正常滑动了。但是要注意设置RecyclerView.setNestedScrollingEnabled(false)这个方法,用来取消RecyclerView本身的滑动效果。


这是因为RecyclerView默认是setNestedScrollingEnabled(true),这个方法的含义是支持嵌套滚动的。也就是说当它嵌套在NestedScrollView中时,默认会随着NestedScrollView滚动而滚动,放弃了自己的滚动。所以给我们的感觉就是滞留、卡顿。所以我们将它设置为false就解决了卡顿问题,让他正常的滑动,不受外部影响。


拜拜


今天聊了不少,关于RecyclerView重要的知识点应该都涉及到了,其中bindview的问题下次有机会我会再详细的说一下,配合图片日志,今天时间确实来不及了。


最后希望大家好好巩固知识,加油。

目录
相关文章
|
Java 程序员
IT学不好没什么,大不了躺平
IT学不好没什么,大不了躺平
|
存储 Web App开发 缓存
一个简单的弱网差点搞死了组内前端
最近上线了一个 React Native 外访项目,用户为公司外访员,外访员根据公司业务去实地考察,收集记录一些资料,考察记录资料的过程全部用公司配的专用手机,里面安装了当前外访项目APP。目前项目试运行阶段,还没有正式交付。APP项目上线后,在用户真实使用中遇到一些各种各样的问题,有些问题处理时也比较棘手(如弱网情况),这次主要复盘APP在实际场景中的弱网(或网络不稳定)相关的问题。
905 0
一个简单的弱网差点搞死了组内前端
|
Web App开发 安全 中间件
学会这招,技术问题再也难不倒你
学会这招,技术问题再也难不倒你
学会这招,技术问题再也难不倒你
|
算法 NoSQL API
到底该不该看源码(懂这三点儿就够了)
1、不要为了看源码而看源码 2、代码积累到一定程度,遇到问题自然就去查源码了,然后你就看懂了 3、两年内不要刻意去看源码,可以点开简单了解一下就行,前两年疯狂做项目就行了,后期项目做的多了,你自己就会有疑问,每次写代码就会问自己为什么要这样写?底层的原理是什么?很自觉的带着问题就去看源码了,如果你没有这样的疑问,那说明你也不适合去看源码了,写写业务代码,了了一生
191 0
|
存储 Web App开发 程序员
程序猿的血泪史:一定要有数据备份的思想,不然死都不知道咋死的!!!
程序猿的血泪史:一定要有数据备份的思想,不然死都不知道咋死的!!!
207 0
|
消息中间件 存储 前端开发
面试官让我手写队列,差点没写出来,回来后赶忙把重点记下来
栈和队列是一对好兄弟,前面我们介绍过一篇栈的文章(栈,不就后进先出),栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出入口,只能后进先出(在外面的先出去,堵在里面先进去的就有点倒霉)。而队列就好比是一个隧道,后面的人跟着前面走,前面人先出去(先入先出)。日常的排队就是队列运转形式的一个描述!
114 0
面试官让我手写队列,差点没写出来,回来后赶忙把重点记下来
|
存储 机器学习/深度学习 监控
我是傻x,被迫看了 1 天源码,千万别学我!
大家好,我是零一,之前一直很忙,业余时间的输入和输出都 24k铝合金人眼可见 得下降,这不最近上海疫情严重么,算了一下居家办公也已经将近 1个月了,这才有些许时间学习,所以最近也是一直在鼓捣点新东西,不为别的,主要是想再多输入一些新的知识
178 0
我是傻x,被迫看了 1 天源码,千万别学我!
|
程序员
能让程序员瞬间崩溃的五个瞬间,共鸣的同学请举手!
在我们的眼里,程序员好像是无所不能的,那么复杂的App和那些游戏都是他们做出来的,这让我们很难相信还有什么是他做不出来的。不过,就是我们每天眼里看着很厉害的程序员,每天都要面临的就是头疼,头疼,头好疼,特别是我接下来要说的几件事情,几乎是所有程序员都会把头抓秃的事     那么这五件事情究竟是什么事呢? 写着代码停电,代码没有保存 如果有一天突然代码写到一半,眼看就快要完工了,突然一下就断电,代码没保存。
1337 0
|
设计模式 架构师 Java
为什么有些蛮厉害的人,后来都不咋样了
写这篇文章目的是之前在一篇文章中谈到,我实习那会有个老哥很牛皮,业务能力嘎嘎厉害,但是后面发展一般般,这引起我的思考,最近有个同事发了篇腾讯pcg的同学关于review 相关的文章,里面也谈到架构师的层次,也再次引起我关于架构师的相关思考,接下来我们展开聊聊吧~
158 0
|
芯片
程序人生 - 手上总有静电该怎么处理?
程序人生 - 手上总有静电该怎么处理?
147 0
程序人生 - 手上总有静电该怎么处理?