目前,在项目中一些主要页面(如图1 首页,采用ViewPager+TabLayout ,已实现数据懒加载)都会缓存第一页数据(网络)存DB,下次进来时会先请求DB数据,然后再请求网络数据,这样用户体验比较好。之前我们主要页面都是使用RadioPullToRefreshListView(封装的PullToRefreshListView,后面简称ListView)方式实现(刷新方式使用的是notifyDataSetChanged),这样不管DB中的数据和网路请求数据是否一样,都会刷新两次,体验上没有什么问题,自从4.0版本陆续改用RecyclerView 方式实现,发现有图片加载的地方会闪烁一下

仅仅只是用RecyclerView 替换ListView 逻辑主体并未修改,因此目光很快锁定在RecyclerView上面,网上一搜索,类似问题有很多,大多说是没有设置setStableIds(true) 和复写getItemId()导致的

照葫芦画瓢,我给对应的Adapter 设置了setStableIds(true) 和复写getItemId(),发现闪络问题依然存在,至于为什么当时没解决(其实可以解决,后面给原因),就放弃尝试这种方式。

我们都知道RecylerView  在ListView 的基础上新增了 很多种刷新方式,既然notifyDataSetChanged()不行 ,于是乎改用了notifyItemRangeChanged(int positionStart, int itemCount),发现改方法竟然解决了上述闪烁问题。

一 、对比两种刷新方式异同点

(1)notifyDataSetChanged()最后作用于onChanged


       唯独可疑的是:RecyclerView.this.setDataSetChangedAfterLayout(),将ViewHolder标记为更新或无用,暂时记录一下,后续会讲。

(2)RecyclerView 中刷新(如notifyItemRangeChanged()

notifyItemRangeChanged 等方法最后,都会调用triggerUpdateProcessor() ,由于主页面基本都是多ViewType 情况,一般不会设置HasFixedSize为true,因而最终只会调用else 分支去。

从(1)和(2)中,两种刷新差别,仅仅在于方法(1)会更改ViewHolder的标志,共同点都会调用requestLayout(),导致RecyclerView 重新测量和布局,到此还是看不出闪烁问题的原因所在。另外还有一个重要实验未说:如果单纯的只进行网络刷新的话,图像闪烁情况基本没有(页面跳转导致闪烁不明显)。种种迹象表明,可能是数据刷新过程中View未复用,从新inflate耗时亦或是其他原因,导致闪烁。

接下来,我们只能从RecyclerView 的测量和布局来分析,找出蛛丝马迹。

二、简要分析RecyclerView 的测量、布局及缓存策略

(1) RecyclerView::onMeasure()

RecyclerView 的onMeasure 相对比较简单 ,具体如下:目前官方提供的LayoutManager的mAutoMeasure为true ,项目中RecyclerView 的宽高是精确的,因而skipMeasure 为true ,直接return 了。从 RecyclerView::onMeasure() 看不出两种刷新的不同点,接下来看看 onLayout 。

(2)RecyclerView::onLayout ()

这里不打算细讲,跳过1000字 ,RecyclerView 之所以能够灵活布局,是因为它将onLayout()委托给了LayoutManager ,这里以LinearLayoutManager 为例说明:

LinearLayoutManager::onLayoutChildren()(调用链  RecyclerView::dispatchLayout()->dispatchLayoutStep2() ->onLayoutChildren()) ,在onLayoutChildren() 讲解两个比较重要的函数:detachAndScrapAttachedViews(recycler) 用于处理ViewHolder复用(View复用),fill()执行布局。

1)detachAndScrapAttachedViews()

detachAndScrapAttachedViews() 会循环遍历所有的View ,根据ViewHolder 的状态 及Adapter.hasStableIds() 来进行不同操作。

分支1:

ViewHolder 无用&&没有Remove&&hasStableIds为false ,会删除对应View,同时根据条件选择 保存在mCachedViews 和 addViewHolderToRecycledViewPool ;

分支2:

先detachView ,同时根据条件保存在 mChangedScrap 和 mAttachedScrap 中;

看到上面的条件,是不是很熟,在notifyDataSetChanged 时,会先将ViewHolder 设置成无效。在此打断点,发现 单独 notifyDataSetChanged() 情况,确实会走进if 分支(notifyItemRangeChanged()会进入else 分支),那设置hasStableIds(true)应该不会吧,发现竟然还是走进去了 ,纳尼????

仔细一看,对应的Adapter 不对,竟然是封装的RecyclerView组件,内部的WrapAdapter (方便实现加载更多等操作),对应修改组件内的WrapAdapter ,设置hasStableIds(true) 和 复写getItemId(),仍然使用notifyDataSetChanged ,闪烁问题果然没有了 。

根据detachAndScrapAttachedViews()发现,两种刷新方式不同在于对ViewHoler的缓存方式不一样,那先看看布局时是如何复用的,fill() 函数是真正进行布局的:

从上可以看到 View 是通过next()方式获取 :

next()->recycler.getViewForPosition()->tryGetViewHolderForPositionByDeadline()

     从上可以看到ViewHolder创建或复用的过程,为了弄懂里面的流程,我们接着简要介绍一下RecyclerView的四级缓存是如何实现的。

(3)RecyclerView 的四级缓存

1)屏幕内缓存

mChangedScrap: 表示数据已经改变的ViewHolder

mAttachedScrap : 表示未与RecyclerView分离的ViewHolder

2)屏幕外缓存

mCachedViews:屏幕划出屏幕时,ViewHolder会被缓存在mCachedViews  (默认 2)

3)自己实现ViewCacheExtension类实现自定义缓存

4)缓存池

tryGetViewHolderForPositionByDeadLine()中操作,可细化为如下流程图

结合 tryGetViewHolderForPositionByDeadLine()调用流程 和RecyclerView 四级缓存策略,我们很容易分析出两种刷新方式,导致的现象不一样。

三、RecyclerView notifyDataSetChanged 导致图片闪烁的真凶

结合上文所讲内容,可以很容易分析出 notifyDataSetChanged 导致图片闪烁的真凶 :

(1)仅使用notifyDataSetChanged ,在重新布局时,会先remove View ,然后根据相关场景,缓存到mCachedViews 、ViewCacheExtension(项目未实现)、缓存池pool 中;在 tryGetViewHolderForPositionByDeadLine()时,会从上述缓存中复用,根据图10,如果在pool中取不到的话,会直接CreateViewHolder,重新inflate,导致闪烁。

(2)使用notifyDataSetChanged() + hasStableIds() true +复写getItemId()  或者notifyItemRangeChanged(),会缓存在 mAttachedScrap,因此不会重新createViewHolder。

原因真的如(1)所说是重新创建CreateViewHolder,导致闪烁的吗,简单实验如下:图中Customxxxx是itemView 的顶级View。

1)notifyDataSetChanged() 刷新

发现请求网络数据后,基本上没有发现CreateViewHolder (有时候会有,日志中没有打印,这里主要展示ItemView的测量和布局),却发现了itemView 重新测量 和布局了。

2)notifyDataSetChanged() + hasStableIds() true +复写getItemId()

发现请求网络数据后,并未发现itemView 重新测量和布局

上述实验说明,图片闪烁的原因不在于此,不是因为createViewHolder 造成的 ,因为第二次网络请求回来时,pool中是有缓存的。

那么闪烁的真正原因是什么?目前还有一个疑点  ,之前 我们只是分析了ViewHolder的复用,但是拿到ViewHolder(或View)后,addView 操作两种方式是不是也存在差别   。

在layoutChunk()中,拿到view后,最终会调用addViewInt()方法,如下图所示:

1) 如果holder 来自scrap,则最终调用 RecyclerView.this.attachViewToParent(child, index, layoutParams) ,用来显示View ,与之前detachView 相对应;

2)child.getParent是当前RecyclerView ,则 仅仅移动View;

3)1)和2)都不是,则直接 RecyclerView.this.addView(child, index),导致 itemView 重新测量和布局

      至此,notifyDataSetChanged 导致图片闪烁的真凶 是itemView 重新测量布局导致的,并非createViewHolder 。

四、解决图片闪烁的几种方式

几种解决的办法:

1)notifyDataSetChanged()+SetHasStableIds(true)  + getItemId 复写

2)使用 notifyItemRangeChanged()

notifyItemRangeChanged() 一般使用在加载更多场景,如果直接使用notifyItemRangeChanged() 代替notifyDataSetChanged 会给人一种错觉,为什么不能使用notifyDataSetChanged(),不是很好理解。

3)使用diffUtil 工具类实现刷新

具体可参看我的另一篇文章 :RecyclerView + DiffUtil 使用预研

五 、关于getItemId()的疑问

为什么这里单独介绍notifyDataSetChanged + setHasStableIds(true) + 复写getItemId() 方法,这种方式解决闪烁是没有问题的,但是存在一个大大的坑。

那就是设置 setHasStableIds(true) 时,一定要保证 有一个unique  identifiters (如下图所示) ,同网上通常做法是直接复用Adapter 中的getItemId()方法,直接返回position (但是数据集的position可能会变的,并不唯一 ,大大的疑问?) 。但是,反复测试确实没有发现问题,可能是因为使用场景不同导致不易察觉。

带着疑问,Google前人杰作及官方示例代码(太简单未涉及),发现了一篇文章 Android RecyclerView 使用 ItemTouchHelper 时异常 (感谢一下),文章提及在使用ItemTouchHelper时发生闪烁数据异常,同时指出原因 是 使用setHasStableIds(true) + 复写getItemId() 返回position导致的,具体原因,并未讲明。

刚好项目中UGC 录音,音效歌曲选择那里使用到ItemTouchHelper,简单实验一下,发现果然如文章所示发生了异常,操作过程如下:滑动过程闪现闪烁,滑动停止后,发现数据异常(两个位置的数据时一样的)。

出现上述现象的原因 :

(1)在移动过程中,会调用 notifyItemMoved(fromPosition, toPosition),导致RecyclerView重新布局  ;

(2)在图9 tryGetViewHolderForPositionByDeadLine()方法中,当hasStableIds 为true 时,会通过getScrapOrCachedViewForId (如下图)获取复用viewHolder 的id  。滑动前:getItemId 为fromPosition,因此 holder.getItemId() 等于fromPosition,由于滑动RecyclerView.this.mAdapter.getItemId(offsetPosition)中的offsetPosition发生了改变,导致 id不等于fromPosition,最终导致getScrapOrCachedViewForId()为null

这一步获取失败后,就会从缓存池pool 中获取,会重新addView ,导致 测量和布局,因此上述拖动过程会发生闪烁。

既然是因为getItemId()返回position导致的,那就返回一个唯一值不就可以了 ,返回 第postion位置data的hashCode不就可以吗  (在DB+网络情况,data内容相同,但是它的hashCode 是不同的),如果这里有更好的表示返回值方式请告诉我 !

由此得出结论 :notifyDataSetChanged + setHasStableIds(true) + 复写getItemId() 方法 并非是万能的,其解决还是要区分场景。

RecyclerView notifyDataSetChanged 导致图片闪烁的原因相关推荐

  1. RecyclerView notifyDataSetChanged 图片重新加载问题

    最近写了一个 图片多选下载的需求, 类似这样,长按图片,或点击下载,变成下面的样式 这个效果肯定是recyclerView  notifyDataSetChanged去改变状态,但是如果不处理 图片会 ...

  2. 画图时屏幕闪烁的原因和解决方法

    显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题.而且多数人认 为MFC的绘图函数效率很低,总是想寻求其它的解决方案. MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,只要使用 ...

  3. VRay动画渲染闪烁的原因和解决办法

    3DMAX+VRayVRay动画渲染闪烁的原因和解决办法 原因1:VRay动画渲染闪烁是因为发光贴图采样的不连续造成的:(标准情况) 原因2:摄像机运动,物体和光源都在变化和运动,其实还有种情况是场景 ...

  4. linux下虚拟lcd屏幕总线错误,LCD显示屏出现闪烁的原因与解决

    比较CRT与LCD两类显示屏时,其中一项最为普遍见到的差别是闪烁问题.一般都会以为CRT显示屏有闪烁,而LCD则没有,查实两类显示屏皆有某程度上的闪烁,机制上的差异和纠正方法影响到成功率.本文讲述LC ...

  5. recycler 刷新图片闪烁_android 解决RecyclerView notifyDataSetChanged刷新闪屏问题(图片刷新)...

    最近遇到了RecyclerView  在执行notifyDataSetChanged操作时会出现闪屏,图片刷新,然后根据网上资料提示使用 修改动画,设置动画时间 recyclerView.getIte ...

  6. CoreAnimation中layer动画闪烁的原因及解决

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 网上有一段Core Animation层动画的例子,是将vie ...

  7. LED调光闪烁的原因

    转载--电源研发精英圈 2016-09-07 21:09 大家是不是在设计led调光电源的时候,电源会出现频闪的现象呢?通常人眼能够感知到频率达70Hz的光闪烁,高于这个频率则不会感知.故在LED照明 ...

  8. win10鼠标右边光圈一直闪烁,原因是

    自从更新系统后,鼠标右边光圈一直在闪烁,在百度上看到了好多方案,都没能解决我的问题,但是总结下来都是--有某个服务在作怪. 但是,服务这么多,总不能一个个试吧!有些还是必须启动的服务! 最后也是用了排 ...

  9. 关灯后灯常亮、微亮、闪烁——多种原因分析

    关灯后电灯还是发光,算得上是历史悠久的故障了.许多人就此事问过我,由于可能性较多,一直不敢回答.今天斗胆献丑,把我自己能够想到的可能性一一列举,不全之处,欢迎补充.转自工控资料窝公众号. 叶子分割线 ...

最新文章

  1. 大数据领域75个核心术语讲解!
  2. LeetCode—1290.二进制链表转整数(Java)
  3. java第六章工具包6.2.Collection Streams 2020.4.7
  4. linux xampp nginx,nginx配置教程_如何配置nginx_nginx安装与配置详解
  5. D. Omkar and Medians
  6. y空间兑换代码_loam代码解析3
  7. RxJS的另外四种实现方式(一)——代码最小的库
  8. 买手机需要128 G的存储空间吗?
  9. MVC4 EF6 MYSQL
  10. Java加密知识体系
  11. foobar2000使用cue文件播放时出现Unable to open item for playback (Object not found):的问题解决
  12. es的插件 ik分词器的安装和使用
  13. 零厨艺也只需10分钟就能让厨房剩货大变身——牛肉饭
  14. 基于J2EE人事考勤管理系统设计与实现(论文+项目源码) 运行环境: idea ,maven,mysql
  15. QQ空间自动点赞脚本
  16. linux io apic,linux-kernel – IO-APIC级别与PCI-MSI-X之间的差异
  17. JQuery实现音乐导航栏的简单实现
  18. 《HeadFirst设计模式》读书笔记-第9章v1-迭代器模式
  19. java编写的网页版 纯色背景图片去除底色工具,变成透明背景的图片工具发布...
  20. 分布式事务、基于Best Efforts 1PC模式的事务

热门文章

  1. android仿最新版本微信相册--附源码
  2. 清除电脑bios密码
  3. ERROR: Cannot uninstall ‘wrapt‘. It is a distutils installed project and thus we cannot accurately d
  4. 阿波罗数据集怎么下载_百度阿波罗发布全球最大的无人驾驶汽车数据集
  5. decimal在java怎么用?
  6. 后缀表达式求值和转换(C++)
  7. 精密仪器及机械类毕业论文文献包含哪些?
  8. 大内高手—内存管理器
  9. 论文 PPT 画图导出 PDF 注意事项
  10. ubuntu php7 pdo,记录捣鼓ubuntu下PHP7.1下安装sqlserver拓展