刨根问底:Handler.postDelay(1L)和MainScope.launch { delay(1L) } 有什么不同

其实基本原理相同,对于Android来说,MainScope里挂起的回调也是通过handler到主线程looper中去执行。

这里提供一个demo程序实现,将会看到还是略有不同。

程序很简单,就是在屏幕中间位置显示一个"Hello World!"字符串,其y轴的位置会实时变化。

这里,我们用两种方式来实现y轴位置的变化,分别是Handler.postDelay和MainScope.launch { delay() }

可以自行写一下试试效果,这里将会直接给出日志情况进行分析。

这里代码有两处日志打印,分别在onDraw和move里。

  • onDraw里打印y轴的位置
  • move里打印距上一次移动/绘制的时间间隔

Handler.postDelay

逻辑就是要每隔1ms令文字移动1px。

运行后的结果如下:

可以发现实际每次打印的时间间隔是11ms左右(测试机屏幕是90hz刷新率,约11ms),并且每次都在onDraw之后,也就是一帧才移动/绘制一次。

这里也有个问题,为什么设置的延迟1ms再次执行,实际却每11ms才再次执行?

因为在move方法中有个invalidate(),这里会加入一个屏障消息(老八股),那么只有等下一帧信号来的时候,会执行view的绘制,并且才会移除屏障消息,所以,这里的消息每隔一帧才会执行一次。

MainScope.launch { delay() }

直接在mainScope中启动协程,并且move()后delay(1ms),看看效果。

这里多打印了一个日志,判断当前执行是否在主线程,很明显,是的。

另外可以看到,这里执行间隔基本是在1ms(除去中间一些计算逻辑),所以每一帧会绘制7次左右,在实际展示效果中,文字移动的速度会比Handler.postDelay的方式快7倍。

两个问题:

  1. delay是如何延迟消息并回到主线程执行的?
  2. 为什么这里就是每隔1ms执行一次

直接上源码:

可以看到,其实也是通过hander进行消息分发。

而这个HandlerContext就是Android里的Dispatcher.Main。

其实例如下:

这里MainDispatcherLoader.dispatcher将会一步步走到这:

注意这里async = true

其实这里是创建了一个异步Handler

而这个异步Handler会把每个消息都当成异步消息

所以在MainScope.launch中delay()时,会向主线程looper发送一个延迟的异步消息,那么遇到屏障消息后,此异步消息也能够照常执行。

修改测试

知道了MainScope的原理,那么对于Handler.postDelay我们也来进行修改下,将每次post的消息也改成异步消息。

运行后如图:

可以看到,此消息执行间隔也变成了1ms,说明此消息也不受屏障消息的影响了。

思考

课后思考(真心求问):

为什么Dispatcher.Main的Handler要设计成异步Handler呢?

相关推荐
大白要努力!16 分钟前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟1 小时前
Android音频采集
android·音视频
小白也想学C2 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程2 小时前
初级数据结构——树
android·java·数据结构
闲暇部落5 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX7 小时前
Android 分区相关介绍
android
大白要努力!8 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee8 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood8 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-11 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记