RecyclerView-的复用机制,终于搞明白了

本文详细解析了RecyclerView的内部工作原理,特别是其复用机制。从Recycler类的结构开始,探讨了ViewCacheExtension的自定义缓存,接着在onLayout流程中,讲解了LayoutManager如何布局和填充ItemView,尤其是LinearLayoutManager.fill()方法的角色。关键在于Recycler.getViewForPosition(),它在预布局阶段从mChangedScrap获取ViewHolder,并在必要时缓存或回收视图。通过对scrapView和recycleViewHolderInternal的分析,揭示了RecyclerView如何高效地管理并复用ItemView。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;

final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();

RecycledViewPool mRecyclerPool;

private ViewCacheExtension mViewCacheExtension;

}

这些缓存集合可以分为 4 个级别,按优先级从高到底为:

*   一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder

1.  mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder

2.  mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder

*   二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder

*   三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。个人感觉这个拓展脱离了 Adapter.createViewHolder 使用的话会造成 View 创建和数据绑定和其它代码太分散,不利于维护,使用场景很少仅做了解

/*
* Note that, Recycler never sends Views to this method to be cached. It is developers

  • responsibility to decide whether they want to keep their Views in this custom cache or let
  • the default recycling policy handle it.
    */
    public abstract static class ViewCacheExtension {
    public abstract View getViewForPositionAndType(…);
    }

*   四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入 RecyclerViewPool 中。

*   按照 Type 来查找 ViewHolder

*   每个 Type 默认最多缓存 5 个

*   可以多个 RecyclerView 共享 RecycledViewPool

接下来我们看下这四级缓存是怎么工作的

## 复用

RecyclerView 作为一个 “平平无奇” 的 View,子 View 的排列和布局当然是从 onLayout 入手了,调用链:

RecyclerView.onLayout(…)
-> RecyclerView.dispatchLayout()
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 类型为 LayoutManager
-> LinearLayoutManager.onLayoutChildren(…) // 以 LinearLayoutManager 为例
-> LinearLayoutManager.fill(…) // The magic functions 😃 填充给定的布局,注释很自信的说这个方法很独立,稍微改动就能作为帮助类的一个公开方法,程序员的快乐就是这么朴实无华。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通过 Recycler 获取指定位置的 ItemView
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(…) // 终于找到你,还好没放弃~


可以看出最终调用 tryGetViewHolderForPositionByDeadline ,来看看这个方法是怎么拿到相应位置上的 ViewHolder :

ViewHolder tryGetViewHolderForPositionByDeadline(int position, …) {
if (mState.isPreLayout()) {
// 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
holder = getChangedScrapViewForPosition(position);
}

if (holder == null) {
    // 1) 分别从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
    //    这个 mHiddenViews 是用来做动画期间的复用
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}

if (holder == null) {
    final int type = mAdapter.getItemViewType(offsetPosition);
    // 2) 如果 Adapter 的 hasStableIds 方法返回为 true
    //    优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
    if (mAdapter.hasStableIds()) {
        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
    }
  
    if (holder == null && mViewCacheExtension != null) {
        // We are NOT sending the offsetPosition because LayoutManager does not know it.
        // 3) 从自定义缓存获取,别问,问就是别用
        View view = mViewCacheExtension
            getViewForPositionAndType(this, position, type);
        holder = getChildViewHolder(view);
    }
}

if (holder == null) {
    // 4) 从 RecycledViewPool 获取 ViewHolder
    holder = getRecycledViewPool().getRecycledView(type);
}

if (holder == null) {
    // 缓存全取过了,没有,那只好 create 一个咯
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
}

// This is very ugly but the only place we can grab this information
// 大半夜看到这句注释的时候笑出声!像极了我写出丑代码时的无奈。

// 在这后面有一些刷新 ViewHolder 信息的代码,放这很丑,但又只能放这,为了能走到这,前面有多少 if (holder == null)

}


分析完复用的部分,接下来再看一下 ViewHolder 存入缓存的部分

## 缓存

所谓的缓存,就是看一下是怎么样往前面提到的四级缓存添加数据的

*   mAttachedScrap 和 mChangedScrap

*   mCachedViews

*   ViewCacheExtension 前面说了,这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据

*   RecycledViewPool

## mAttachedScrap 和 mChangedScrap

如果调用了 Adapter 的 notifyXXX 方法,会重新回调到 LayoutManager 的onLayoutChildren 方法里面, 而在 onLayoutChildren 方法里面,会将屏幕上所有的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。

// 调用链
LinearLayoutManager.onLayoutChildren(…)
-> LayoutManager.detachAndScrapAttachedViews(recycler)
-> LayoutManager.scrapOrRecycleView(…, view)
-> Recycler.scrapView(view);

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 后面会讲,缓存到 mCacheViews 和 RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 缓存到 scrap
recycler.scrapView(view);
}
}

void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
mAttachedScrap.add(holder);
} else {
mChangedScrap.add(holder);
}
}


其实还有一种情况会调用到 scrapView , 从 mHiddenViews 获得一个 ViewHolder 的话(发生在支持动画的操作),会先将这个 ViewHolder 从 mHiddenViews 数组里面移除,然后调用:

Recycler.tryGetViewHolderForPositionByDeadline(…)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值