RecyclerView 核心要点

首先感谢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,不停的滚动就会一直查找控件。

RecyclerView 核心要点_第1张图片
image.png

如图所示,其实item view 和 ViewHolder 是 一一对应的关系,因为每创建一个item view 需要创建一个ViewHolder,然后后面需要使用ViewHolder的时候是从convertView中getTag中取出ViewHolder来使用。所以ViewHolder和item view是一一对应的关系。

ViewHolder主要解决了item view的复用问题,减少findViewById操作,增加列表性能。

RecyclerView和Listview的缓存机制对比

  • ListView的缓存:
    图一:


    RecyclerView 核心要点_第2张图片
    image.png

    图二:


    RecyclerView 核心要点_第3张图片
    image.png

    由图可以看出
    ListView 的缓存由RecycleBin来控制,分为两部分Active View 和Scrap View
  1. Active View是指在屏幕内的item view ,缓存这部分是因为android的屏幕帧是一直在更新的,当更新的屏幕的时候如果item view 已经在屏幕内的是直接会被拿来复用的,不会在走getView方法绑定数据
  2. Scrap View是指划出屏幕的item view, 被回收利用放入Scrap View中,当下以个item view要显示的时候会先到Scrap View中去查找有没有view type同类型的可以复用,如果有直接拿来绑定数据,如果没有找到会Create View然后在绑定数据。

总结:凡是Active View直接拿来复用的不会走getView绑定数据方法,凡是Scrap View不管是创建View还是复用View都会走getView方法来绑定数据。

  • RecyclerView的缓存:
    图一:


    RecyclerView 核心要点_第4张图片
    image.png

    图二:


    RecyclerView 核心要点_第5张图片
    image.png

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适用于整个页面需要刷新,但是有部分数据可能相同情况


    image.png

    RecyclerView 核心要点_第6张图片
    image.png

    image.png

    image.png

    image.png

    在列表很大的时候异步计算Diff
    1.使用Thread/Handler 讲DiffResult 发送到主线程
    2.使用RxJava 讲calculateDiff 操作放到后台线程
    3.使用Google 提供的AsyncListDiffer(Executor) / ListAdapter

你可能感兴趣的:(RecyclerView 核心要点)