Android——RecyclerView缓存机制

Posted 虞美人体重90

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——RecyclerView缓存机制相关的知识,希望对你有一定的参考价值。

RecyclerView是一种高度可定制的View控件,它可以用于显示大量的数据集合,用一种更有效的方式来管理数据的展示和滚动。

RecyclerView之所以那么高效有很大程度上归功于它的缓存机制。

一.使用步骤:

1.添加依赖

implementation \'androidx.recyclerview:recyclerview:1.1.0\' //recyclerview布局

2.layout文件中定义RecyclerView

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_ android:layout_ />

<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_ android:layout_ />

3.配置布局管理器

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

4.创建adapter

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> 

    private List<String> mData;

    public MyAdapter(List<String> data) 
        mData = data;
    

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
        return new ViewHolder(view);
    

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) 
        holder.mTextView.setText(mData.get(position));
    

    @Override
    public int getItemCount() 
        return mData.size();
    

    public static class ViewHolder extends RecyclerView.ViewHolder 
        private TextView mTextView;

        public ViewHolder(View itemView) 
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.text_view);
        
    

5.为RecyclerView设置adapter

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
MyAdapter adapter = new MyAdapter(data);
recyclerView.setAdapter(adapter);

 

二.缓存机制

RecyclerView把列表视图展示出来的过程主要分为三步:

  1.createViewHolder:创建条目对应的视图

   2.bindViewHolder:为条目视图绑定数据

  3.renderViewHolder:将绑定好数据的条目视图展现出来

其中createViewHolder耗时最长,因此recyclerView设计了一套缓存机制来提高列表视图的性能。

①一级

缓存

当列表处于屏幕内时,屏幕中显示的ViewHolder会缓存在mAttachedScrap、mChangedScrap中 :

mChangedScrap 表示数据已经改变的ViewHolder列表
mAttachedScrap 表示未与RecyclerView分离的ViewHolder列表

该级缓存主要用于缓存出现在屏幕内的item,当我们通过notifyItemRemoved(),notifyItemChanged()通知item发生变化的时候,通过mAttachedScrap缓存没有发生变化的ViewHolder,其他的则由mChangedScrap缓存,添加itemView的时候快速从里面取出,完成局部刷新。源码如下:

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);
    

源码分析:

对于页面中显示的Item,当调用 LayoutManager 类的 onLayoutChildren() 方法对views进行布局,这时会将RecyclerView上的items全部暂存到一个 ArrayList 集合,这里的数据是没有做修改的,所以不用重新绑定 Adapter。而如果其他情况比如调用了 notifyItemChanged()notifyItemRangeChanged() 来通知数据发生了更新,数据或位置发生改变,那么该ViewHolder会被缓存到mChangedScrap中,这里存储的是发生了变化的ViewHolder,所以要重新走Adapter的绑定方法。

图解:

 图中的itemB删除掉,然后itemC,itemD依次移动上来,这里itemA和itemB前后参数没有发生变化(虽然itemB被移除了,但移除的时候它还是有效的,会被打上REMOVED标签,表示它是要删除的),所以他们两个存储到mAttachedscrap(),而itemC和itemD的位置发生了改变,所以他俩要存到mChangedScrap()中去。总结来说,删除itemB时,ABCD都会进入Scrap缓存,删除后,会从Scrap中将ACD取出,A的位置和数据都没有发生变化,CD的位置发生了变化但数据还是原封不动。

 

文章来源:https://blog.csdn.net/m0_51276753/article/details/125667231

复用

ViewHolder的复用是有顺序的,首先会判断是否预布局。

如果是就从一级缓存中的mChangedScrap()中获取。

如果没获取到就去mAttachScrap()和二级缓存中找。

而一级缓存之所以说轻量,首先是因为它只针对当前页面显示的这些item,其次是因为它用完就会清空缓存,不占空间,效率也快。

所以通知数据更新我们推荐使用notifyItemChanged(),实现局部刷新,用的是一级缓存来实现复用。而如果我们调用notifyDataChanged()来通知更新,会使数据全部进行刷新,不会走Scrap,性能低下。

②二级

缓存

当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews列表 ,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。

当一个viewHolder从屏幕中消失时,会把它先放进mCachedViews中,这个时候,其数据(position等)、状态均未丢失,当列表滑动,需要该position的viewHolder展示时,会先从mCachedViews中找该实例,如果找到了,那就拿出来进行展示。mCachedViews是一个缓存列表,用于移出屏幕表项的回收和复用,不会清空数据。

 复用 查找顺序:先判断是否为预布局,如果是则从一级缓存中的mChangedScrap()中获取。如果不是,则进入一级缓存的mAttachScrap()中查找,mAttachScrap()没有就进入二级缓存mCachedViews列表查找。

// Search in our first-level recycled view cache. 官方说这里是第一级,但在我们日常使用中还是称他为第二级缓存
// 查找过程
// 这是根据position来取
final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be // retrieved via getScrapOrCachedViewForId // 这里要对索引进行判断,只有当位置对得上才能拿来复用, // 这也就意味着从mCatchedViews中取出的ViewHolder只能复用到指定的位置。 if (!holder.isInvalid() && holder.getLayoutPosition() == position && !holder.isAttachedToTransitionOverlay()) // 如果不在容量范围内,就把ViewHolder丢出去,丢到缓存池中。 if (!dryRun) mCachedViews.remove(i); if (DEBUG) Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position + ") found match in cache: " + holder); return holder;

mAttachedScrapp和mCachedViews都是需要进行索引判断,也就是说从这两个缓存中取出的ViewHolder只能复用到指定的位置。mCachedViews只能缓存屏幕外它容量大小的ViewHolder,超出容量的部分会被移除,丢到缓存池中。

默认大小为2,可通过Recyclerview.setItemViewCacheSize()动态设置。

③三级缓存

可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。

如果我们自定义了一个缓存并且前面的一二级缓存没有找到ViewHolder,系统就会从我们自定义的这个缓存里去找ViewHolder。

④四级

缓存

ViewHolder首先会缓存在 mCachedViews 中,当超过了个数(比如默认为2), 就会按照先入先出原则被移出mCachedViews列表,添加到 RecycledViewPool 中。

RecycledViewPool 会根据每个ViewType把ViewHolder分别存储在不同的列表中,一个viewType对应一个mRecyclerPool,每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 个ViewHolder。

此时其数据、状态会被重置(position置为-1,bindViewHolder中的操作均失效)。

需要展示在屏幕中的viewholder会先在mCachedViews中找position对应的viewHolder,如果未找到,那么就去对应viewType的mRecyclerPool中找该对象。

RecycledViewPool源码:

public static class RecycledViewPool 
    //同类ViewHolder缓存个数上限为5
    private static final int DEFAULT_MAX_SCRAP = 5;
    // Tracks both pooled holders, as well as create/bind timing metadata for the given type.
    // 回收池中存放单个类型ViewHolder的容器
    static class ScrapData 
        //同类ViewHolder存储在ArrayList中
        ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;
    
    //回收池中存放所有类型ViewHolder的容器
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    ...
    //ViewHolder入池按viewType分类入池,一个类型的ViewType存放在一个ScrapData中
    public void putRecycledView(ViewHolder scrap) 
        final int viewType = scrap.getItemViewType();
        final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
        //如果超限了,则放弃入池
        if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) 
            return;
        
        if (DEBUG && scrapHeap.contains(scrap)) 
            throw new IllegalArgumentException("this scrap item already exists");
        
        scrap.resetInternal();
        //回收时,ViewHolder从列表尾部插入
        scrapHeap.add(scrap);
    
    
    //从回收池中获取ViewHolder对象
    public ViewHolder getRecycledView(int viewType) 
        // 获取到viewType
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) 
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            //复用时,从列表尾部获取ViewHolder(优先复用刚入池的ViewHoler)
            return scrapHeap.remove(scrapHeap.size() - 1);
        
        return null;
    

RecycledViewPool中的ViewHolder存储在SparseArray中,并且按viewType分类存储,同一类型的ViewHolder存放在一个ArrayList中。虽然没有了对索引的判断,但是从mRecyclerPool中取出的ViewHolder只能复用于相同viewType的表项。

复用

holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) 
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST)
invalidateDisplayListInt(holder);

// 这里是getRecyclerViewPool(),主要作用就是new了一个RecyclerViewPool对象出来。 // 然后再根据type来从缓存池中获取对应类型的ViewHolder。 RecycledViewPool getRecycledViewPool() if (mRecyclerPool == null) mRecyclerPool = new RecycledViewPool(); return mRecyclerPool;

当ViewHolder从缓存池取出来后,判断holder是否为空,如果不为空,说明holder从缓存池中取出来了。那么就执行 holder.resetInternal(),该方法是将数据,状态重置。

把该对象拿出来,重新走一遍bindViewHolder、renderViewHolder,进行展示。

如果holder为空,则未找到,那么就走create、bind、renderViewHolder的完整流程。

深入理解Android RecyclerView的缓存机制

我们知道,RecyclerView在大量数据时依然可以丝滑般顺畅的滑动,那它究竟是怎么实现的呢,而RecyclerView之所以好用得益于它优秀的缓存机制。

我们知道,RecyclerView本身是一个ViewGroup,因此在滑动时就避免不了添加或移除子View(子View通过RecyclerView#Adapter中的onCreateViewHolder创建),如果每次使用子View都要去重新创建,肯定会影响滑动的流畅性,所以RecyclerView通过Recycler来缓存的是ViewHolder(内部包含子View),这样在滑动时可以复用子View,某些条件下还可以复用子View绑定的数据。所以本质上来说,RecyclerView之所以能够实现顺畅的滑动效果,是因为缓存机制,因为缓存减少了重复绘制View和绑定数据的时间,从而提高了滑动时的性能。

一、缓存

1.1、四级缓存

Recycler缓存ViewHolder对象有4个等级,优先级从高到底依次为:

  • mAttachedScrap:缓存屏幕中可见范围的ViewHolder;
  • mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,默认最大2个;
  • ViewCacheExtension:自定义实现的缓存;
  • RecycledViewPool :ViewHolder缓存池,可以支持不同的ViewType;

1.1.1 mAttachedScrap

mAttachedScrap存储的是当前屏幕中的

以上是关于Android——RecyclerView缓存机制的主要内容,如果未能解决你的问题,请参考以下文章

android 浅析RecyclerView回收复用机制及实战(仿探探效果)

android进阶篇02RecyclerView回收复用机制源码解析

RecyclerView详解一,使用及缓存机制

Android:ListView 和RecyclerView区别

Android 开发面试必问知识点 RecyclerView

RecyclerView缓存机制