RecyclerView 缓存及复用机制
RecyclerView 为什么要预布局
答案参考自:
什么是预布局
预布局是指在正式布局 RecyclerView
中的 ItemView
前执行的一次布局过程。
预布局的作用
预布局的作用是为了使 ItemAnimator
执行时能给用户更好的视觉体验。
预布局和正式布局的区别
预布局过程和正式布局过程执行的都是一样的代码,不同的是预布局过程得到的是 ItemAnimator
执行前的布局,而正式布局得到的是 ItemAnimator
执行后的布局也就是最终用户看到的布局。
什么情况下会执行预布局
当布局结束后若有新的 ItemView
在布局结尾显示则需要执行预布局,也就是当 RecycleView
中有 ItemView
被删除或更新时需要执行预布局。看下图更清晰。
从上图中可以看到当不执行预布局时如果布局结尾有新的 ItemView
出现会执行 DefaultItemAnimator
的添加动画(淡入),这种看起来好像卡顿一样的显示给用户的感觉并不好。当然如果根本没有动画那预布局也就没有了意义。
RecyclerView的多级缓存机制,每一级缓存具体作用是什么,分别在什么场景下会用到哪些缓存
答案参考自:
RecyclerView 是先复用,后回收。
共四级缓存,分别为:
ArrayList<ViewHolder> mAttachedScrap
&ArrayList<ViewHolder> mChangedScrap
(屏幕内)ArrayList<ViewHolder> mCachedViews
(屏幕外)ViewCacheExtension mViewCacheExtension
(自定义缓存)RecycledViewPool mRecyclerPool
(缓存池)
每一级缓存的具体作用是什么?
- mAttachedScrap(屏幕内)
- 用于屏幕内itemview快速重用,不需要重新createView和bindView。
- 缓存大小没有限制,大小等于RecyclerView子View的数量。
- 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position对应上。
- mCacheViews(屏幕外)
- 保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用。
- 应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
- 缓存大小有限制,默认缓存大小为2,可以修改默认缓存大小。
- 该缓存的特性是FIFO。
- 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position和itemType对应上。
- mViewCacheExtension(自定义缓存)。
- 不直接使用,需要用户自定义实现,默认不实现。
- 该接口只提供了get方法,没提供put方法。
- mRecyclerPool(缓存池)
- 当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。需要重新执行onBindView的只有一种缓存区,就是缓存池mRecyclerPool。
- 每个itemType对应的ScrapData的缓存大小默认值是5,可以修改缓存大小。
- 该缓存中的ViewHolder需要重新绑定数据。
- 可以提供给多个RecyclerView共享。
RecyclerView的回收复用机制
RecyclerView
滑动时会触发onTouchEvent#onMove
,回收及复用ViewHolder
在这里就会开始。
保存缓存流程:
插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中。
滑动屏幕的时候,先消失的itemview会保存到CacheView,CacheView大小默认是2,超过数量的话按照先入先出原则,移出头部的itemview保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype进行保存,每个itemType缓存个数为5个,超过就会被回收。
获取缓存流程:
AttachedScrap中获取,通过pos匹配holder——>获取失败,从CacheView中获取,也是通过pos获取holder缓存 ——>获取失败,从自定义缓存中获取缓存——>获取失败,从mRecyclerPool中获取 ——>获取失败,重新创建viewholder——createViewHolder并bindview。
总结一下上述流程:
- 通过mAttachedScrap、mCachedViews及mViewCacheExtension获取的ViewHolder不需要重新创建布局及绑定数据;
- 通过缓存池mRecyclerPool获取的ViewHolder不需要重新创建布局,但是需要重新绑定数据;
- 如果上述缓存中都没有获取到目标ViewHolder,那么就会回调Adapter#onCreateViewHolder创建布局,以及回调Adapter#onBindViewHolder来绑定数据。
ListView 和 RecyclerView 的区别
缓存机制
- ListView 是二级缓存,缓存的对象为 View
- RecyclerView 是四级缓存,缓存的对象为 RecyclerView.ViewHolder
使用方式
- ListView
- 需要继承 BaseAdapter 类
- 需要自定义 ViewHolder 实现 Item 的复用
- RecyclerView
- 需要继承 RecyclerView.Adapter 以及 RecyclerView.ViewHolder
布局效果
- ListView 只有一种纵向布局
- RecyclerView 默认有三种布局效果,可以自己继承 LayoutManager 实现自己想要的效果
空数据处理
- ListView 有
setEmptyView
方法处理 Adapter 数据为空的情况 - RecyclerView 必须自己处理数据为空的情况
Header View 与 Footer View
- ListView 提供了添加 Header View 与 Footer View 的方法
- RecyclerView 必须自己实现添加的方法
动画效果
- ListView 中没有默认实现的动画效果,但是我们可以在 Adapter 中自行实现。
- RecyclerView 中已经实现了部分默认的动画效果,例如
notifyItemChanged()
、notifyDataInserted()
等方法。如果需要自定义动画,我们可以继承 RecyclerView.ItemAnimator类实现自己的动画效果。
点击事件
- ListView 实现了
onItemClickListener
接口。 - RecyclerView 中并没有实现默认的点击事件的监听方法,我们需要自行实现。
嵌套滚动机制
- ListView没有实现嵌套滚动机制。
- 在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(
Nested Scrolling
)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制。
RecyclerView性能优化
答案参考自:
- [RecyclerView性能优化](RecyclerView性能优化 - 简书 (jianshu.com))
- [RecyclerView性能优化及高级使用](RecyclerView性能优化及高级使用 - 简书 (jianshu.com))
减少 xml 文件 inflate 时间
xml文件包括:layout、drawable的xml,xml文件inflate出ItemView是通过耗时的IO操作。可以使用代码去生成布局,即new View()的方式。这种方式是比较麻烦,但是在布局太过复杂,或对性能要求比较高的时候可以使用。
设置高度固定
如果item高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。
共用RecycledViewPool
在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那么可以设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool。
Note: 如果LayoutManager是LinearLayoutManager或其子类,需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)。
加大RecyclerView的缓存
用空间换时间,来提高滚动的流畅性。
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
增加RecyclerView预留的额外空间
额外空间:显示范围之外,应该额外缓存的空间。
1
2
3
4
5
6newLinearLayoutManager( this {
@Override
protected int getExtraLayoutSpace(RecyclerView.Statestate) {
return size;
}
});减少ItemView监听器的创建
对ItemView设置监听器,不要对每个item都创建一个监听器,而应该共用一个XxListener,然后根据ID来进行不同的操作,优化了对象的频繁创建带来的资源消耗。
回收资源
通过重写 RecyclerView.onViewRecycled(holder) 来回收资源。
TODO
- RecyclerView的刷新回收复用机制
- RecyclerView的滑动回收复用机制