由View的onAttachedToWindow引发的图片轮播问题探究
由View的onAttachedToWindow引发的图片轮播问题探究
文章目录
- 由View的onAttachedToWindow引发的图片轮播问题探究
- 前言
- Handle#removeCallbacks
- 待解决问题
- View#dispatchAttachedToWindow
- 再说post和postDelayed
- 话说RecycleView
前言
本篇文章是在View的postDelayed方法深度思考这篇文章的所有的基础理论上进行研究的,可以说是对于View的postDelayed方法深度思考这篇文章知识点的实践。
某天同事某进在做一个列表页添加轮播Banner
的需求的时候,发下偶尔会出现轮播间隔时间错乱的问题。
我看了他的轮播的实现方案:利用Handle.postDelayed
间隔轮播时长每次执行完轮播之后再次循环发送;
代码貌似没有太大问题,但通过现象看来应该是removeCallbacks
失效了~!
Handle#removeCallbacks
在stackoverflow
上找了相关资料Why to use removeCallbacks() with postDelayed()?,之后尝试将postDelayed
不靠谱那么改为post
,发现貌似轮播间隔时间错乱的问题解决了~!
虽然不清楚什么原因导致问题不再出现,但后续因为其他工作打断未能继续排查下去。
若干天之后,再次发现轮播间隔时间错乱的问题有一次出现了。
这次我们使用自定
Handler
进行removeCallBacks
和postDelayed
,完美的解决了问题。
下面记录一下整问题解决过程中的思考~!
待解决问题
View.removeCallbacks
是否真的可靠;View.post
和View.postDelayed
相比为什么bug复现频率更低;
View#dispatchAttachedToWindow
Handle
的removeCallBacks
移除方法是不可靠的么?如果当前的任务不是在执行中,那么该任务一定会被移除。
换句话说,Handle#removeCallBacks
移除的就是在队列中等待被执行的Message
。
那么问题到底出在哪里,而且为什么
postDelayed
替换为post
问题的复现概率降低了?
这次有些时间,跟了一下源码发现使用View#postDelayed
发送的消息不一定会立即被放在消息队列。
回顾之前View的postDelayed方法深度思考这篇文章中关于View.postDelayed小结
中的描述:
postDelayed
方法调用的时候,如果当前的View
没有依附在Window
上的时候,先将Runnable
缓存在RunQueue
队列中。等到View.dispatchAttachedToWindow
调用之后,再被ViewRootHandler
进行一次postDelayed
。这个过程中相同的Runnable
只会被postDelay
一次。
我们打印stopTimer
和startTimer
方法执行的时ViewPager#getHandler
的Handler
实例,发现在列表快速滑动时大部分为null
。
好吧,之前忽略了这个Banner
在滑动过程中的被View#dispatchDetachedFromWindow
。这个方法的调用会导致View
内部的Handle
为null
。
如果View
的Handle
为null
,那么Message
的执行可能会收到影响。
在View的postDelayed方法深度思考这篇文章中关于mAttachInfo
对于View.postDelayed
的影响,也都进行了分析。这里我们捡主要的源码阅读一下。
//View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {mAttachInfo = info;/****部分代码省略*****/// Transfer all pending runnables.if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}performCollectViewAttributes(mAttachInfo, visibility);onAttachedToWindow();/****部分代码省略*****/
}
public boolean postDelayed(Runnable action, long delayMillis) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.postDelayed(action, delayMillis);}// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after attach.getRunQueue().postDelayed(action, delayMillis);return true;
}
public boolean post(Runnable action) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after attach.getRunQueue().post(action);return true;
}
public boolean removeCallbacks(Runnable action) {if (action != null) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {attachInfo.mHandler.removeCallbacks(action);attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, action, null);}getRunQueue().removeCallbacks(action);}return true;
}
post
和postDelayed
在View的postDelayed方法深度思考这篇文章中进行过讲解,会在View
执行dispatchAttachedToWindow
方法的时候执行RunQueue
中存放的Message
。
RunQueue.executeActions
是在ViewRootImpl.performTraversal
当中进行调用;
RunQueue.executeActions
是在执行完host.dispatchAttachedToWindow(mAttachInfo, 0);
之后调用;
RunQueue.executeActions
是每次执行ViewRootImpl.performTraversal
都会进行调用;
RunQueue.executeActions
的参数是mAttachInfo
中的Handler
也就是ViewRootHandler
;
从这里看也是没有任何问题的,我们使用View#post
的消息都会在View
被Attached
的时候进行执行;
一般程序在开发的过程中,如果涉及容器的使用那么必然需要考虑的生产和消费两个情况。
上面的源码我们是看了到了消息被执行的逻辑(最终所有的消息都会被放在MainLooper
中被消费),如果涉及消息被移除呢?
public class HandlerActionQueue {public void removeCallbacks(Runnable action) {synchronized (this) {final int count = mCount;int j = 0;final HandlerAction[] actions = mActions;for (int i = 0; i < count; i++) {if (actions[i].matches(action)) {// Remove this action by overwriting it within// this loop or nulling it out later.continue;}if (j != i) {// At least one previous entry was removed, so// this one needs to move to the "new" list.actions[j] = actions[i];}j++;}// The "new" list only has j entries.mCount = j;// Null out any remaining entries.for (; j < count; j++) {actions[j] = null;}}}
}
移除消息的时候如果当前View
的mAttahInfo
为空,那么我们只会移除RunQuque
中换缓存的消息。。。
哦哦
原来是这样啊~!
确实只能这样~!
总结一下,如果View#mAttachInfo
不为空那么你好,我好,大家好。否则View#post
的消息会在缓存队列中等待被添加,但移除的消息却只能移除RunQueue
中缓存的消息。如果此时RunQueue
中的消息已经被同步到MainLooper
中那么,抱歉没有View#mAttachInfo
臣妾移除不了呀。
按照之前的业务代码,如果当前
View
被dispatchDetachedFromWindow
之后执行消息的移除操作,那么已经在MainLooper
队列中的消息是无法被移除且如果继续添加轮播消息,那么就会造成轮播代码块的频繁执行。
文字描述可能一时间不太容易理解,下面是一次超预期之外的轮播(为什么会有多个轮播消息)流程简单的分析图:
再说post和postDelayed
如果只看相关源码我感觉是发现不了问题了,因为post
最后执行的也是postDelayed
方法。所以两者相比只不过时间差而已,这个时间差能造成什么影响呢?
回头看了看自己之前写的文章又一年对Android消息机制(Handler&Looper)的思考,其中有一个名词叫做同步屏障。
同步屏障:忽略所有的同步消息,返回异步消息。再换句话说,同步屏障为
Handler
消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。
而同步屏障用的最多的就是页面的刷新(ViewRootImpl#mTraversalRunnable
)相关文章可以阅读Android系统的编舞者Choreographer,而ViewRootImpl的独白,我不是一个View(布局篇)这篇文章讲述了View#dispatchAttachedToWindow
的方法就是由ViewRootImpl#performTraversals
触发的。
为什么要说同步屏障呢?上面的超预期轮播的流程图中可以看出View#dispatchAttachedToWindow
的方法调用对于整个流程非常重要。移除
和添加
两个消息两个如果由于postDelayed
导致中间有其他消息的插入,而同步屏障是最有可能被插入的消息且这条消息会使View#mAttachInfo
产生变化。
这就使原来有些小问题的代码雪上加霜,bug更容易复现。
话说RecycleView
为什么要提到这个问题,因为好多时候我们使用View.post
执行任务是没有问题(PS:我感觉这个观点也是这个问题产生的最初的源头)。
我们知道RecycleView
的内部子View
仅仅是比屏幕大小多出一条预加载View
,超过这个范围或者进入这个范围都会导致View
被添加和移除。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {/***部分代码省略***/private void initChildrenHelper() {this.mChildHelper = new ChildHelper(new Callback() {public int getChildCount() {return RecyclerView.this.getChildCount();}public void addView(View child, int index) {RecyclerView.this.addView(child, index);RecyclerView.this.dispatchChildAttached(child);}public int indexOfChild(View view) {return RecyclerView.this.indexOfChild(view);}public void removeViewAt(int index) {View child = RecyclerView.this.getChildAt(index);if (child != null) {RecyclerView.this.dispatchChildDetached(child);child.clearAnimation();}RecyclerView.this.removeViewAt(index);}}/***部分代码省略***/}/***部分代码省略***/
}
如果我们频繁来回滑动列表,那么这个Banner
会不断的被执行dispatchAttachedToWindow
和dispatchDetachedToWindow
。
这样导致View#mAttachInfo
大部分时间为null
,从而影响到业务代码中往主线程中发送的Message
的执行逻辑。
文章到这里就讲述的差不多了,解决这个问题给我带来的感受挺深刻的,之前学习Android系统的相关源码只不过是大家都在学、面试都在问。
能在应用到实际研发过程中涉及到的知识点还是比较少,好多情况下都是能解决问题就行,也就是知其然而不知其所以然。
这次解决的问题能让我深切感受到fuck the source code is beatifully
。
文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!
2023年祝你在新一年心情日新月异,快乐如糖似蜜,朋友重情重义,爱人不离不弃,工作频传佳绩,万事称心如意!
由View的onAttachedToWindow引发的图片轮播问题探究相关推荐
- 制作支持View,图片轮播的Banner
之前有一个项目中有用到轮播,不过不是简单的轮播图片就完了,而是要轮播很多个View,一开始我的想法和大家一样在github在一个算了,哈哈,不过在试用了很多个项目之后都觉得不能完全满足我的需求,大部分 ...
- android首页图片轮播效果,Android_Android自动播放Banner图片轮播效果,先看一下效果图支持本地图 - phpStudy...
Android自动播放Banner图片轮播效果 先看一下效果图 支持本地图片以及网络图片or本地网络混合. 使用方式: android:id="@+id/banner" andro ...
- 【Swift】自定义控件无限轮播 + 无限图片轮播
CocoaPods安装 pod 'PGBannerSwift' 复制代码 使用 1.无限图片轮播 首先引入import PGBannerSwift let banner = PGBanner(fram ...
- Android 使用ViewPager 做的半吊子的图片轮播
Android 使用ViewPager 做的半吊子的图片轮播 效果图 虽然不咋样,但是最起码的功能是实现了,下面我们来一步步的实现它. 界面 下面我们来分析一下界面的构成 整体的布局: 因为我们要做出 ...
- 安卓首页图片轮播效果(淘宝、京东首页广告效果)
2019独角兽企业重金招聘Python工程师标准>>> 直奔主题: 1.主要原理就是利用定时任务器定时切换ViewPager的页面. 2.里面用了一个读取网络图片的插件,做客户端使用 ...
- 关于图片轮播的几种思路
图片轮播我们经常在众多网站中看到,各种轮播特效在有限的空间上展示了几倍于空间大小的内容,并且有着良好的视觉效果.很多初学js的小伙伴都会拿这个来练习.我也不例外,所以在此分享几个我写轮播图的过程,代码 ...
- 图片轮播器,relativelayout,外加textview小结
十多个textview,外加三个relativelayout,心烦的是图片轮播器,就环境老玄了,写了三个图片轮播器才成功,直接来代码吧,gogogo. Activity_main.xml <?x ...
- java postdelayed_Android开发使用Handler的PostDelayed方法实现图片轮播功能
本文实例讲述了Android开发使用Handler的PostDelayed方法实现图片轮播功能.分享给大家供大家参考,具体如下: 第一步:创建MainActivity类 public class Ba ...
- android 图片轮播
今天,简单讲讲Android图片轮播功能. 其实图片轮播功能就是使用ViewPager和Handler,网上查找了资料,把这个简单的问题写的很复杂.其实一个自定义控件完全可以解决.这里直接上代码,不在 ...
最新文章
- window.unload ajax不执行_AJAX
- oracle 10 升级补丁
- 【unity基础系列】1、unity Texture Type设置为Advanced时纹理的格式列表
- Linux Centos7安装hadoop3.x安装流程(跪舔文)
- u-boot中filesize环境变量
- shell脚本的一些用法笔记
- 【kafka】InconsistentGroupProtocolException: The group member‘s supported protocols
- mysql innodb禁用事务_MySQL InnoDB事务中锁问题(三)
- springBoot 注入@Slf4j,框架中的方法无法调用。。。
- ACL 2019 | 清华与华为提出ERNIE:知识图谱结合BERT才是「有文化」的语言模型
- yii2 联表查询时,查询关联表的字段时,使用asArray方法
- 凸优化第三章凸函数 3.6关于广义不等式的凸性
- linux命令行添加管理员用户,Linux基础命令---添加用户useradd
- 如何在css中将图片横向摆放,css如何将图片横向平铺?
- Java编码规范总结(腾讯+阿里)
- k2p拆机ttl刷breed_【1.10】k2p A版 22.10.3.42;22.10.3.38;拆机TTL刷BREED;B版 21.6.25.20刷机 图文教程...
- UR5 UR10 ------六自由度机械臂
- 到底程序员的工资有多高?你不了解的程序员!
- c加加语言complex的用法,complex的用法总结大全
- 学习神经网络(深度学习)电脑的配置要求
热门文章
- 负数的原码反码和补码
- 樱顶老图正在沦为公共厕所
- python提取html文件里的csv数据_文件(csv、excel、xml、html)的读取(read)和写入(write)方法——python...
- Misc_冰蝎流量分析
- 阿里的数据中台正在背离初心
- idea中连接数据库并展示数据库表的信息
- 孙溟㠭先生禅意篆刻——正
- 线性表顺序存储结构的基本操作(C++)
- 河池南丹亩产500公斤 国稻种芯·中国水稻节:广西米新品种
- Oracle入门精读24-数据库名,数据库实例名,数据库服务名等概念区别