RecyclerView源码解析(三):RecyclerView的缓存机制

本文围绕RecyclerView的缓存机制展开源码解析。先介绍ViewHolder的不同状态及相关标志位,接着以LinearLayoutManager的next方法为切入点,分析Recycler类结构。重点剖析tryGetViewHolderForPositionByDeadline方法,阐述从不同缓存区获取ViewHolder的过程,最后总结获取方式及回收池限制内存的机制。

RecyclerView源码解析(三):RecyclerView的缓存机制

一只猫在公交车站等待,动漫卡通

导言

上两篇文章我们结合LinearLayoutManager对RecyclerView整体是如何绘制的有了大致的了解,不过RecyclerView的重头戏并不是简单显示列表,而是它通过缓存机制实现的高性能,本篇文章我们继续对源码进行分析来解析RecyclerView最重要的缓存机制。

关于viewHolder的状态

在RecyclerView中,ViewHolder作为视图的持有者会处于各种不同的状态,下面是可能出现的状态:

  1. Attached(附加):ViewHolder 附加到 RecyclerView 中的父视图,它是可见的并可以响应用户输入事件。在这个状态下,ViewHolder 可以被看到,而且可以与用户进行交互。
  2. Detached(分离):ViewHolder 不再附加到 RecyclerView 中的父视图,它不再可见。在这个状态下,ViewHolder 可能已经超出了屏幕可见范围,或者它已经从 RecyclerView 中分离,但尚未被回收。
  3. Recycled(回收):ViewHolder 被回收,通常是因为它不再可见或不再需要。在这个状态下,ViewHolder 的视图资源被释放,但ViewHolder 对象本身可能会被保留在 RecyclerView 的回收池中,以便稍后重用。
  4. Invalid(无效):ViewHolder 被标记为无效,通常表示该 ViewHolder 对应的数据项已经发生了变化。在这个状态下,ViewHolder 可能需要重新绑定以反映最新的数据。
  5. Removed(移除):ViewHolder 被标记为已移除,通常表示该 ViewHolder 对应的数据项已从适配器数据集中删除。这个状态通常会伴随着一些动画效果,直到 ViewHolder 完全离开屏幕。
  6. Update(更新):ViewHolder 被标记为需要更新,通常表示 ViewHolder 所对应的数据发生了部分更改,需要进行局部更新而不是完全的重新绑定。

而源文件中也有各种各样的FLAG标志位的定义和注释,这里介绍一些经常会出现的标志位:

  • FLAG_BOUND标志: 表示 ViewHolder 已经绑定到了一个数据位置上,ViewHolder 的 mPositionmItemIdmItemViewType 都已经设置为有效值。

  • FLAG_UPDATE标志:表示 ViewHolder 的视图所反映的数据已经过时,需要由适配器重新绑定。ViewHolder 的 mPositionmItemId 仍然保持有效,但是需要重新执行数据绑定操作以更新视图的内容。(区别于完全重新绑定,viewType还是没变)

  • FLAG_INVALID标志: 表示 ViewHolder 的数据无效。ViewHolder 的 mPositionmItemId 不再可信,而且可能不再匹配项视图的类型。这意味着 ViewHolder 需要完全重新绑定到不同的数据项,通常是因为与之前的数据不再匹配。这个标志位用于通知 RecyclerView,特定的 ViewHolder 需要进行完全的重新绑定。

  • FLAG_REMOVED标志:表示 ViewHolder 对应的数据项曾经从数据集中移除。但是,它的视图可能仍然可以用于执行一些操作,例如进行退出动画。这个标志位用于标记 ViewHolder 对应的数据项已被移除,但 RecyclerView 可能仍然需要对其执行一些动画等操作。

  • FLAG_NOT_RECYCLABLE标志:表示 ViewHolder 不应该被回收。通常,RecyclerView 会尝试回收不再需要的 ViewHolder 以便节省内存和性能。然而,在某些情况下,你可能希望保持 ViewHolder 不被回收,比如在执行动画时需要保持视图。

  • FLAG_RETURNED_FROM_SCRAP标志:表示这个 ViewHolder 是从废弃状态返回的。当一个 ViewHolder 从废弃状态返回时,它会保留在废弃列表中,直到布局过程结束,然后如果没有重新添加到 RecyclerView 中,它会被 RecyclerView 回收。

  • FLAG_IGNORE标志: 表示这个 ViewHolder 被布局管理器完全管理。它不会被废弃、回收或移除,除非布局管理器被替换。这个 ViewHolder 对布局管理器来说仍然是完全可见的。通常情况下,这种标志用于标识 RecyclerView 中的某些视图应该由布局管理器自己处理,而不被 RecyclerView 的回收和复用系统管理。

  • FLAG_TMP_DETACHED标志:表示当视图从父视图中分离(detached)时,设置了这个标志,以便在需要将其移除或添加回来时采取正确的操作。这个标志通常用于处理视图的临时分离状态。

源码解析

从next方法开始

解析缓存机制的话我们还是以LinearLayoutManager为例,从它的next方法作为切入点进行分析,因为在布局是LinearLayoutManager是通过next方法来获取下一个布局的视图的,来看next方法:

View next(RecyclerView.Recycler recycler) {
   
   
    if (mScrapList != null) {
   
   
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

这个方法张红出现了一个mScrapList变量,根据注释:

在RecyclerView中,LayoutManager(LLM)可以设置一个mScrapList(或称为"废弃列表")来存储特定的ViewHolder对象。当LayoutManager需要布局特定的视图时,它将这些视图的ViewHolder对象添加到mScrapList中。然后,在LayoutState中,当需要获取下一个要布局的视图时,会首先尝试从mScrapList中获取ViewHolder对象,如果找到则返回,否则返回null。

这个机制的目的是提高性能,因为LayoutManager在进行布局时可能需要多次获取ViewHolder对象,而不必每次都从Recycler中获取。通过使用mScrapList,LayoutManager可以在内部管理一组特定的ViewHolder对象,以提高布局效率。

需要注意的是,mScrapList是一个临时的列表,用于特定的布局操作,它不会永久存储ViewHolder对象。这些ViewHolder对象仍然受Recycler的管理,并可以被回收和重用。

我们可以把这个mScrapList看做是一个由LinearLayoutManager管理的缓存列表,不过它不会永久存储ViewHolder对象。这些ViewHolder对象仍然受Recycler的管理,并可以被回收和重用。RecyclerView实现缓存主要也不是依赖于LayoutManager类,而是通过RecyclerView自身的Recycler回收池,就比如之后就调用了recycler.getViewForPosition(mCurrentPosition),这就是通过Recycler来获取view的。我们首先大致看一看Recycler类的大致结构。

Recycler类

这个类也有必要的注释:

在这里插入图片描述

Recycler(回收器)是负责管理被丢弃或分离的视图以供重用的组件。

"被丢弃"的视图是指仍然附加到其父RecyclerView但已被标记为要删除或重用的视图。

RecyclerView.LayoutManager通常通过Recycler来获取适配器的数据集中特定位置或项ID的视图。如果要重用的视图被认为是 “脏的”,则适配器将被要求重新绑定它。如果不是脏视图,LayoutManager可以快速地重新使用它,而无需进一步的工作。未请求布局的干净视图可能会被LayoutManager重新定位,而无需重新测量。Recycler的主要作用是协助RecyclerView的LayoutManager有效地管理和重用视图,以提高性能和滚动的流畅性。

如果要对数据进行缓存的话,显然需要一些成员变量来作为容器,我们正好可以在Recycler类中发现这些成员变量:

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//一级缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//二级缓存
RecycledViewPool mRecyclerPool;//四级缓存
private ViewCacheExtension mViewCacheExtension;//三级缓存

主要是通过这四级缓存来构建起来了整个RecyclerView的缓存机制。

何处真正获取到View对象

tryGetViewHolderForPositionByDeadline方法的第一部分

前面说到,next方法中将通过recycler.getViewForPosition(mCurrentPosition)方法来试图获取到View,而这个方法实际上又会跳转到别的方法中,最终会调用到RecyclerView的tryGetViewHolderForPositionByDeadline方法中,同样的,这个方法也有注释:

在这里插入图片描述

这段注释可以说是很重要,因为它清楚地指出了view的获取来源:

这段代码描述了RecyclerView中的一个方法,用于获取给定位置的ViewHolder。ViewHolder是用来显示RecyclerView中的每个项的视图容器。这个方法的作用是尝试从不同的来源获取ViewHolder,包括Recycler的临时存储(scrap)、缓存(cache)、RecycledViewPool(用于存储被回收的视图)、或者直接创建ViewHolder。

这就指明了常用的三个缓存区,分别就是mAttachedScrapmCachedViewsmRecyclerPool.接下来分析这个方法,把这个方法拆解成几个步骤:

boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
   
   
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}

这一步将根据当前是否在进行预布局来决定是否要通过 getChangedScrapViewForPosition(position)方法来获得ViewHolder。这个是否正在进行预布局是在哪里设置的呢?在RecyclerView中这个标志位主要是根据另一个标志位mRunPredictiveAnimations来决定的,当该标志位为true时才需要进行预布局。而这个mRunPredictiveAnimations又需要mRunSimpleAnimations标志位来决定,总的来说如果有动画的话才会进行预布局,默认状态下是为false的。

// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
   
   
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
   
   
        if (!validateViewHolderForOffsetPosition(holder)) {
   
   
            // recycle holder (and unscrap if relevant) since it can't be used
            if (!dryRun) {
   
   
                // we would like to recycle this but need to make sure it is not used by
                // animation logic etc.
                holder.addFlags(ViewHolder.FLAG_INVALID);
                if (holder.isScrap()) {
   
   
                    removeDetachedView(holder.itemView, false);
                    holder
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值