你有没有在自助餐厅里遇到过这种事:高峰期盘子不够用,服务员一边收脏盘子一边给新来的客人用,你端着食物干等着。这时候要是有一套高效的餐具回收流程,客人吃完一拨,盘子马上洗干净摆回来,大家都不用等。
安卓里的ListView和RecyclerView就是干这个活的——滑动列表时,不停创建新的Item视图太耗内存,必须把滑出屏幕的“旧盘子”回收再利用。说白了,就是视图复用。
一、ListView的“餐具回收”——RecycleBin
早期的ListView内部有个叫RecycleBin的管家。你可以想象:餐厅里每个餐桌(屏幕上的每个item)撤下来后,服务员不会直接扔进洗碗机,而是先放在手边的小推车上。
ListView的回收分两块:
- Active Views:屏幕边缘即将进场或刚刚退场的item,处于“待命区”,还不是正在用餐的盘子。
- Scrap Views:已经滑出屏幕的item,被放进一个“临时缓存区”。下次需要新item时,优先从这里面拿,拿到后调用
getView()重新绑定数据。
但有个痛点:ListView不区分item的类型(比如你的列表里有头部、正文、广告三种样式),所有盘子混在一起。服务员从混在一起的推车里拿出来的盘子可能是广告用的金盘子,但你只想装米饭——这就是getView()里那个convertView参数为null的原因,只能重新造一个。
二、RecyclerView的“智能回收”——多级缓存
RecyclerView像是一家更精细的现代化餐厅。它把回收流程分成四层,每一层对应不同的“盘子状态”:
-
一级缓存(mAttachedScrap):刚撤下来的热乎盘子,布局重新计算时直接用,不需要重新走
onCreateViewHolder()。通常用于屏幕内Item的位置微调或notifyDataSetChanged()后的重新布局。至于要不要重新摆盘(绑定数据),看具体情况——如果数据没变,就不用动。 -
二级缓存(mCachedViews):撤下来的盘子简单冲一下,放在手边。默认大小2,虽然不需要重新创建(不走
onCreateViewHolder()),但取出时还是要重新摆盘(走onBindViewHolder())。适合快速来回滑动时快速复用。 -
三级缓存(ViewCacheExtension):留给开发者自己定制的“特殊餐具柜”,比如你非要给VIP客人用金盘子,自己控制怎么取放。
-
四级缓存(RecycledViewPool):真正的洗碗机+消毒柜。不同ViewType(不同样式的item)有各自的池子,并且可以跨多个RecyclerView共享(比如ViewPager里两个列表共用缓存)。池子里的盘子必须重新摆盘(绑定数据)才能用。
ListView缺的就是这个——按类型分离,不会拿广告样式去当正文用。
整个过程像流水线:服务员先看手边有没有干净的(一级),没有就去小推车看看(二级),再没有就去洗碗机拿(四级),最后才现造一个盘子。
三、面试官爱怎么问?
问:RecyclerView比ListView好在哪?
答:就好比手工作坊 vs 自动化流水线。ListView缓存级别少、不区分ViewType,容易“拿错盘子”。RecyclerView强制使用ViewHolder,设计了四级缓存 + 按类型分池,还支持跨列表共享缓存、动画、局部刷新,滑动更顺滑。
问:如果RecyclerView出现图片错位或内容闪烁,怎么排查?
答:通常是缓存复用导致的。检查onBindViewHolder()里是否重置了所有可见状态。因为四级缓存池里的“盘子”是洗过但可能还有之前客人的“水渍”(旧数据残留),比如之前加载的图片没清空,新位置又异步加载了旧图。解决办法就是每次绑定都把图片先清掉,再加载新的。
四、总结表格
| 对比点 | ListView(老式餐厅) | RecyclerView(智能餐厅) |
|---|---|---|
| 缓存级别 | 2级(Active + Scrap) | 4级(AttachedScrap / CachedViews / 开发者定制 / ViewPool) |
| 是否按ViewType区分 | ❌ 所有盘子混在一起 | ✅ 每种样式独立池子,可跨列表共享 |
| 是否强制ViewHolder | ❌ 可选 | ✅ 强制,架构基础 |
| 取出时是否重新绑定 | Scrap取出必调getView() | CachedViews需重新绑定;AttachedScrap视情况 |
| 扩展性 | 弱 | 强(可自定义缓存策略、布局管理器、动画) |
五、人话总结
ListView就是服务员手忙脚乱地传盘子,RecyclerView则是给盘子贴了分类标签、还分了手边和洗碗池的智能回收流水线,连盘子样式都强制统一了。
汇总导航