首先感谢Jiaheng的分享,主要以记录内容为目的
主要记录内容包括:
- RecyclerView和ListView优缺点
- ViewHolder究竟是什么?
- RecyclerView和Listview的缓存机制对比
- 你可能不知道的RecyclerView 性能优化策略
RecyclerView和ListView优缺点
ListView的局限
- 只有纵向列表一种布局
- 没有支持动画的API
- 没有强制实现ViewHolder
- 性能不如RecyclerView
RecyclerView的优势
- 默认支持 Linear, Grid, Staggered Grid 三种布局
- 友好的 ItemAnimator 动画 API
- 强制实现 ViewHolder
- 解耦的架构设计
- 相比 ListView 性能更好
ViewHolder究竟是什么?
- ViewHolder 和 item view 是什么关系?一对一?一对多?多对一?
以ListView为例:
不使用ViewHolder
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
if (convertView == null) {
convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_xxx, null)
}
var itemText = convertView!!.findViewById(R.id.textView) as TextView
itemText?.text = getItem(position)
return convertView
}
使用ViewHolder
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
val holder: ViewHolder
if (null == convertView) {
holder = ViewHolder()
convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_xxx, null)
holder.itemText = convertView!!.findViewById(R.id.textView) as TextView
convertView.tag = holder
} else {
holder = convertView.tag as ViewHolder
}
holder.itemText?.text = getItem(position)
return convertView
}
在getView()方法中,使用不使用ViewHolder,都有判断convertView==null,所以控件都有复用,主要的区别在于不使用ViewHolder每次显示Item时,View都回去findViewById,不停的滚动就会一直查找控件。
如图所示,其实item view 和 ViewHolder 是 一一对应的关系,因为每创建一个item view 需要创建一个ViewHolder,然后后面需要使用ViewHolder的时候是从convertView中getTag中取出ViewHolder来使用。所以ViewHolder和item view是一一对应的关系。
ViewHolder主要解决了item view的复用问题,减少findViewById操作,增加列表性能。
RecyclerView和Listview的缓存机制对比
-
ListView的缓存:
图一:
图二:
由图可以看出
ListView 的缓存由RecycleBin来控制,分为两部分Active View 和Scrap View
- Active View是指在屏幕内的item view ,缓存这部分是因为android的屏幕帧是一直在更新的,当更新的屏幕的时候如果item view 已经在屏幕内的是直接会被拿来复用的,不会在走getView方法绑定数据
- Scrap View是指划出屏幕的item view, 被回收利用放入Scrap View中,当下以个item view要显示的时候会先到Scrap View中去查找有没有view type同类型的可以复用,如果有直接拿来绑定数据,如果没有找到会Create View然后在绑定数据。
总结:凡是Active View直接拿来复用的不会走getView绑定数据方法,凡是Scrap View不管是创建View还是复用View都会走getView方法来绑定数据。
-
RecyclerView的缓存:
图一:
图二:
RecyclerView和ListView的缓存对象有点不太一样,ListView缓存的额是item view 而 RecyclerView缓存的是ViewHolder,但是原理都是一样的,因为item view和ViewHolder本来就是一一对应的。RecyclerView的缓存由Recycler来控制,分为4个部分Scap, Cache,ViewCacheExtension,RecycledViewPool
1.RecyclerView的Scap相当于ListView的Active View,但是Scap查找缓存是通过position来实现的,可以直接拿来显示,都不需要在绑定数据。
2.RecyclerView的Cache相当于ListView的Scrap View,不同的地方是 Cache也是通过position来查找实现的,Cache缓存也是直接可以使用的View,如果用户反复小范围上下滑动,那么会通过查找Cache里面的positon直接显示,不需要走onBindViewHolder绑定数据,和Scap里面的效果一样,但是ListView是需要走getView重新绑定数据的。
3.ViewCacheExtension自定义缓存的item view,基本很少使用
4.RecycledViewPool 被废弃item的缓存池,从这里面被找出来复用的item会再次走onBindViewHolder方法重新绑定数据显示。
总结:RecyclerView缓存会先从Scap和Cache缓存通过positon来查找是否有直接可用的item显示,并且这两个缓存里面拿出来的item不会走onCreateViewHolder和onBindViewHolder方法,如果两个里面都没有那会走ViewCacheExtension自定义的缓存,最后会从RecycledViewPool里面去查找缓存,通过RecycledViewPool取到的缓存会执行onBindViewHolder方法重新绑定数据显示,如果所有的缓存都找不到,那么会走onCreateViewHolder方法创建View,并且会执行onBindViewHolder方法绑定数据。
小知识点 :如何统计列表中的广告显示次数
- ListVIew中可以直接使用getView方法,因为不管是复用还是重新CreateView都需要走getView方法绑定数据。
- RecyclerView使用onBindViewHolder吗?因为RecyclerView里面有一个Cache缓存的存在,Cache是通过position来实现的,当用户反复上下小范围滑动时,Cache缓存里面通过position拿出来的数据是可以直接展示的不会走onBindView,所以需要使用onViewAttachedToWindow方法来统计。
你可能不知道的RecyclerView 性能优化策略
最好不要在onBindViewHolder里面设置监听
在onBindViewHolder设置点击监听器会导致重复创建对象,因为监听只是一个观察者模式,并且ViewHolder和item view 是一一对应的,所以设置一次就可以进行监听,最好是放在onCreateViewHolder里面设置LinearLayoutManager.setInitalPrefetchItemCount(横线列表初次显示时可见的item个数)
对使用竖向的RecyclerView里面有嵌套一个横向的RecyclerView进行优化,如果滑到了横向滑动item RecyclerView时由于创建更多的子View,会引起页面卡顿。由于RenderThread的存在,RecyclerView会进行prefetch,所以可以使用LinearLayoutManager.setInitalPrefetchItemCount()。值得注意的是只有LinearLayoutManager有这个API,并且只能在嵌套内部的RecyclerView的LinearLayoutManager调用才生效RecyclerView.setHasFixedSize()
设置是否有固定的Size,如果Adapter的数据变化不会导致RecyclerView的大小变化,直接调用RecyclerView.setHasFixedSize(true)-
多个RecyclerView共用RecycledViewPool
使用场景在ViewPager+Fragment+RecyclerView中有相同的列表item。var recycledViewPool = RecyclerView.RecycledViewPool() recyclerView1.recycledViewPool = recycledViewPool recyclerView2.recycledViewPool = recycledViewPool recyclerView3.recycledViewPool = recycledViewPool
-
DiffUtil
局部更新方法notifyItemXXX()不适用于所有情况
notifyDataSetChange()会导致整个布局重绘,重新绑定所有的ViewHolder,而且会失去可能的动画效果
DiffUtil适用于整个页面需要刷新,但是有部分数据可能相同情况
在列表很大的时候异步计算Diff
1.使用Thread/Handler 讲DiffResult 发送到主线程
2.使用RxJava 讲calculateDiff 操作放到后台线程
3.使用Google 提供的AsyncListDiffer(Executor) / ListAdapter