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