相关知识:
ListView 和 RecyclerView 的缓存机制原理大致相似,如下图所示:
过程中,离屏的ItemView即被回收至缓存,入屏的ItemView则会优先从缓存中获取,只是ListView与RecyclerView的实现细节有差异。(这只是缓存使用的其中一个场景,还有如刷新等)
缓存机制对比
1.层级不同
RecyclerView 比 ListView 多了两层缓存,支持多个离屏 ItemView 的缓存,支持开发者自定义缓存处理逻辑,支持所有 RecyclerView 共用一个 RecyclerViewPool 缓存池。
具体来说:
ListView(两级缓存):
RecyclerView(四级缓存):
ListView 和 RecyclerView 缓存机制基本一致:
1). mActiveViews 和 mAttachedScrap 功能相似,意义在于快速重用屏幕上可见的列表项 ItemView,而不需要重新 createView和 bindView。
2). mScrapView 和 mCachedViews + mReyclerViewPool 功能相似,意义在于缓存离开屏幕的 ItemView,目的是让即将进入屏幕的 ItemView 重用。
3). RecyclerView 的优势在于:
- a.mCacheViews 的使用,可以做到屏幕外的列表项 ItemView 进入屏幕内时也无须 bindView 快速重用;
- b.mRecyclerPool 可以供多个 RecyclerView 共同使用,在特定场景下,如 viewpaper + 多个列表页下有优势,客观来说,RecyclerView 在特定场景下对 ListView 的缓存机制做了补充和完善。
2.缓存的对象不同
1). RecyclerView 缓存 ViewHolder,抽象可理解为:View + ViewHolder(避免每次 createView 时调用 findViewById) + flag(标识状态);
2). ListView 直接缓存 View。
缓存不同,二者在缓存的使用上也略有差别,具体来说:
ListView获取缓存的流程:
RecyclerView获取缓存的流程:
1).RecyclerView 中 mCacheViews(屏幕外)获取缓存时,是通过匹配 pos 获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新 bindView。
情景:用手指上下滑动,重复让 2-3 个 Item 滑出和滑入屏幕,此时是不需要重新 bindView 的。如果一直向一个方向滑动,RecyclerView 还是会去 bindView,因为 mCacheViews 缓存空间有限。
而同样是离屏缓存,ListView 从 mScrapViews 根据 pos 获取相应的缓存,但是并没有直接使用,而是重新 getView(即必定会重新 bindView),相关代码如下:
1 | //通过匹配pos从mScrapView中获取缓存 |
2).ListView 中通过 pos 获取的是 view,即 pos → view。
RecyclerView 中通过 pos 获取的是 viewholder,即 pos → (view,viewHolder,flag);
从流程图中可以看出,标志 flag 的作用是判断 view 是否需要重新 bindView,这也是 RecyclerView 实现局部刷新的一个核心。
局部刷新
由上文可知,RecyclerView 的缓存机制确实更加完善,但还不算质的变化,RecyclerView 更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的 bindView。
[RecyclerView和ListView添加,移除Item效果对比]
结合 RecyclerView 的缓存机制,看看局部刷新是如何实现的:以 RecyclerView 中 notifyItemRemoved(1)
为例,最终会调用 requestLayout()
,使整个 RecyclerView 重新绘制,过程为:
onMeasure()→onLayout()→onDraw()
其中,onLayout() 为重点,分为三步:
dispatchLayoutStep1()
:记录 RecyclerView 刷新前列表项 ItemView 的各种信息,如 Top、Left、Bottom、Right,用于动画的相关计算;dispatchLayoutStep2()
:真正测量布局大小,位置,核心函数为layoutChildren()
;dispatchLayoutStep3()
:计算布局前后各个 ItemView 的状态,如Remove、Add、Move、Update等,如有必要执行相应的动画。
其中,layoutChildren()
流程图:
当调用 notifyItemRemoved()
时,会对屏幕内 ItemView 做预处理,修改 ItemView 相应的 pos 以及 flag (流程图中红色部分):
当调用fill()
中 RecyclerView.getViewForPosition(pos)
时,RecyclerView 通过对 pos 和 flag 的预处理,使得bindview()
只调用一次.
需要指出,ListView 和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的 View 都重新 bindView()
,而 RecyclerView 则是更加灵活地对每个 View 修改标志位,区分是否重新bindView()
。
参考
Bugly 的 Android ListView 与 RecyclerView 对比浅析–缓存机制