Android—RecyclerView进阶(4)—复用机制及性能优化

我的CSDN: ListerCi
我的简书: 东方未曦

一、RecyclerView基本结构

RecyclerView的运行主要依赖于Adapter、LayoutManager和Recycler这三个类,其中Adapter负责与数据集交互,LayoutManager负责ItemView的布局,Recycler负责管理ViewHolder,其结构如下图。

RecyclerView结构.png

得益于RecyclerView设计时的解耦,ItemView的创建、绑定和复用对LayoutManager来说都是不可见的,LayoutManager只需要关心如何布局ItemView即可。当LayoutManager要布局数据集中的第i个Item时,它通过recycler.getViewForPosition(i)获取该ItemView,此时Recycler会先查找缓存中是否存在该ItemView,如果不存在就调用Adapter的onCreateViewHolder(...)新建一个。

而Recycler中缓存的ViewHolder也是LayoutManager放进去的,那LayoutManager什么时候将ItemView放入缓存中呢?主要分为两种情况。
① 数据集发生变化。此时LayoutManager调用detachAndScrapAttachedViews()回收当前屏幕上的所有ItemView,再根据新的数据进行布局。
② ItemView滑出可视区域。此时LayoutManager会将该ItemView放入Recycler的缓存中。缓存为FIFO结构,当有新的ItemView被放入缓存时,旧的ItemView会被移出。

二、回收复用机制原理

Recycler中有多个缓存池,其定义如下。

// mAttachedScrap在重新layout时使用
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
// mChangedScrap用于动画
ArrayList<ViewHolder> mChangedScrap = null;
// mCachedViews和RecycledViewPool用于滑动时的缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
RecycledViewPool mRecyclerPool;
// 用户自定义缓存,一般不用
private ViewCacheExtension mViewCacheExtension;

当ItemView因为不同的原因被回收时,它们也会进入不同的缓存池,最常见的场景就是数据集发生变化或Item滑出可视区域,下面根据Item回收的场景来看各个缓存池的使用。

场景1—数据集发生变化

mAttachedScrap被称为一级缓存,在重新layout时使用,主要是数据集发生变化的场景。被mAttachedScrap缓存的ItemView大部分会马上得到复用。当LayoutManager通过recycler.getViewForPosition(i)寻找ItemView时会优先去mAttachedScrap中查找。

当数据集发生变化时,LayoutManager的onLayoutChildren(...)方法会被调用,该方法先通过detachAndScrapAttachedViews(Recycler recycler)将当前屏幕上的所有ItemView缓存至mAttachedScrap,之后再重新布局。

举个栗子,假设初始有5个Item,remove掉Data1,此时数据集发生了变化,需要重新布局。LayoutManager的onLayoutChildren(...)方法被调用,初始的5个ItemView都被添加到了mAttachedScrap中,随后重新布局时,有4个ItemView得到了复用。

数据集变化示例.png

之前提到,Recycler以ItemView在数据集中的position作为唯一定位,当需要展示数据集中第i项时,LayoutManager通过recycler.getViewForPosition(i)获取对应的ItemView。来看一下Recycler在mAttachedScrap中查找时,是怎么判断缓存中的ItemView是否就是当前所需要的。

    final int scrapCount = mAttachedScrap.size();
    for (int i = 0; i < scrapCount; i++) {
   
   
        final ViewHolder holder = mAttachedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
   
   
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }

主要的判断条件就是holder.getLayoutPosition() == position,用于判断ViewHolder Layout时的位置是否与数据集中的position相等。ViewHolder的getLayoutPosition()方法如下。

    public final int getLayoutPosition() {
   
   
        return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
    }

这个有个要注意的地方:数据集发生变化后,ViewHolder原本的LayoutPosition很有可能过期,就像上面例子中,Remove掉Data1后,Data2的LayoutPosition应该从2变为1,所以我们断定,RecyclerView一定在某个地方对ViewHolder的位置信息进行了更新,我们来看下RecyclerView是怎么做的。

以上面的示例作为说明,将Data1从数据集移除后,RecyclerView开始重新布局,在dispatchLayoutStep1()中的processAdapterUpdatesAndSetAnimationFlags()方法中更新ViewHolder的position信息。假设没有设置动画,则执行mAdapterHelper.consumeUpdatesInOnePass()

    private void processAdapterUpdatesAndSetAnimationFlags() {
   
   
        // ......
        // simple animations are a subset of advanced animations (which will cause a
        // pre-layout step)
        // If layout supports predictive animations, pre-process to decide if we want to run them
        if (predictiveItemAnimationsEnabled()) {
   
   
            mAdapterHelper.preProcess();
        } else {
   
   
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        // ......
    }

mAdapterHelper.consumeUpdatesInOnePass()中判断数据集发生的变化,通过AdapterHelper.Callback回调通知RecyclerView遍历ViewHolder更新position

    void consumeUpdatesInOnePass() {
   
   
        // ......
        for (int i = 0; i < count; i++) {
   
   
            UpdateOp op = mPendingUpdates.get(i);
            switch (op.cmd) {
   
   
                case UpdateOp.ADD:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.REMOVE:
                    mCallback.onDispatchSecondPass(op);<
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值