RecyclerView的剖析:搜索ViewHolder(续)

170 阅读5分钟

介绍

为了便于参考,我在此处复制了RecyclerView的视图搜索算法的概述。
  1. 搜索 **changed scrap**
  2. 搜索 **attached scrap**
  3. 搜索未被移除的 **hidden views**
  4. 搜索 **view cache**
  5. 如果Adapter具有稳定的ID, 通过指定ID再次搜索 **attached scrap** 和 **view cache**
  6. 搜索 **ViewCacheExtension**
  7. 搜索 **RecycledViewPool**
到目前为止,我们已经介绍了RecycledViewPool,接下来看**view cache**

View Cache

当我说“View Cache”或只是“Cache”时,我指的是RecyclerView.Recycler类中的字段mCachedViews。在代码中的某些注释中,它也被称为“一级缓存”。

这只是ViewHolders的ArrayList,此处未按视图类型拆分。默认容量为2,您可以通过RecyclerView的方法setItemViewCacheSize()对其进行调整。
正如我之前提到的,池和其他缓存(包括**view cache**)之间最重要的区别是,在这些其他缓存中搜索与给定位置关联的ViewHolder,而按视图类型搜索池。当ViewHolder位于**view cache**中时,我们希望按原样重用它,而无需重新绑定,该缓存的位置与它进入缓存之前的位置相同。
因此,让我们清楚地区分此区别:
  • 如果在任何地方都找不到ViewHolder,则会创建并绑定它。
  • 如果在池中找到ViewHolder,则将其绑定。
  • 如果在缓存中找到ViewHolder,则无需执行任何操作。
这样我们就清楚:一个ViewHolder的**绑定**和**回收**到池(`onViewRecycled()`)与在列表中滑出和滑入是不一样的东西。列表中滑出,其ViewHolder可以转到**View Cache**而不是池中,滑入时,有时会从**View Cache**中检索ViewHolder并没有绑定。如果需要跟踪屏幕上项目的存在,请使用适配器的onViewAttachedToWindow()和onViewDetachedFromWindow()回调。
**填充池和缓存**
现在,进入下一个问题:ViewHolders如何最终出现在视图缓存中?当我谈论通向池的场景时,我实际上欺骗了您一些。在这些情况下(第三种情况除外),ViewHolder会同时进入高速缓存和或池。
[⁵](https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#24c3)

让我说明选择高速缓存或池的规则。假如,我们最初有空的池和缓存,然后逐一回收这些项目。这是填充池和缓存的方式(假设默认容量和一种视图类型):

![图片发布](https://miro.medium.com/max/1038/0*JKHlomcG7gdcpA7h.)
因此,只要缓存未满,ViewHolders就会到达那里。如果已满,则新的ViewHolder会将ViewHolder从缓存的“另一端”推送到池中。如果池已满,则该ViewHolder会被遗忘到垃圾回收器中。 [
](https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#db80)

运行中的池和缓存

现在让我们看一下池和缓存在两种实际的RecyclerView使用场景中的行为方式。考虑滚动:
![图片发布](https://miro.medium.com/max/1802/0*loJwYG5qlTLTRiTX.)
当我们向下滚动时,当前看到的项目后面有一个“尾巴”,该项目由缓存的项目然后是合并的项目组成。当项目8出现在屏幕上时,在缓存中找不到合适的ViewHolder:没有与位置8关联的ViewHolder。因此,我们使用池化的ViewHolder,该池以前位于位置3。当项目6消失在顶部时,它进入缓存,将4推入池中。当我们开始朝相反的方向滚动时,图片会有所不同:
![图片发布](https://miro.medium.com/max/1712/0*lcgR2438JvV5g3Sz.)
在这里,我们在视图缓存中找到位置5的ViewHolder,无需重新绑定即可立即重用。这似乎是缓存的主要用例-使反向滚动到我们刚刚看到的项目更加有效。因此,如果您有新闻提要,则缓存可能无用,因为用户不会经常返回。但是,如果要作为选择的清单,例如壁纸画廊,您可能想扩展缓存的容量。
这里有几件事要注意。

首先,如果我们向上滚动以查看3怎么办?请记住,该池就像一个堆栈一样工作,因此,如果自上次看到3以来我们只进行了滚动操作,则ViewHolder 3将是最后一个放入池中的池,因此现在选择反弹在位置3。如果数据不变,我们实际上不需要进行任何重新绑定。您应该始终检查自己onBindViewHolder()是否确实需要更改此TextView或ImageView等,这是您不需要时的一种示例。

其次,请注意,滚动时池中始终只有一个项目(每个视图类型)!(当然,如果您有一个包含
n列的多列网格,那么池中将有n个项目。)通过方案2–5在池中最终出现的其他项目,在滚动过程中只会毫无用处。
现在,让我们看一下一个场景,相比之下,有很多项目进入池中:调用

notifyDataSetChanged()(或``notifyItemRangeChanged()范围很广):

![图片发布](https://miro.medium.com/max/1644/0*wywfZF1PM6PCtRoH.)
所有ViewHolders均无效,缓存不适合它们放置,它们都尝试进入池中。他们可能没有足够的空间,因此将收集一些不幸的垃圾,然后重新创建。与滚动相反,在这种情况下,您可能需要更大的缓冲池。大池有用的另一种情况是通过编程从一个位置跳到另一个位置

scrollToPosition()

那么我们如何选择池的最佳大小呢?
似乎最佳策略是在需要变大之前先扩展池,然后再缩小它。实现此目标的一种肮脏方法如下:
recyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
adapter.notifyDataSetChanged();
new Handler().post(new Runnable() {
    @Override    public void run() {
        recyclerView.getRecycledViewPool()                    
            .setMaxRecycledViews(0, 1);
    }
});

接下来看 ViewCacheExtension