RecyclerView缓存机制讲解

一.简介

RecyclerView的回收复用机制内部实现都是由Recycler内部类实现。与ListView和GridView不同的是RecyclerView缓存的是ViewHolder。而ListView和GridView缓存的是View。

RecyclerView继承ViewGroup类。实现了ScrollingView接口和NestedScrollingChild2接口。也就是说其实RecyclerView其实是一个自定义的可以滑动的ViewGroup。

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

   ...

}

 

ListView缓存机制详解

https://blog.csdn.net/weixin_37730482/article/details/79351927

 

 

 

 

 

 

二.源码分析

Recycler类源码

public final class Recycler {

    //存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder
    final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList<>();

    //表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder。
    ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;

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

    private final List<RecyclerView.ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;

    int mViewCacheMax = DEFAULT_CACHE_SIZE;

    RecyclerView.RecycledViewPool mRecyclerPool;

    private RecyclerView.ViewCacheExtension mViewCacheExtension;

    static final int DEFAULT_CACHE_SIZE = 2;

        ......
}

由其源码可知,Recycler 是 RecyclerView 的内部类。并且 Recycler 的主要成员变量也都是用来缓存和复用 ViewHolder 的。

 

<1> mAttachedScrap集合和mChangedScrap集合

这两个集合对象都属于Scrap 缓存列表。是 RecyclerView 最先查找 ViewHolder 地方,是RecyclerView的第一层缓存。它跟 RecycledViewPool  或者 ViewCache 有很大的区别。两个集合只在布局阶段使用。其他时候它们是空的。布局完成之后,这两个缓存中的 viewHolder,会移到 mCacheView 或者 RecyclerViewPool 中。

 

 两个集合添加时机是scrapView方法

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
            throw new IllegalArgumentException("Called scrap view with an invalid view."
                    + " Invalid views cannot be reused from scrap, they should rebound from"
                    + " recycler pool." + exceptionLabel());
        }
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

而scrapView方法的调用时机在哪里呢?

StaggeredGridLayoutManager和LinearLayoutManager两个RecyclerView的LayoutManager中都会在各自的onLayoutChildren方法中调用detachAndScrapAttachedViews(recycler);方法。

以LinearLayoutManager为例

/**
 * {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {


   ...

   detachAndScrapAttachedViews(recycler);   

   ...


}
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
    final int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View v = getChildAt(i);
        scrapOrRecycleView(recycler, i, v);
    }
}


private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        if (DEBUG) {
            Log.d(TAG, "ignoring view " + viewHolder);
        }
        return;
    }
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);


        //调用Recycler类的scrapView方法
        recycler.scrapView(view);



        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

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

 

 

 

<2> mCachedViews 第二层缓存

用来缓存刚刚移出屏幕的缓存ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder。当其容量被充满同时又有新的数据添加的时候,会根据FIFO原则,把优先进入的缓存数据移出并放到下一级缓存中,然后再把新的数据添加进来。

如果使用GridLayoutManager建议设置为列的个数。

//声明
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();



//默认最大值
static final int DEFAULT_CACHE_SIZE = 2;

private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;

int mViewCacheMax = DEFAULT_CACHE_SIZE;


//修改最大值

/**
 * Set the maximum number of detached, valid views we should retain for later use.
 *
 * @param viewCount Number of views to keep before sending views to the shared pool
 */
public void setViewCacheSize(int viewCount) {
    mRequestedCacheMax = viewCount;
    updateViewCacheSize();
}

 

 

<3> RecycledViewPool  第三层缓存

RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。当第二层Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中。

   (1) 按照 Type 来查找 ViewHolder。

   (2) 每个 Type 默认最多缓存 5 个

   (3) 可以多个 RecyclerView 共享 RecycledViewPool。

 

 

<4>mCachedViews缓存和RecycledViewPool缓存 区别

(1) 从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder;
 

(2) 而RecycledViewPool是根据itemType获取的,如果没有重写getItemType(),itemType是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()。​​​​​​​

 

 

 

 

 

 

 

三.缓存优化实践

 

1.尽量使用 notifyItemXxx 方法进行细粒度的通知更新,而不是 notifyDatasetChanged。

 

2.局部刷新使用DiffUtil工具类。

 

详情

https://blog.csdn.net/weixin_37730482/article/details/72822870

 

https://blog.csdn.net/weixin_37730482/article/details/71173037

 

https://blog.csdn.net/weixin_37730482/article/details/79391171

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值