腾讯WeTest 安卓易学,爬坑不易——腾讯老司机的 RecyclerView 局部刷新爬坑之路

腾讯WeTest · 2016年10月20日 · 772 次阅读

针对手游的性能优化,腾讯 WeTest 平台的 Cube 工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中。
点击地址:http://wetest.qq.com/cube立即体验!


作者:Hoolly,腾讯移动客户端开发工程师。
商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处

WeTest 导读

安卓开发者都知道,RecyclerView 比 ListView 要灵活的多,但不可否认的里面的坑也同样埋了不少人。下面让我们看看腾讯开发工程师用实例讲解自己踩坑时的解决方案和心路历程。
话说有图有真相,首先来对比一下局部刷新前后的效果:
优化之前的效果:

优化之后的效果:

可以看到,优化之后,列表中的这张大图不在有一闪一闪亮晶晶的效果了!
那么,这是如何做到的呢?这是本文的重点,本文的大纲主要包括:
分析为什么会闪一下
对分析的可能造成闪动的问题进行解决
验证是否解决

一、为什么会闪一下?

我们的需求是大家已经看到了,点击打分,弹出一个对话框,点击一个分数,这时候,通过一些列复杂的转换(当然不是本文的论述的重点),这时候到了要更新列表项了,如是很自然,我们会这么做:

因为,操作的那个列表项你是知道他的 position,所以你可以这么做,(当然,我之前是直接 notifyDataSetChanged 的,这个会照成所以不不要的 item 也会刷新)然而,闪动还是出现了,那么我开始怀疑:
流传甚为广泛的一种说法,imageView 的宽高不固定导致的(wrap_content)?
这个是 RecyclerView 自带的更新动画效果导致的?
这个是因为图片加载框架 (glide 的 animte) 的动画效果导致的?
getView 中(RecyclerView 中是 onBindViewHolder)加载图片的时候,设置一个 tag,当发现这个 imageView 的 tag 和之前的 tag 一致时就不加载

二、带着思考,就去尝试吧!

1、对于第一种,我的做法是自己写了一个自定义的 imageView,重写 omMeasure 方法,如下:

因为我们的这个列表项中的图片是(高=宽)的,因此,我才这么写,这样写也有一个好处,不用在 onBindViewHolder 中去动态的计算出高度,然后在已 layoutParm 的方式设置给 imageView,相信不少小伙伴都做过了吧!
然而,遗憾的是,他并没有解决闪一下的问题!此时这个闪动的原因显然不在这里,但是这里做的,可以保留下来。

2、对于第二种说法,我参考了这里
http://stackoverflow.com/questions/29331075/recyclerview-blinking-after-notifydatasetchanged
的做法:

以及也尝试了这种

然而,那种渐变的闪动消失了,但是,取而代之的是一种更加不可接受的闪动,这里就不用 gif 展示了,因此原因也并不在此处。

3、对于对三种说法,我也去尝试了一下将 glide 加载改为:

然而得到的依然是一个失望的结果,依然没有解决闪动的问题,原因也不在此处。

4、那么,就剩下最后一个猜测了,那么会不会是它呢?那就试试吧,于是代码改为:

这里的做法其实就是设置 Tag,那么是骡子是马,拉出来溜溜吧,结果更加令人发指,如图:

好吧,此时已经有点崩溃了,显然这个也不是我要的结果,那么此时是否应该在静下来想一想,自己对于可能的几种原因做过的一些对策,是否有哪里遗漏了。经过思考,发现并没有!!那么一定是还有其他的原因,没有考虑到!
还是去翻一翻 RecyclerView 的 api 吧,我注意到了这个 api:

可以看到这里有一个 payload 的参数,use null to identify a "full" update 这是说如果传 null 就是全部更新,回过头去看一看我们之前的调用方式:

看一下源码,发现

实际上,payload 这个参数就是传的 null,那也就是说如果传一个不为 null 的参数,就可以对列表项中的具体控件更新了?
http://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec
我了解到这个方法的使用方式是这样的:

然来,onBindViewHolder 有这么一个重载方式,如是我也这么做了,在下面这个重载中,去更新我想更新的控件:

然后,更新的方式变成了这种:

是骡子是马,那就在遛一遛吧!
然而,依然是会闪一下!!!这这么会!!!还是调试一下吧,新重载 onBindViewHolder 方法有没有被执行,一更代码,发现果然没有被执行!
那么,究竟是什么鬼?去网上查了一下,有人给出了一个解决办法:
http://stackoverflow.com/questions/32463136/recyclerview-adapter-notifyitemchanged-never-passes-payload-to-onbindviewholde

需要重写这个动画,让永远返回 true,已达到 newHolder 和 olderHolder 是同一个,然而,这真的就是我的救命稻草吗?

那么,是骡子是马,拉出来溜溜吧,然而,并不是马!!进源码看一看

发现其实只要我们传入的 payload 不为空,那么返回的就是 true?重写有意义吗?显然,我重载的 onBindViewHolder 方法并没有执行的原因显然不是这个。
那么,到底,到底问题出在何处?会不会是 XrecyclerView 的问题?根据调用栈,我看到第一个 onBindViewHolder 被执行了,往上面跟,发现 XrecyclerView 的实现果然存在问题!

如图,作者仅仅只实现了,不带 payload 的方法,最后 adapter 调用的只有不带 paylaod 的方法!所以,重写一个吧!

最后!终于达到了想要的效果了,经过这次爬坑,选择一个开源的框架真滴是需要慎重再慎重。

总结

实际上 RecyclerView 做局部刷新是非常容易的,其实就是使用好带 payload 参数的这个 notifyItemRangeChanged 方法,以及 override 带 payload 的这个 onBindViewHolder 方法,在 onBindViewHolder 中去刷新你想更新的控件即可,并非是网上传闻的那些原因,当然此处爬坑时间之长,也可能更选用开源控件不当有关,所以,选择开源控件,要谨慎再谨慎!


针对手游的性能优化,腾讯 WeTest 平台的 Cube 工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中。
点击地址:http://wetest.qq.com/cube立即体验!

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册