6月26日,法拉第解雇了几十名未来的无薪员工,包括该公司位于加州的汉福德工厂的制造工人。据悉,这些员工从2018年底开始停薪留职,期间曾一次延长停薪留职,并获得相应的岗位福利。根据一名前员工提供的辞退文件,这些新辞退员工的福利将于6月30日到期。
/作者简介/
本文来自于lyldalek的贡献,分享了他对RecyclerView缓存机制的理解。相信对大家都有帮助!同时也感谢作者精彩的文章。
莱尔达莱克的博客地址:
https://blog.csdn.net/weixin_43130724
/前言/
以后我会尽量写一些不是源代码的东西,因为过了一段时间,我发现自己在努力读自己的文章。而且大部分源代码都在分析方法的调用链中,意义不大,需要多写一些自己的理解。如果有时候要写,也要简化调用链的分析,提供类图,写出重点和结论。
本文的观点和结论是对回收视图的剖析:基于参考文献寻找视图持有者
https://Android . jll else . eu/anatomy-of-recycle view-part-1-a-search-for-a-view holder-404 ba 3453714
对回收视图的剖析:寻找视图支架
https://Android . jll else . eu/analog-of-recycle view-part-1-a-search-for-a-view holder-continued-d81c 631 a2 b 91
这两篇文章来自。
因为这两篇文章的一些观点与其他文章的观点不一致,所以我在自己的理解和推理调试后支持这两篇文章的观点,所以当你阅读参考文献时,你需要自己考虑。
/了解回收视图/
RecyclerView有五个老虎将军:
“回收视图”的职责是按照一定的规则在其上显示数据集中的数据。但是据说天行者视图只是一个ViewGroup,只知道View,不知道Data数据的具体结构。所以,当两个陌生人想要建立通话时,我们很容易想到Adapter模式。因此,回收视图需要一个适配器来与数据集通信:
如上图所示,recycle view表示只和ViewHolder联系,Adapter的工作是将数据转换成recycle view已知的ViewHolder,所以recycle view间接知道数据。
虽然事情进展顺利,但RecyclerView是个懒人。虽然适配器已将数据转换为RecyclerView熟知的视图,但RecyclerView不想单独管理某些子视图。因此,它聘请了一位名叫LayoutManager的大祭司来帮助它完成布局。现在,图表如下所示:
如上图所示,布局管理器协助回收视图完成布局。但是,LayoutManager这个大祭司也有一个弱点,就是只知道在RecyclerView上一个个的布局视图,却不知道如何管理这些视图。如果大祭司肆无忌惮的玩View,一定会出事的。
因此,必须有一个管理视图的保管者,即回收者。当布局管理器需要视图时,他会向保管人请求。当布局管理器不需要视图时,他会直接将放弃的视图扔给回收器,如下所示。
这里,适配器负责转换数据,布局管理器负责布局,回收器负责管理视图。一切都很完美,但是回收视图是如此神圣,以至于它命令当子视图改变时,姿势应该是优雅的,所以雇佣了一个舞蹈演员,项目动画师。因此,舞者也进入这个图表:
如上,我们从宏观层面对RecylerView有一个大致的了解。我们可以看到,Reckler View作为一个视图,只负责接收用户的各种消息,然后按照各自的职责进行消息的分发。
最后,项目装饰是显示每个项目之间的分离风格。它的本质实际上是一个可画的。当RecyclerView执行onDraw方法时,它将调用其onDraw。此时,如果您重写此方法,就相当于直接在RecyclerView上绘制一个可绘制的表示。
最后,他内部有一个叫getItemOffsets的方法,可以从字面上理解,用来偏移每个项视图。当我们在每一个项视图之间强行插入并绘制一个drawing able截面时,那么如果按照原来的逻辑绘制项视图,那么Decoration就会被覆盖,所以我们需要getItemOffsets的方法,这样每一个项都会被稍微偏到后面,这样就不会覆盖之前绘制的分离样式。
PS:
实际上ItemDecoration的宽度和高度都是在itemview中计算的,但是itemview本身的绘图区域并没有那么大,预留的地方也只是透明的,所以ItemDecoration是通过itemview显示的。然后就很有意思了。如果我故意在ItemDecoration的偏移量写0,那么itemview就会屏蔽掉ItemDecoration,当itemview被添加或者删除的时候,就会短时间消失。使用这种组合也可以制作出意想不到的动画效果。
虽然Google尽力去解耦,但是源代码中还是有一些逻辑混在一起的地方,比如动画处理。
为了更好的理解下面的内容,本文首先介绍了什么是前置布局和后置布局。
有一个场景,我们有三个项目,其中A和B显示在屏幕上,我们删除B就会显示C..
我们想看到的是,C从底部顺利滑向新的位置。
但是这是怎么发生的呢?
我们知道C在新布局中的最终位置,但是如何知道它应该从哪里开始滑动呢?
Google的解决方案如下:
适配器更改后,回收视图从布局管理器请求两个布局。
第一个——预布局,因为我们可以接收适配器的变化,我们可以在这里做一些特殊的处理。在我们的例子中,因为我们现在知道B已经被删除,我们将另外显示C,即使它已经超过了限制。
第二种是后期布局,一种正常的布局,对应于已更改的适配器状态。
现在,通过比较c在预布局和后布局中的位置,我们可以正确地制作它的动画。
仔细想想这个动画,这种动画——当动画视图不存在于以前的布局或新的布局中时——被称为预测动画。
再想想另一个场景:如果B变了而不是被删了会怎么样?
答案是,c在预布局阶段还是会被排在后面!为什么?因为无法预测C是什么样的动画,也许是动画让B的高度变小了,那么就需要显示C,如果不是,那么就把C放入缓存。
publicfinalclassRecycler{
最终数组列表<。ViewHolder>。mattachedstrap = new ArrayList & lt;>。;
ArrayList<。ViewHolder>。mChangedScrap = null
最终数组列表<。ViewHolder>。mCachedViews = newArrayList & ltViewHolder>。;
privatefinalList<。ViewHolder>。
munmodifieleattachedstrap = collections . unmodifieblelist;
privateintmRequestedCacheMax = DEFAULT _ CACHE _ SIZE;
intmViewCacheMax = DEFAULT _ CACHE _ SIZE;
RecycledViewPool mRecyclerPool
privateViewCacheExtension mViewCacheExtension;
staticfinaintdefault _ CACHE _ SIZE = 2;
碎片
废弃缓存是recyclerView搜索的第一个缓存。互联网上有许多缓存调用图,第一次缓存调用是废弃的。报废分为两套。
仅在布局期间,废料不是空。当布局管理器开始布局时,所有视图支架都被放入废料堆。然后把他们一个个找回来,除非某些观点有所改变。
这里有一个题外话,就是有人可能会问,为什么要放到废铁里再拿出来?不是要去找麻烦吗?
我的观点是:布局管理器负责布局,回收器负责缓存。布局管理器不应该知道哪个视图持有者是有效的,这是一种职责分离的设计。
MAttachedScrap和mChangedScrap:这两个挺特别的。我在LinearLayoutManager的onLayoutChildren方法中跟踪了添加mattachedscrap的时机:
publicationlayoutchildren{
...
onAnchorReady;
//这里调用mattachedstrap . add;
//这里也叫mchangedscrack . add;
detachAndScrapAttachedViews;
...
}
这里的通话时机值得思考。它在布局时被放入缓存中。它显示缓存针对屏幕上显示的视图。
那么问题来了,为什么要缓存屏幕上的显示?我的想法倾向于减少布局方法调用的影响。
例如,当我们调用notifyItemRangeChanged方法时,将触发requestLayout方法,并重新排列布局。如果布局重新排列,viewHolder会先放入废料中,然后在填充布局时,从mattachedslap中取出直接使用。
mcchangedslap中的viewHolder将被移动到RecycledViewPool,所以对应于mcchangedslap的项需要从池中取出对应的viewHolder,然后重新绑定。
现在让我们想想为什么我们需要两个缓存,mChangedScrap和mAttachedScrap。
mChangedScrap指示项目已更改,可能是数据更改或类型更改,因此无法重用其视图持有者,因此只能从RecycledViewPool中检索相应的视图持有者,然后重新绑定它。
MChangedScrap和mAttachedScrap的功能类似。
MChangedScrap更多用于预布局的动画处理。
那么需要注意的是,mChangedScrap只能用于预布局,mAttachedScrap可以用于预布局和后布局。
在继续讨论之前,有必要解释几种方法之间的差异:
在视图中分离和移除
视图组中分离的实现非常简单,只需将当前视图从父视图的子视图数组中移除,并将当前视图的mParent设置为null,可以理解为轻量级的临时移除。
Remove代表真正的移除,它不仅从ChildView数组中移除,还完全切断了与视图树的其他链接。
回收视图中的报废视图
废弃视图是指在回收视图中经历分离操作的缓存。RecyclerView源代码中部分代码注释的分离实际上是指移除,这种缓存是按位置匹配的,不需要重新绑定视图。
回收视图是指真正的删除操作后的缓存,需要被bindView重用。
缓存和池
缓存和池中存储的内容属于回收视图,需要再次添加到列表中。
MCachedViews,这个比较简单。
它是ArrayList类型,不区分viewHolder的类型,大小限制为2,但是可以使用SetItemViewCache调整它的大小。
因为没有区分viewpholder的类型,所以只能根据位置获取viewpholder。
回收视图池,存储各种类型的视图容器
最大数量为5,每种类型的存储容量可以通过setMaxRecycledViews方法设置。另一个要点是,多个列表可以共享一个回收视图池,并使用setRecycledViewPool方法。
这里,顺便说一下,每个缓存的使用差异使得对每个缓存池有一个大致的了解成为可能:
如果在所有缓存中都没有找到 viewHolder,那就会调用 create 和 bind 方法。如果在 pool (RecycledViewPool ) 中找到了,那么会调用 bind 方法。如果在 cache (mCachedViews)中找到了,啥都不用做,直接显示就好了。所以要注意他们的不同。视图持有者进入缓存和池是不同的。
现在,让我们来思考下一个问题:mCachedViews的大小是有限制的。救不了怎么办?
实际上,虽然mCachedViews是一个数组列表,但是它的工作模式有点类似于链表。当mCachedViews已满时,它将删除第一个保存的元素并将其放入池中,如下所示:
当我们滑动列表时,一旦项目离开屏幕,它将被放入mCachedViews。如果已满,则“尾部”元素将被移动到池中。如果池已满,将被丢弃,等待回收。
下面,使用几个场景来巩固我们所学的内容:
场景1:
先看图片左侧。向下滑动时,3首先进入mCachedViews,然后是4和5。5会挤出3,然后3会跑进池子里。
再看图的右边,继续下滑的时候,4被6挤出,放入池中。同时需要显示8,先从池中取出,发现只有一个3,就取出来重新显示在屏幕上。
场景2:
如果,显示下滑7后,不再继续下滑,而是向上滑,那么会发生什么?
看图片右侧,很明显是5从缓存中取出,不重新绑定直接重用,7放入缓存。
想想,应该怎么利用这种情况?
比如我们有一个壁纸库列表,用户经常上下滑动,那么如果增加缓存的容量,会得到更好的性能。但是用户很少返回提要流等列表,所以增加缓存容量意义不大。
此外,如果我们继续向上滑动,那么4和7将被放入缓存,3将从池中取出。但是这里要注意的是,因为3是从池中取出来的,所以需要重新绑定,但是从逻辑上讲,如果3位的数据没有变化,就不需要重新绑定,也是有效的。所以也可以以此为优化点,在onBindViewHolder方法中进行检查。
进一步,在滑动的过程中,池中应该总是只有一个一种类型的viewHolder,所以如果你的池中有多个viewHolder,那么它们在滚动的过程中基本没有用。
场景3:
当我们调用notifyDataSetChanged或者notifyitemlrangechanged的时候,很多viewHolder最终都会被放入池中,因为只有五个可以放入池中,所以剩余的会被丢弃等待回收。最重要的是重新创建和绑定,这对性能有很大的影响。如果您的列表可以容纳许多行,并且经常使用notifyDataSetChanged方法,您应该考虑设置容量。
recycle view . GetRecycledviewPool . SetMaxRecycledviews;
ViewCacheExtension
这个需要定制,使用上有很大的限制,就不深入介绍了。
因为它需要你创建自己的viewHolder并缓存,那么问题来了。当我们删除或添加一个项目时,适配器帮助程序会调用回收视图来通知它需要处理更改。回收视图遍历当前显示的视图支架,然后移动它们的位置。但是这里有个bug。RecyclerView不知道你创建的viewHolder,所以它不关心你自己缓存的viewHolder。
位置固定,比如,广告位。数量合理,保存在内存中没啥关系。稳定的Ids
就像我们之前说的,调用notifyDataSetChanged的时候,recyclerView不知道发生了什么,所以它只能认为一切都变了,也就是把所有的viewHolder放入池中。
但是,如果我们设置稳定的id,情况就不同了:
观众席是放在废料而不是池。注意这里,它的性能提升了不少!
不用重新绑定,重新创建新的 viewHolder,不用重新 addView。addView会导致重新测量…原来我们需要调用 notifyItemMoved,但是现在直接调用 notifyDataSetChanged 就好了,但是我测试的是没有动画效果的。在这里,您应该能够回答以下问题:
notifyDataSetChanged 与 notifyItemRangeChanged 的区别?RecyclerView 与 ListView 缓存的区别?这个问题,即使你不知道 ListView 的缓存机制,也应该能说些什么。如何对一个列表进行性能优化?调用 notifyDataSetChanged 时闪烁的原因?1.《recycler 看完这篇,面试RecyclerView的时候再也不怕了》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《recycler 看完这篇,面试RecyclerView的时候再也不怕了》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/yule/1792047.html