前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详细聊聊 RecyclerView 缓存机制

详细聊聊 RecyclerView 缓存机制

作者头像
字节流动
发布2021-04-23 12:56:01
2.2K0
发布2021-04-23 12:56:01
举报
文章被收录于专栏:字节流动

前言

本文主要通过如下几个方面讲解RecyclerView缓存机制:

  1. ViewHolder
  2. 缓存架构
  3. Recycler.recycleViewHolderInternal(ViewHolder holder)
  4. Recycler.tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)

1. ViewHolder

1.1 作用

ViewHolder是对RecyclerView上的ItemView的封装,它是RecyclerView缓存的载体。它封装了以下属性:

  1. View itemView:对应RecyclerView的子View
  2. int mPosition:View当前对应数据在数据源中的位置
  3. int mOldPosition:View上次绑定的数据在数据源中的位置
  4. long mItemId:可以判断ViewHolder是否需要重新绑定数据
  5. int mItemViewType:itemView对应的类型
  6. int mPreLayoutPosition:在预布局阶段ViewHolder对应数据在数据源中的位置
  7. int mFlags:ViewHolder对应的标记位
  8. List<Ojbect> mPayloads:实现局部刷新
  9. Recycler mScrapContainer:如果不为空,表示ViewHolder是存放在Scrap缓存中

1.2 flag

  1. FLAG_BOUND:ViewHolder对应的View已经绑定好了数据,无需重新绑定
  2. FLAG_UPDATE:数据发生了变化,View需要重新绑定
  3. FLAG_INVALID:数据失效了,View需要重新绑定
  4. FLAG_REMOVED:数据从数据源中删除,View在消失动画中仍然有用
  5. FLAG_NOT_RECYCLABLE:ViewHolder不能被回收,ViewHolder对应ItemView做动画时需要保证ViewHolder不能被回收掉
  6. FLAG_RETURNED_FROM_SCRAP:从scrap缓存中获取到的ViewHolder
  7. FLAG_IGNORE:如果回收该类型的ViewHolder会报错
  8. FLAG_TMP_DETACHED:表示ItemView从RecyclerView上DETACHED了,detach和remove的区别是,remove会将View从ViewGroup的children数组中删除并且刷新ViewGroup,detach只会删除不会触发刷新
  9. FLAG_ADAPTER_FULLUPDATE:表示ViewHolder需要全量更新,如果没有设置该标志位,则是局部更新
  10. FLAG_MOVED:当ViewHolder的位置发生变化,做动画时需要使用
  11. FLAG_APPEARED_IN_PRE_LAYOUT:ViewHolder出现在预布局中,需要做APPEARED动画

2. 缓存架构

2.1 四级缓存

  1. ArrayList<ViewHolder> mAttachedScrap & ArrayListmChangedScrap
  2. ArrayList<ViewHolder> mCachedViews
  3. ViewCacheExtension mViewCacheExtension
  4. RecycledViewPool mRecyclerPool

2.2 scrap缓存

scrap缓存由mAttachedScrap和mChangedScrap两个缓存组成,在RecyclerView调用dispatchLayout时会使用该缓存,保存RecyclerView上的子View。

两部分组成
  1. mAttachedScrap
  2. mChangedScrap
缓存特性
  1. 对应的数据结构是ArrayList<ViewHolder>
  2. 缓存大小没有限制,大小等于RecyclerView子View的个数
  3. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position对应上了
  4. 调用notifyItemRemoved、notifyItemMoved、notifyItemInserted方法,ViewHolder放入mAttachedScrap中
  5. 调用notifyItemChanged(int position, Object payload),如果payload!=null ViewHolder放入mAttachedScrap中,否则ViewHolder放入mChangedScrap中
  6. 调用notifyDataSetChanged()时,如果Adapter.hasStableIds返回true,ViewHolder放入mAttachedScrap中,否则会将ViewHolder回收到非scrap缓存中
  7. LinearLayoutManager.layoutForPredictiveAnimations()阶段,mAttachedScrap数组剩下的ViewHolder是被挤出屏幕的

2.3 mCachedViews缓存

缓存特性
  1. 对应的数据结构是ArrayList<ViewHolder>
  2. 缓存大小有限制,默认缓存大小为2,可以修改默认缓存大小。如果使用GridLayoutManager建议设置为列的个数
  3. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position和itemType对应上了
  4. 该缓存的特性是FIFO
  5. ViewHolder mFlag如果有FLAG_INVALID、FLAG_REMOVED、FLAG_UPDATE、FLAG_ADAPTER_POSITION_UNKNOWN之一,不会放入该缓存
  6. 当RecyclerView滑动时会将ViewHolder放入该缓存或者从该缓存获取ViewHolder

2.4 ViewCacheExtension

该接口只提供了get方法,没提供put方法。

2.5 RecyclerViewPool

缓存特性
  1. 对应的数据结构是SparseArray<ScrapData>,根据itemType将缓存分组,组的数据结构是ScrapData
  2. ScrapData对应的数据结构是ArrayList<ViewHolder>,每个itemType对应的ScrapData的缓存大小默认值是5,可以修改缓存大小
  3. 该缓存中的ViewHolder需要重新绑定数据
  4. 可以提供给多个RecyclerView共享

3. Recycler.recycleViewHolderInternal(ViewHolder holder)

3.1 调用时机

  1. 调用notifyItemRemoved(),ViewHolder动画结束后
  2. 被挤出屏幕的ViewHolder在动画结束后
  3. 滑出屏幕的ViewHolder需要回收掉
  4. 调用notifyDataSetChanged(),而且Adapter.hasStableIds()返回false
  5. 缓存中获取到的ViewHolder校验失败,需要回收
  6. 调用setAdapter()时

3.2 回收逻辑

3.2.1 回收流程图

3.2.2 源码分析

  1. 如果当前mCachedViews容量>=缓存最大数量,将存放最久的ViewHolder回收到RecyclerViewPool中,直到mCachedViews size=mViewCacheMax-1
  2. 将ViewHolder缓存到mCachedViews最后一个槽位上
  3. 如果缓存到mCachedViews失败,则缓存到RecyclerViewPool中
3.2.2.1 Recycler.recycleViewHolderInternal()
3.2.2.2 Recycler.recycleCachedViewAt(int cachedViewIndex)
3.2.2.3 RecyclerViewPool.putRecycledView(ViewHolder scrap)

4. Recycler.tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)

4.1 调用时机(以LinearLayoutManager为例)

dispatchLayoutStep1()、dispatchLayoutStep2()、滑动RecyclerView时会发生复用

  1. LinearLayoutManager.onLayoutChildren() -> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()
  2. LinearLayoutManager.layoutForPredictiveAnimations()-> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()
  3. LinearLayoutManager.scrollBy()-> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()

4.2 获取ViewHolder流程

4.2.1 Recycler.getChangedScrapViewForPosition(int position)

预布局过程中从mChangedScrap缓存中获取ViewHolder。获取逻辑如下:

  1. 线性遍历 mChangedScrap,position == ViewHolder.mPreLayoutPosition,返回该ViewHolder,否则走逻辑2
  2. Adapter.hasStableIds()返回false,返回null,否则走逻辑3
  3. 线性遍历 mChangedScrap,mAdapter.getItemId(offsetPosition) == holder.getItemId,返回该ViewHolder,否则走逻辑4
  4. 上述都没有获取到,返回null。

4.2.2 Recycler.getScrapOrHiddenOrCachedHolderForPosition(int position)

4.2.2.1 该方法从三个地方获取缓存,获取后还需要验证是否合法,如果验证失败会调用Recycler.recycleViewHolderInternal()
  1. mAttachedScrap
  2. hidden list中,ChildHelper通过位图算法,逻辑隐藏的ViewHolder
  3. mCachedViews
4.2.2.2 从mAttachedScrap中获取,必须满足以下所有条件:
  1. holder.getLayoutPosition() == position,即预布局 viewHolder.mPreLayoutPosition == position,布局中 viewHolder.mPosition == position
  2. !holder.isInvalid()
  3. 非预布局情况下,不可返回removed的ViewHolder
4.2.2.3 从hidden list中获取ViewHolder
  1. 调用调用ChildHelper.findHiddenNonRemovedView(position)获取View
  2. 调用ChildHelper.unhide(view),不隐藏View
  3. 调用mChildHelper.detachViewFromParent(layoutIndex),轻量删除View,从RecyclerView children数组中删除,但是不会重新绘制RecyclerView
  4. 放入scrap缓存中
4.2.2.4 从mCachedViews中获取ViewHolder
  1. !holder.isInvalid()
  2. holder.getLayoutPosition() == position
  3. 如果满足会将ViewHolder从mCachedViews中移除

4.2.3 Recycler.getScrapOrCachedViewForId(long id, int type, boolean dryRun)

  1. 首先从attachedScrap中查找,如果id和type都匹配上了,返回该ViewHolder,并且在非预布局情况下,将removed的设置成update
  2. 如果id匹配上了 type没匹配上,将该ViewHolder回收掉
  3. 其次从mCachedViews中查找,如果id和type都匹配上了,返回并移除掉该ViewHolder
  4. 如果id匹配上了 type没匹配上,将该ViewHolder回收掉

4.2.4 getRecycledViewPool().getRecycledView(int type)

根据viewType从SparseArray中获取相应的ViewHolder,该缓存中获取的ViewHolder需要重新绑定

4.2.5 Adapter.createViewHolder(RecyclerView recyclerView, int type)

4.2.6 Recycler.tryBindViewHolderByDeadline(RecyclerView recyclerView, int type)

  1. 调用Adapter.bindViewHolder()方法
  2. 将ViewHolder flag设置为FLAG_BOUND,清除FLAG_UPDATE、FLAG_INVALID、FLAG_ADAPTER_POSITION_UNKNOWN
  3. 重写onBindViewHolder(VH holder, int position, List<Object> payloads),通过payloads可以实现局部刷新
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节流动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1. ViewHolder
    • 1.1 作用
      • 1.2 flag
      • 2. 缓存架构
        • 2.1 四级缓存
          • 2.2 scrap缓存
            • 2.3 mCachedViews缓存
              • 2.4 ViewCacheExtension
                • 2.5 RecyclerViewPool
                • 3. Recycler.recycleViewHolderInternal(ViewHolder holder)
                  • 3.1 调用时机
                    • 3.2 回收逻辑
                      • 3.2.1 回收流程图
                      • 3.2.2 源码分析
                  • 4. Recycler.tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)
                    • 4.1 调用时机(以LinearLayoutManager为例)
                      • 4.2 获取ViewHolder流程
                        • 4.2.1 Recycler.getChangedScrapViewForPosition(int position)
                        • 4.2.2 Recycler.getScrapOrHiddenOrCachedHolderForPosition(int position)
                        • 4.2.3 Recycler.getScrapOrCachedViewForId(long id, int type, boolean dryRun)
                        • 4.2.4 getRecycledViewPool().getRecycledView(int type)
                        • 4.2.5 Adapter.createViewHolder(RecyclerView recyclerView, int type)
                        • 4.2.6 Recycler.tryBindViewHolderByDeadline(RecyclerView recyclerView, int type)
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档