recyclerView
缓存机制
简述:
一级缓存为屏内缓存scrapView,分为没有变化的可以直接复用的ViewHolder mAttachedScrap和因notifyXXX标记为需要重新绑定的ViewHolder mChangedScrap;(用position索引)
二级缓存为离屏2个的ViewHolder离屏缓存cacheView,直接复用;(用position索引)
三级缓存为自定义缓存ViewCacheExtension,较少用
四级缓存为超出上述缓存的需要重新绑定的ViewHolder缓存池RecycledViewPool; (用viewType索引ViewHolder,每种viewType最多5个)
Recyclerview缓存集合可以分为 4 个级别,按优先级从高到底为:
一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder
mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder
mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder
二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder
三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。个人感觉这个拓展脱离了 Adapter.createViewHolder 使用的话会造成 View 创建 与 数据绑定及其它代码太分散,不利于维护,使用场景很少仅做了解
四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。
按照 Type 来查找 ViewHolder
每个 Type 默认最多缓存 5 个
可以多个 RecyclerView 共享 RecycledViewPool
1.1、四级缓存
Recycler缓存ViewHolder对象有4个等级,优先级从高到底依次为:
一级、mAttachedScrap:缓存屏幕中可见范围的ViewHolder;
二级、mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,默认最大2个;
三级、ViewCacheExtension:自定义实现的缓存;
四级、RecycledViewPool :ViewHolder缓存池,可以支持不同的ViewType;
一级缓存:mAttachedScrap与mChangedScrap
1.1.1 mAttachedScrap (完全复用)
mAttachedScrap存储的是当前屏幕中的ViewHolder,mAttachedScrap的对应数据结构是ArrayList,在调用LayoutManager#onLayoutChildren方法时对views进行布局,此时会将RecyclerView上的Views全部暂存到该集合中,该缓存中的ViewHolder的特性是,如果和RV上的position或者itemId匹配上了那么可以直接拿来使用的,无需调用onBindViewHolder方法。
1.1.2 mChangedScrap (需要onBindViewHolder)
mChangedScrap和mAttachedScrap属于同一级别的缓存,不过mChangedScrap的调用场景是notifyItemChanged和notifyItemRangeChanged,只有发生变化的ViewHolder才会放入到mChangedScrap中。mChangedScrap缓存中的ViewHolder是需要调用onBindViewHolder方法重新绑定数据的。
二级缓存: mCachedViews (完全复用)
mCachedViews缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个。mCachedViews对应的数据结构是ArrayList,但是该缓存对集合的大小是有限制的。
该缓存中ViewHolder的特性和mAttachedScrap中的特性是一样的,只要position或者itemId对应就无需重新绑定数据。开发者可以调用setItemViewCacheSize(size)方法来改变缓存的大小,该层级缓存触发的一个常见的场景是滑动RecyclerView。当然调用notify()也会触发该缓存。
三级缓存: ViewCacheExtension
ViewCacheExtension是需要开发者自己实现的缓存,基本上页面上的所有数据都可以通过它进行实现。
四级缓存: RecyclerViewPool (重新onBindViewHolder)
ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder。
1.2 四级缓存对比
缓存级别 | 涉及对象 | 说明 | 是否重新创建视图View | 是否重新绑定数据 |
---|---|---|---|---|
一级缓存 | mAttachedScrap mChangedScrap | 缓存屏幕中可见范围的ViewHolder | false | mAttachedScrap不需要mChangedScrap需要 |
二级缓存 | mCachedViews | 缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存 | false | false |
三级缓存 | mViewCacheExtension | 开发者自行实现的缓存 | ||
四级缓存 | mRecyclerPool | ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder | false | true |
ItemDecoration
getItemOffsets()
view之间的分割距离
1 | RecyclerView.java { |
onDraw、onDrawOver
由于View绘制顺序是(忽视未标出的不常用步骤):
1 | View.java { |
其中,RecyclerView.onDraw会先绘制ItemDecoration.onDraw,然后再走dispatchDraw绘制子View(即itemView)
故而,ItemDecoration的onDraw内容是会被itemView所遮挡,如有需要,调用onDrawOver可覆盖itemView的onDraw内容。
优化措施:
1、预取机制
Prefetch : 引入RenderThread后,在UI线程将页面数据交由Render线程渲染以后,UI线程会出现大量的空闲时间这些空闲等待时间就被浪费了。Prefetch的核心思想就是利用这部分空闲时间来预先处理 item的创建(如果没有缓存)和数据绑定。
对比一下使用了Prefetch以后的渲染时序图如下:
在UI线程将页面数据交由Render线程渲染以后,会出现大量的空闲时间。如下图所示:
时空上的复用,会大大提高页面渲染的效率,提高页面流畅度。
该机制是默认打开的,但如果出现rv嵌套rv的情况,由于情况较为复杂,需要我们手动调用setInitialPrefetchItemCount 就是指定这个recyclerview预取时的数量(合适的),来达到更好的加载效果。
setInitialPrefetchItemCount这个API可以设置预加载的Item数,使用这个API有三个前提:
1:当前recyclerview是嵌套在另一个Recyclerview之中的;
2:当前recyclerview是LinearLayoutManager并且横向的(只有LinearLayoutManager 才有这个API);
3:Android 5.0(引入RenderThread,并默认打开recyclerview的mItemPrefetchEable)
2、setHasFixedSize(true)
如果Recyclerview的宽高不会随着它的内容改变而改变,则可以用这个API,避免不必要的requestLayout带来的性能消耗。(notifydatasetchange无效)
1 | void onItemsInsertedOrRemoved() |
3、尽量使用notifyItemChange而非notifyDataSetChange
数据变化有两种情况:
一种是item change
一种是 structure change;
前者是单个item数据更新但不会导致位置变换;
后者是数据集中items 发生插入,删除,移动的情况下。
Notifydatasetchange认为现有所有的现有项和结构不再有效,会使Layoutmanager强制完全重新绑定和重新布局所有的可见item。
Google建议尽量使用刷新特定itemchange的高效方法。而把此方法视为可以用的最后的手段。
4、RecyclerView RecycledViewPool复用
其实就是让一个Recyclerview 可以复用其他具有相同ViewType的Recyclerview。
5、DiffUtil
DifferResult.dispatchUpdatesTo(final RecyclerView.Adapter adapter)
或者
AysncListData.submitList()
走到最后其实也是 notifyItemChanged();
notifyItemInserted();
notifyItemRangeRemoved();
notifyItemMoved();
6、other
- 如果发现item因为measure任务过重,可以通过自定义view来优化此item,比如说一个ViewHolder里面有很多标签的情况。
- 在快速滑动时不加载网络图片或停止gif图和视频的播放
- 设置合适的缓存策略
recyclerView