RecycleView的复用和优化

最近了解了一下RecycleView的缓存机制,做了一些记录,防止遗忘

一、RecyleView四级缓存

image

首先明确RecyecleView中缓存的对象是ViewHolder.

Recycler负责管理和缓存所有的ViewHolder。
RecycleView的缓存从上到下分为四层:scrap、cache、ViewCacheExtension、RecycleViewPool

1.1、 scrap

scrap 用来缓存正在显示的ViewHolder。

scrap 分为两个集合:mAttachedScrap 和 mChangedScrap。

  • mAttachedScrap 用于缓存正在显示的ViewHolder
  • mChangedScrap 用于缓存屏幕上发生变化的viewHolder,可能是数据发生了变化,也可能是ViewHolder类型发生了变化,mChangedScrap 中的ViewHolder会被移动到RecycledViewPool中。

当我们调用 notifyItemRangeChanged 方法的时候,会触发 requestLayout 方法,就会重新布局,重新布局的话,就会先将 viewHolder 放到 scrap 中(屏幕上变化的放入mChangedScrap 中,其余的放入mAttachedScrap 中),然后 fill 布局的时候,再从 mAttachedScrap 里面取出来直接使用。mChangedScrap 中的 viewHolder 会被移动到 RecycledViewPool 中,所以 mChangedScrap 对应的 item 需要从 pool 中取对应的 viewHolder,然后重新绑定。

image

View中的detach和remove

  • detach 在ViewGroup中的实现很简单,只是将当前View从ParentView的ChildView数组中移除,将当前View的mParent设置为null, 可以理解为轻量级的临时remove。
  • remove 代表真正的移除,不光从ChildView数组中移除,其他和View树各项联系也会被彻底斩断。

Recycled View中的Scrap View

  • Scrap View指的是在RecyclerView中,经历了detach操作的缓存。此类缓存是通过position匹配的,不需要重新bindView。
  • Recycled View指代的就是真正的移除操作remove后的缓存,取出时需重新bindView使用。

1.2、cached

数据结构mCachedViews,用于缓存从屏幕中移除,但是可能很快被再次显示的ViewHolder

  • 它是一个 ArrayList 类型,不区分 viewHolder 的类型
  • mCachedViews大小限制为2,但是你可以使用 setItemViewCacheSize()这个方法调整它的大小。

1.3、ViewCacheExtension

这个是需要自定义的,而且使用有很大的限制,所以不深入介绍了。

1.4、RecycledViewPool

RecycledViewPool 储存各个类型的 viewHolder 它缓存的是被恢复出厂设置的viewHolder,需要重新调用bind 绑定数据。

  • RecycledViewPool 是按照ItemViewType 存储ViewHolder的,每种ItemViewType最大数量为5
  • 可以通过 setMaxRecycledViews() 方法来设置每个类型储存的容量。
  • 针对RecycleView嵌套的场景,如一个纵向的RecycleView 嵌套横向的RecycleView ,可以使用 setRecycledViewPool() 方法,公用RecycledViewPool

RecycleView滑出屏幕时的ViewHolder的复用过程

image

滚出屏幕的View会优先保存到mCacheViews, 如果mCacheViews中保存满了,就会保存到RecyclerViewPool中。

  • 检查mCacheViews集合中是否还有空位,如果有空位,则直接放到mCacheViews集合
  • 如果没有的话就把mCacheViews集合中最前面的ViewHolder拿出来放到RecyclerViewPool中,然后再把最新的这个ViewHolder放到mCacheViews集合
  • 如果没有成功缓存到mCacheViews集合中,就直接放到RecyclerViewPool

二、Recycler 缓存加载流程

image
  • scrap负责缓存屏幕中正在显示的ViewHolder,命中缓存后直接使用,不需要create和Bind
  • 如果在 cache (mCachedViews)负责缓存刚刚移出屏幕,很可能被复用的ViewHolder。通过position获取,命中后不需create和bind
  • ViewCacheExtension google预留的一个空的缓存,暂不讨论
  • pool (RecycledViewPool )根据ViewType缓存ViewHolder, 用于缓存数据解绑后的ViewHolder,pool中命中的viewHolder 需要进行重新bind 进行数据绑定
  • 如果所有缓存中都没有命中 viewHolder,会重新调用createViewHolder 和 bindViewHolder

三、一些优化方法

3.1、 setHastFixedSize

当知道Adapter内Item的改变不会影响RecyclerView宽高的时候,可以设置为true让RecyclerView避免重新计算大小。

注意两点:

  • 当调用Adapter的增删改插方法,最后就会根据mHasFixedSize这个值来判断需要不需要requestLayout();
onItemRangeChanged(),

onItemRangeInserted(),

onItemRangeRemoved(),

onItemRangeMoved()

上面四个方法会调用triggerUpdateProcessor方法

    void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

会根据mHasFixedSize这个值来判断需要不需要requestLayout();

  • 当调用Adapter的notifyDataSetChanged() 最后调用了onChanged,调用了requestLayout(),会去重新测量宽高。
   public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;

            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

3.2、setHasStableIds

Adapter.setHasStablesId(true)开启固定ID

DemoAdapter  mAdapter=new DemoAdapter();
mAdapter.setHasStablesId(true);

在 Adapter 类中重写getItemId来给每个 Item 一个唯一的ID。

@Override
public long getItemId(int position){
    return items.get(position).getId();
}

setHasStableIds(true)之后,数据为发生变化情况下,滚动recycleView

ViewHolder会被缓存到mAttachedScrap中,复用时通过position 从mAttachedScrap直接取出显示,不需要重新createViewHolder、bindViewHolder

用空间换时间,
从而规避滑动recyelveView过程中出现的闪烁问题。

3.3、recycleView 图片列表快速刷新

recyleView 中显示图片列表,快速滑动容易出现卡顿。一个优化思路,可以设置在滑动过程中暂停正在加载的图片,滑动停止之后再恢复图片的加载。

recyceView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                 if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    Glide.with(requireActivity()).resumeRequests()
                } else {
                    Glide.with(requireActivity()).pauseRequests()
                }
            }
        })

参考文章:

https://www.jianshu.com/p/1d2213f303fc
https://blog.csdn.net/weixin_43130724/article/details/90068112
https://www.jianshu.com/p/4a2b18135447

https://zhuanlan.zhihu.com/p/80475040

https://www.jianshu.com/p/aeb9ccf6a5a4

图片闪烁问题分析:
https://www.jianshu.com/p/29352def27e6

你可能感兴趣的:(RecycleView的复用和优化)