Android从源码分析RecyclerView四级缓存复用机制一(缓存ViewHolder)

RecyclerView相比较ListView先说多了多布局和缓存,目前已经在Android列表中大量普及使用,面试中也经常问到,所以对于RecyclerView的四级缓存机制也叫复用回收机制的分析很有必要(这部分很重要请@全村人来听)。 在这里插入图片描述

先说一下结论RecyclerView的四级缓存分别为:

  1. mChangeScrap与 mAttachedScrap 用来缓存还在屏幕内的 ViewHolder
  2. mCachedViews 用来缓存移除屏幕之外的 ViewHolder
  3. mViewCacheExtension 开发给用户的自定义扩展缓存,需要用户自己 管理 View 的创建和缓存
  4. RecycledViewPool ViewHolder 缓存池

本文将从从以下问题中逐步分析源码并寻找答案并验证结论:

1.RecyclerView回收(缓存)什么?复用什么?

答:回收和复用的都是ViewHolder,RecyclerView 的复用的主要方法tryGetViewHolderForPositionByDeadline()返回的是ViewHolder。同时几个回收(缓存)的主要方法返回的也是ViewHolder。

2.RecyclerView回收(缓存)到哪里去?从哪里获得复用?

1.先来看一下ViewHolder最终缓存地方RecyclerView.Recycler类中,本文的源码分析基于最新的AndroidX的RecyclerView(和以前的RecyclerView可能有点不一样,但是主要流程都是一致的)

 public final class Recycler {
        //一级缓存中用来存储屏幕中显示的ViewHolder
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;
        //二级缓存中用来存储屏幕外的ViewHolder
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
        //暂可忽略 mAttachedScrap的不可变视图
        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
        
        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        //mCachedViews屏幕外缓存的存储上限默认为DEFAULT_CACHE_SIZE也就是2,可变
        int mViewCacheMax = DEFAULT_CACHE_SIZE;
        //四级缓存当屏幕外缓存的大小大于2,便放入mRecyclerPool中缓存
        RecycledViewPool mRecyclerPool;
        //三级缓存自定义缓存,自己定义的缓存规则
        private ViewCacheExtension mViewCacheExtension;
        //默认屏幕外缓存大小
        static final int DEFAULT_CACHE_SIZE = 2;
        //... 

2.再看一下主要的方法调用流程,从RecyclerView的onMeasure方法开始一直到三个存储的地方一级,二级和四级缓存,别问为啥没有mViewCacheExtension,问就是这个你需要自己去存 在这里插入图片描述

3.源码分析,本文对重点方法和重点代码进行分析(流程最好自己去跟一下)。

/**
* 5.RecyclerView.scrapOrRecycleView
*/
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
   final ViewHolder viewHolder = getChildViewHolderInt(view);
      //只展示重点代码...
      if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
         removeViewAt(index);
         //这里调用到二级和四级缓存
         recycler.recycleViewHolderInternal(viewHolder);
      } else {
         detachViewAt(index);
         //这里调用到一级缓存
         recycler.scrapView(view);
         mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
      }
} 

① 分析一级缓存

/**
* 13.RecyclerView.scrapView
*/
void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                //...
                holder.setScrapContainer(this, false);
                //缓存adapter其他notify系列方法(包括notifyDataSetChanged)被移除的ViewHolder
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                //缓存adapter的notifyItemRangeChanged被移除的ViewHolder
                mChangedScrap.add(holder);
            }
        } 

② 分析二级缓存

/**
* 6.RecyclerView.recycleViewHolderInternal
*/
void recycleViewHolderInternal(ViewHolder holder) {
    //...一系列是否需要二级回收的判断
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    //判断mCachedViews的大小是否大于2
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        //重点分析一
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    //...计算targetCacheIndex的下标 让mCachedViews满足队列先进先出原则
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    //...如果二级缓存没有存储则添加到四级缓存
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
     } else {
         //...
     }
     //...
}
/**
* 重点分析一:7.RecyclerView.recycleViewHolderInternal
* 作用:如果mCachedViews的大小大于2则内部调用addViewHolderToRecycledViewPool方法添加到RecycledViewPool中 
*/        
void recycleCachedViewAt(int cachedViewIndex) {
    
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    //Viewholder存储到四级缓存
    addViewHolderToRecycledViewPool(viewHolder, true);
    //Viewholder在四级缓存存储后移除mCachedViews中对应的Viewholder
    mCachedViews.remove(cachedViewIndex);
} 

③分析四级缓存

/**
* 8.RecyclerView.recycleViewHolderInternal
*/
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
     clearNestedRecyclerViewIfNotNested(holder);
     //...
     if (dispatchRecycled) {
         dispatchViewRecycled(holder);
     }
     holder.mOwnerRecyclerView = null;
     //存储到RecycledViewPool中
     getRecycledViewPool().putRecycledView(holder);
}
/**
* 9.RecyclerView.recycleViewHolderInternal
*/
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    //每个类型 viewType 最多只能缓存5个viewHolder
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
         return;
    }
    //...
    scrap.resetInternal();
    //将viewHolder添加到RecycledViewPool.ScrapData.scrapHeap中
    scrapHeap.add(scrap);
} 

④ mCachedViews存储满了后(默认是2个)后,存储到RecycledViewPool中 在这里插入图片描述 ⑤ 最后:三种缓存最终都是存储到ArrayList中,(Bugly博客偷一张图😆 ) 在这里插入图片描述

关于RecyclerView四级缓存复用机制二(复用ViewHolder)后面会分析,如果觉得这篇文章有用,欢迎点赞加收藏✨。

相关参考文章: RecyclerView源码解析(三)——深度解析缓存机制

本文转自 https://juejin.cn/post/6946100631669047333,如有侵权,请联系删除。

### 回答1: 在使用 Android 的 NestedScrollView 嵌套 RecyclerView 的过程中,出现了数据无法复用缓存的问题,这是因为 RecyclerView复用机制并不适用于 NestedScrollView 中的嵌套情况。 NestedScrollView 中包含的 RecyclerView 实际上是作为一个子View 的形式出现的,而每一个子View 的布局和数据都是不同的,所以 RecyclerView 无法反复利用之前已经使用过的缓存。这会导致在滑动过程中不断创建新的ViewHolder,从而降低应用程序的性能。 为了解决这个问题,可以将 RecyclerView 操作放到调用 NestedScrollView 的 onScrollChanged() 方法里面,以确保它的缓存机制被正确使用。另外,为RecyclerView设置setHasFixedSize(true)属性可以使性能有一些提升。 最好的解决方案是使用单个 RecyclerView 以及多个 Adapter 对其进行管理。这种方法可以让您更好地控制与管理 RecyclerView 的数据,在保证 NestedScrollView 能够适当地管理视图树的同时,确保 RecyclerView 没有内存和性能问题。 ### 回答2: Android中,RecyclerView是一个非常高效的列表控件,它可以使用ViewHolder机制对视图进行缓存,减少频繁更新视图的开销,但是在使用NestedScrollView嵌套RecyclerView时,却会出现数据无法复用缓存的问题。 这是因为NestedScrollView默认会将RecyclerView的所有子视图都展开,导致RecyclerView中所有的子视图都处于可见状态,无法被缓存。因此,当列表滑动时,RecyclerView会重新实例化缓存ViewHolder,以展示新的子视图。这个过程会消耗大量的内存和CPU,导致RecyclerView变得非常缓慢,甚至会导致OOM崩溃等问题。 为了解决这个问题,我们需要使用一些技巧来保证RecyclerView的子视图只有在需要的时候才被展开。一种解决方案是通过设置NestedScrollView的fillViewport属性为false,使NestedScrollView不将RecyclerView的所有子视图都展开。这样做可以让RecyclerView正确地使用ViewHolder机制,但是在滑动过程中,子视图的高度会频繁变化,导致列表的抖动,用户体验也会受到一定的影响。 另一种解决方案是使用RecyclerView的setRecycledViewPool()方法,为RecyclerView缓存一个视图池,这个视图池可以在RecyclerView的所有嵌套层级间共享,让所有子视图都可以被重复使用。这个方案的优点是可以减少在滑动过程中子视图高度变化带来的问题,但是需要在代码中进行额外的编写。 总之,NestedScrollView嵌套RecyclerView在使用时要注意子视图的缓存问题,需要通过调整布局属性或使用RecyclerView的视图池等技巧来解决。 ### 回答3: Android的嵌套滑动规范是在Android 5.0(API level 21)中引入的,用于使父View和子View之间的滑动效果更加协调。而NestedScrollViewRecyclerView都是Android中常用的滑动控件,但是在嵌套使用的过程中,RecyclerView的数据无法复用缓存,这是为什么呢? 首先,我们需要了解RecyclerView缓存机制RecyclerView使用三种缓存机制:首先是ViewHolder缓存,它可以在滑动时快速重新绑定已经存在的ViewHolder对象;其次是View缓存,它缓存了滑出屏幕的View,可以加速滑动时的UI响应;最后是Bitmap缓存,它用于缓存RecyclerView中的图像。 然而,在嵌套使用NestedScrollViewRecyclerView时,RecyclerView缓存机制无法发挥作用。这是因为NestedScrollView将滑动事件先处理,然后再将滑动事件交给RecyclerView处理,这会重新调用RecyclerView的Adapter中的getView()方法,导致RecyclerView中的缓存被清空。因此,RecyclerView的数据无法复用缓存,会造成性能上的损失。 为了解决这个问题,可以采取以下措施: 1.不要嵌套滑动控件:尽量避免使用NestedScrollViewRecyclerView等嵌套滑动控件。 2.使用LayoutManager:使用LayoutManager可以缓存RecyclerViewViewHolder对象,加速滑动过程中的UI响应。 3.自定义LayoutManager:自定义LayoutManager可以对滑动速度、滑动方向等进行优化,提高RecyclerView的性能。 4.使用分组显示:对RecyclerView的数据进行分组,将分组内的数据合并成一张图片,然后将图片缓存起来,这样可以减少RecyclerView的刷新次数。 总的来说,NestedScrollViewRecyclerView的嵌套使用会导致RecyclerView缓存无法复用,降低RecyclerView的性能,因此需要采取一些措施来解决这个问题。使用LayoutManager、自定义LayoutManager、分组显示等方法可以优化RecyclerView的性能,提高用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值