RecycleView可优化的点

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RecycleView可优化的点相关的知识,希望对你有一定的参考价值。

参考技术A

因为onBind方法的调用时机是View滑到屏幕内可显示时这个方法就会调用此方法,避免在这个方法内设置点击事件等创建对象的操作。

多用于RecycleView嵌套RecycleView,减少对ViewHolder的创建

notifydatasetchange全部刷新,notifyItemRemoved(0)局部删除, notifyItemRangeInserted(start,end)等刷新机制的选择。

其实setItemViewCacheSize设置的是CacheViews的大小

所以我们可以适当的通过调用setItemViewCacheSize方法,来增加CacheViews的大小(默认是2),来防止小范围的滑动导致的重复Bind而导致的卡顿。 典型的拿空间还时间,所以要考虑内存问题,根据自己的应用实际情况设置大小

当知道Adapter内Item的改变不会影响RecyclerView宽高的时候,可以设置为true让RecyclerView避免重新计算大小。

需要升级Recycle版本到25以上的版本才能使用recyclerview prefetch功能。
参考文章: RecyclerView的新机制:预取(Prefetch)
RecyclerView Prefetch功能探究

如何让你的 RecycleView 帧率提高到55帧?

误区

网搜列表优化, 大多会建议我们从以下几个方面优化, 来提高列表的流畅度.

  1. 降低布局层级
  2. 缓存布局对象, 或者控制内存分配等
  3. 控制列表刷新范围

可我们经常面对的场景是:

  1. 业务复杂, 布局层级无法再简化
  2. 该缓存的布局由viewHolder做了, 该控制的缓存也都控制了
  3. 快速全列表滑动时, 肯定是全列表刷新的, 范围就是列表控件展示的所有Item.

这使得列表帧率的优化, 几乎无法推进.

其实这些建议都没有大毛病, 但是, 都没有抓到问题的症结.

症结

症结确实是 "Item的布局太复杂".

而这个"太复杂"会引起Android Framework的两个方法非常耗时:

  • inflate()
  • onMeasure()

通过方法耗时检测工具, 我们可以确认这一点. 方法耗时检测工具设计方案看这里

这两个方法, 分别在列表滚动过程中, 当有新的item需要展示时, 会被调用.

如果你是用ViewHolder, 那么inflate()可能不会频繁调用. 但是onMeasure一定会调用.

因为列表控件需要通过这个方法确认item的大小.

复杂的布局xml文件, 会大量消耗inflate()的耗时, 这个很容易理解.

但是onMeasure()为什么会耗时? 其实还有另外一个因素影响onMeasure()的方法耗时:=

    android:layout_width="match_parent"
    android:layout_height="wrap_content"

match_parent wrap_content代表这个布局的尺寸是不确定的, 是依赖父布局或者子View的.

这就必须遍历父布局的尺寸, 或者遍历子View来确认当前Item的大小.

这是一个很耗时的过程, 会引起卡顿.

这就是症结了. 可我们面对的场景是, 布局复杂度没有办法再减了, 再减就要砍需求了.

怎么可能砍需求...被需求砍还差不多. . .

方案

好在确认了两个关键问题点

  • inflate()
  • onMeasure()

我们可以针对这两个方法做优化.

  1. 阶段绘制
  2. 尺寸缓存

阶段绘制

针对inflate()耗时. 我们可以将Item的inflate()过程, 拆分成至少两个阶段.

  1. 滚动时仅仅初始化ItemView框架, 忽略细节布局
  2. 滚动停止后再初始化/绘制ItemView细节布局

当列表在快速滚动时, 用户其实看不清每个Item的细节. 所以只要初始化一个简单的框架即可.

框架布局简单, inflate会很快

当列表停止后, 此时, 每个正在展示的Item, 在去inflate细节布局, addView()到ItemView, 绘制细节内容即可.

阶段性绘制的方案很多, 可以监听滚动(不推荐). 或者在ViewHolder中使用handler#sendMessage()/ #removeMessage(). 不扩展了.

难点

这里比较难的点是, 列表高速滚动时. 很难确认是否有过渡绘制的情况.

这里的过渡绘制是指, 同一个viewHolder, 多次绘制了不同Item的内容数据, 但是只有最后一个Item的数据展示了.

这种情况, 之前的绘制就浪费了, 而且容易引起卡顿. 这个问题需要通过日志小心甄别.

尺寸缓存

对于列表控件来说, 其实每一个Item的大小, 大多数情况下是固定的.

此时就可以利用缓存, 来提高onMeasure()的执行效率.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if(hasCache){
        //有缓存, 直接执行缓存结果.
        setMeasuredDimension(mWidthCache, mHeightCache);
    }else{
        //没缓存, 执行一次onMeasure()
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidthCache = getMeasuredWidth();//获取缓存
        mHeightCache = getMeasuredHeight();//获取缓存
        hasCache = true
    }
}

难点

  1. 如果你的Item内容, 可能会变化, 比如有个TextView设置为GONE, 就会改变Item的大小, 此时要及时更新缓存.

  2. addView/removeView, 也要及时更新缓存.

总结

症结

Item布局复杂导致以下方法耗时卡帧

  1. inflate()耗时
  2. onMeasure()耗时

方案

  1. 阶段绘制
  2. 尺寸缓存

如此优化之后, 让你的RecycleView帧率提高到55帧+

如果没有, 建议:

  1. 继续分成N阶段绘制
  2. 检查过渡绘制

以上是关于RecycleView可优化的点的主要内容,如果未能解决你的问题,请参考以下文章

Recycleview模仿瑞幸咖啡菜单物品列表

如何让你的 RecycleView 帧率提高到55帧?

ListView嵌套RecycleView滑动卡顿问题的优化方案

ListView嵌套RecycleView滑动卡顿问题的优化方案

RecycleView性能优化

如何从 recyclerview 裁剪图像并使用新裁剪的图像更新 recyclerview?