RecyclerView滚动时回收和复用机制

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: RecyclerView滚动时回收和复用机制

谈到RecyclerView的时候,复用机制是我们能脱口而出的优点之一。系统内置的ViewHolder避免了使用ListView时手动去创建ViewHolder的麻烦。关于何时回收View,何时复用View,我们能做到胸有成竹吗?当我们滑动一个RecyclerView时,是先回收View,再复用View?还是先复用View,再回收View呢?答案是都有可能。详情且看下面分析:


「名词解释」


「1. 回收:是指View不需要再展示在屏幕中,被回收到回收池中」


「2. 复用:本文中的复用是指调用了onCreateViewHolder或者onBindViewHolder方法」


1.滑动RV的两个场景


1.1 场景一

RV中每个Item高度都为100px,最后一个Item超出屏幕50px。RV初始状态如下图

640.png

Q1 假设向上滑动40px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答回收和复用都没有发生

Q2    假设向上滑动60px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答没有发生回收,发生了复用

Q3    假设向上滑动120px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答发生了回收和复用。先复用后回收


1.2 场景二


RV中第一个Item高度为50px,其它都为100px,最后一个Item超出屏幕95px。RV初始状态如下

640.png


Q1    假设向上滑动40px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答回收和复用都没有发生

Q2    假设向上滑动60px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答发生了回收,没有发生复用

Q3    假设向上滑动120px

请问是否有View发生回收和复用?如果有,先复用还是先回收?

答发生了回收和复用。先回收后复用

从答案可以看出。回收和复用并没有固定的答案。它因场景而异。下面我们通过案例验证答案真伪。


2. DEMO验证答案


2.1 我们来验证场景一

程序运行图

640.png


程序代码

640.png

日志输出如下


首先进入初始状态

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 0

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 1

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 2

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 3

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 4

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 5

点击上滑40px。打印日志不变。证明 回收和复用都没有发生

点击上滑60px。打印日志如下。证明 没有发生回收,发生了复用

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 0

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 1

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 2

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 3

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 4

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 5

RecyclerView 场景一 onCreateViewHolder //只发生了复用

RecyclerView 场景一 onBindViewHolder 6

点击上滑动120px。打印日志如下。证明 发生了回收和复用。先复用后回收

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 0

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 1

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 2

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 3

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 4

RecyclerView 场景一 onCreateViewHolder

RecyclerView 场景一 onBindViewHolder 5

RecyclerView 场景一 onCreateViewHolder //先复用

RecyclerView 场景一 onBindViewHolder 6

RecyclerView 场景一 发生回收 item 0  //后回收


2.2 我们来验证场景二


程序运行图

640.png

程序代码

640.png


日志输出首先进入初始状态


RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 0

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 1

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 2

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 3

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 4

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 5

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 6

点击上滑40px。打印日志不变。证明 回收和复用都没有发生

点击上滑60px。打印日志如下。证明 发生回收,没有发生复用

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 0

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 1

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 2

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 3

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 4

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 5

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 6

RecyclerView 场景二 发生回收 item 0   //只发生了回收

点击上滑动120px。打印日志如下。证明 发生了回收和复用。先回收后复用

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 0

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 1

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 2

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 3

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 4

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 5

RecyclerView 场景二 onCreateViewHolder

RecyclerView 场景二 onBindViewHolder 6

RecyclerView 场景二 发生回收 item 0 //先回收

RecyclerView 场景二 onBindViewHolder 7 //再复用


3. 滑动原理分析


640.jpg


3.1 相关参数


如图所示,介绍几个关于坐标的参数


  1. delta:手指滑动的距离120px。


  1. mOffset:RV最后一个子View的Bottom在屏幕坐标系的Y坐标600px。RV的下一个View(Item7)从mOffset处布局。


  1. mScrollingOffset:RV最后一个子view的Bottom离RV Bottom的距离50px。向上滑动不超过该距离。如超过需创建新的View填充。


  1. mVailable:delta-mScrollingOffset。可以填充View的空间。如果大于0表示有空间填充新的View


  1. 如果delta<mScrollingOffset,mScrollingOffset=delta,mVailable<0,即滑动距离小于mScrollingOffset,不要填充新的View


3.2 滑动填充和回收逻辑

滑动逻辑如下


  1. 从RecyclerView的第0个View开始遍历,直到View的Bottom>mScrollingOffset,并记录该View的下标index,回收[0,index)区间的View,index为开区间,如果index>=1,则会将[0,index)区间的View移除屏幕,并按照回收算法放入回收池。具体回收算法先按下不表。


  1. 如果mVailable>0,则从mOffset处,用新的View填充。mOffset+=新View的高度,mVailable-=新View的高度,mScrollingOffset+=新View的高度,如果mVailable<0,mScrollingOffset+=mVailable。布局完成后用步骤1的算法按需回收上面的View。


  1. 重复步骤2


  1. 将RV整体,向上移动delta或者consumed距离(一般是delta距离,但是当RecyclerView下面没有Item时会是具体消耗掉的距离)


逻辑1对应的代码如下

640.png

逻辑2填充View代码如下

640.png

3.3 分析场景一


根据此滑动逻辑,我们分析场景一中的向上滑动120px

640.jpg


mOffset = 600px

mScrollingOffset = 50px

mAvailable = 70px

item1高度100px


  1. 首先从第0个View遍历Bottom>50px。找到item1.bottom=100px,记录index=0。因为index<1。所以不发生回收


  1. mAvailable>0,从Item6的底部,增加View Item7(此处发生复用逻辑)高度为100px,mOffset=700px,mAvailable=-30,


  1. mScrollingOffset=mScrollingOffset+100-30=120px。然后检查回收。首先从第0个View遍历Bottom>120px。找到item2.bottom=200px,记录index=1。回收[0,1)区间的View。即回收Item1


  1. mAvailable=-30<0,退出填充逻辑


  1. 整体向上移动120px


我们看到先创建Item7 然后回收Item1。跟日志相符合

RecyclerView 场景一 onCreateViewHolder //先复用

RecyclerView 场景一 onBindViewHolder 6

RecyclerView 场景一 发生回收 item 0  //后回收

同样的逻辑我们也可以分析场景二中的向上滑动120px的情况。场景二会先发生回收,再发生复用。读者可以自己去求证。


4.源码分析


RV的滑动,最终会调用LayoutManager的scrollBy方法。我们使用的是LinearLayoutManager。

640.png


  1. 代码1 updateLayoutState方法,主要是计算mOffset等参数。


  1. 代码2 fill方法,根据剩余空间,填充View


  1. 代码3 offsetChildren,整体移动RV的子View

640.png

640.png


  1. 代码1,首先判断是否需要回收View
  2. 代码2,根据剩余空间,判断是否需要填充View
  3. 代码3 是具体的layout方法
  4. 代码4 当单个layout完成后判断是否需要回收View


5. 提问互动


最后为了巩固大家对知识的理解,提出一个问题,请在评论区写出你的答案吧。

640.jpg


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
RecyclerView禁止复用
RecyclerView禁止复用
1684 0
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (一)
RecyclerView 性能优化 | 把加载表项耗时减半 (一)
10977 0
|
存储 缓存 Android开发
RecyclerView 面试题 | 滚动时表项是如何被填充或回收的?
RecyclerView 面试题 | 滚动时表项是如何被填充或回收的?
69 0
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (二)
RecyclerView 性能优化 | 把加载表项耗时减半 (二)
221 0
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (三)(下)
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
125 0
|
XML 前端开发 数据格式
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
145 0
|
存储 缓存
RecyclerView 面试题 | 哪些情况下表项会被回收到缓存池?
RecyclerView 面试题 | 哪些情况下表项会被回收到缓存池?
139 0
|
存储 缓存 Android开发
RecyclerView 缓存机制 | 回收些什么?
RecyclerView 缓存机制是面试中的常客。上一篇文章讲述了“从哪里获得回收的表项”,这一篇会结合实际回收场景分析下“回收哪些表项?”。
104 0
|
存储 缓存 算法
更高效地刷新 RecyclerView | DiffUtil二次封装
每次数据变化都全量刷新整个列表是很奢侈的,不仅整个列表会闪烁一下,而且所有可见表项都会重新绑定一遍数据。这一篇对 DiffUtil 进行二次封装以让其更易于使用。
571 0
|
存储 缓存
RecyclerView 缓存机制 | 回收到哪去?
--- # 主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy # 贡献主题:https://github.com/xitu/juejin-markdown-themes theme: github highlight: github --- RecyclerView 内存性能优越,这得益于它独特的缓存机制,上两篇已经分析了 RecyclerView 缓存机制会回收哪些表项,及如何从缓存中获取表项。本篇在此基础上继续走读源码,分析“回收的表项是以怎样的形式存放”。 这是`RecyclerView`缓存机制系列文章的第三
61 0