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之间的分割距离

image-20240729173733345

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RecyclerView.java {
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}

if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
}

onDraw、onDrawOver

由于View绘制顺序是(忽视未标出的不常用步骤):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
View.java {
public void draw(Canvas canvas) {
// Step 1, draw the background, if needed
drawBackground(canvas);
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
}

RecyclerView.java {
public void onDraw(Canvas c) {
super.onDraw(c);

final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
}

其中,RecyclerView.onDraw会先绘制ItemDecoration.onDraw,然后再走dispatchDraw绘制子View(即itemView)

故而,ItemDecoration的onDraw内容是会被itemView所遮挡,如有需要,调用onDrawOver可覆盖itemView的onDraw内容。

示意图

优化措施:

1、预取机制

Prefetch : 引入RenderThread后,在UI线程将页面数据交由Render线程渲染以后,UI线程会出现大量的空闲时间这些空闲等待时间就被浪费了。Prefetch的核心思想就是利用这部分空闲时间来预先处理 item的创建(如果没有缓存)和数据绑定。

对比一下使用了Prefetch以后的渲染时序图如下:

在UI线程将页面数据交由Render线程渲染以后,会出现大量的空闲时间。如下图所示:

image-20211015172001352

时空上的复用,会大大提高页面渲染的效率,提高页面流畅度。

该机制是默认打开的,但如果出现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
2
3
4
5
6
7
void onItemsInsertedOrRemoved() 
{
if (hasFixedSize)
layoutChildren();
else
requestLayout();
}

image-20211015172554781

3、尽量使用notifyItemChange而非notifyDataSetChange

数据变化有两种情况:

一种是item change

一种是 structure change;

前者是单个item数据更新但不会导致位置变换;

后者是数据集中items 发生插入,删除,移动的情况下。

Notifydatasetchange认为现有所有的现有项和结构不再有效,会使Layoutmanager强制完全重新绑定和重新布局所有的可见item。

Google建议尽量使用刷新特定itemchange的高效方法。而把此方法视为可以用的最后的手段。

4、RecyclerView RecycledViewPool复用

其实就是让一个Recyclerview 可以复用其他具有相同ViewType的Recyclerview。

5、DiffUtil

image-20211015173057677

image-20211015172940305

DifferResult.dispatchUpdatesTo(final RecyclerView.Adapter adapter)

或者

AysncListData.submitList()

走到最后其实也是 notifyItemChanged();

​ notifyItemInserted();

​ notifyItemRangeRemoved();

​ notifyItemMoved();

6、other

  • 如果发现item因为measure任务过重,可以通过自定义view来优化此item,比如说一个ViewHolder里面有很多标签的情况。
  • 在快速滑动时不加载网络图片或停止gif图和视频的播放
  • 设置合适的缓存策略
Author

white crow

Posted on

2021-10-15

Updated on

2024-10-22

Licensed under