- 时间:2020-12-16 07:13 编辑: 来源: 阅读:
- 扫一扫,手机访问
摘要:View.post() 不靠谱的地方你知道多少
[b]一、前言[/b]
有时候,我们会需要用到 [code]View.post() [/code]方法,来将一个 Runnable 发送到主线程去执行。这一切,看似很美好,它最终会通过一个 [code]Handler.post()[/code] 方法去执行,又避免我们重新定义一个 Handler 对象。
但是,从 Android 7.0(Api level 24) 开始,View.post() 将不再那么靠谱了,你 post() 出去的 Runnable ,可能永远也不会有机会执行到。
[b]二、post 在 7.0 的差异[/b]
[b]2.1 post 方法的差异[/b]
前面提到,这个问题只出现在 Android 7.0 上。那么就先从源码分析 Android 7.0 到底对 View.post() 做了什么改动。
[img]http://files.jb51.net/file_images/article/201708/2017082915025118.jpg[/img]
用 Diff 看一下它们的差异,左边是 Api Level 24+(以下简称 Api24) 的代码,右边是 Api level 23-(以下简称 Api23) 的代码。
很明显的可以看出来,它们只有在 [code]mAttachInfo[/code] 为 null 的时候,执行的逻辑才会有差异。
Api24 中,会调用 [code]getRunQueue().post(action)[/code],而 Api23 会调用[code] ViewRootImpl.getRunQueue().post(action) [/code]方法,他们的差异就在这里。
[b]2.2 Api23 post 的细节[/b]
先简单理解一下,[code]ViewRootImpl [/code]是什么。
[code]ViewRootImpl [/code]可以理解是一个 Activity 的 ViewTree 的根节点的实例。每个 ViewRootImpl 就是用来管理 DecorView 和 ViewTree。
[code]ViewRootImpl [/code]中的用来承载 Runnable 的队列是 sRunQueues ,它一个静态的变量,也就是说在 App 的生命周期内,[code]ViewRootImpl [/code]中的这个消息队列都是同一个。
再来看看前面提到的 [code]ViewRootImpl.getRunQueue().post() [/code]到底干了什么?
[img]http://files.jb51.net/file_images/article/201708/2017082915025119.jpg[/img]
[code]post() [/code]方法只是单纯的将它包装成一个 HandlerAction 对象,然后放入 mActions 这个 ArrayList 中。继续追查下去就需要知道 mActions 中添加的 HandlerAction 在何时被消费掉了。
消费 HandlerAction 的地方,是 [code]executeActions() [/code]方法。
|
[img]http://files.jb51.net/file_images/article/201708/2017082915025120.jpg?201772915627[/img]
它最终,还是调用的 [code]handler.postDelayed() [/code],这没什么好说的,关键点在于[code] executeAction() [/code]方法,是在什么时候被调用的。
[code]executeAction() [/code]是被 TraversalRunnable 调用 [code]doTraversa()[/code] ,在[code]doTraversa() [/code]方法中,进行调用的。而 TraversalRunnable 又是通过 [code]Choreographer.postCallBack() [/code]去循环调用的。这个[code] Choreographer [/code]通过 [code]doScheduleCallback()[/code] 发送一个 [code]MSG_DO_SCHEDULE_CALLBACK [/code]类型的消息循环调用,间隔就是一个 VSync 的间隔。
关于 Choreographer ,不是本文的重点,有兴趣可以单独了解一下。
所以,在 Api23 以下,[code]executeAction()[/code] 是会被循环调用,基本上其内的 mActions 只要有未执行的 Runnable 立刻就会被消费掉。
所以在 Api23 以下的设备上,View.post() 基本上是靠谱的,post 出去的 Runnable 都会有机会执行到。
[b]2.3 Api24 的细节[/b]
再来看看在 Api24 中的实现细节,在 Api24 中,调用的是[code] getRunQueue().post() [/code]方法,它操作的是一个 [code]HandlerActionQueue[/code] 对象。
[img]http://files.jb51.net/file_images/article/201708/2017082915025121.jpg[/img]
内部的结构其实和 Api23 很像,也是维护了一个 HandlerAction 的数组 mActions 。
最终消费掉 mActions 的地方,依然是一个 [code]executeActions() [/code]方法。
[img]http://files.jb51.net/file_images/article/201708/2017082915025122.jpg?20177291575[/img]
回到根本的问题,[code]executeActions() [/code]方法在什么时机会被调用到,继续追查可以看到它在 [code]View.dispatchAttachedToWindow() [/code]方法中,会被调用。
[img]http://files.jb51.net/file_images/article/201708/2017082915025123.jpg[/img]
既然,exe[code]cuteActions()[/code] 方法,在 Api24 及以上,只会在 [code]dispatchAttachedToWindow() [/code]的方法中,才有机会被调用到,而 [code]View.dispatchAttachedToWindow() [/code]方法,只有在这个 View 通过 [code]addView() [/code]等方法,加入到一个 ViewGroup 的时候,才会被调用到。这就导致写在 Layout 布局中的控件,是不会有机会再调用 [code]addView() [/code]方法的,所以它永远也得不到执行。这也就到时了 Api24 下,[code]View.post() [/code]表现的现象不一致的缘故。
[b]三、小结[/b]
[code]View.post() [/code]方法,在不同版本的差异,根本原因还是在于 Api23 和 Api24 中,[code]executeActions() [/code]方法的调用时机不同,导致 View 在没有 mAttachInfo 对象的时候,表现不一样了。
所以我们在使用的过程中需要慎用,区分出实际使用的场景,一般规范自己的代码即可:
在 View 已经被显示出来之后,再调用 [code]View.post() [/code]方法(这个时候 mAttachInfo 已经不为空了)。
尽量避免使用 [code]View.post() [/code]方法,可以直接使用 [code]Handler.post() [/code]方法来替代。
[b]总结[/b]
以上所述是小编给大家介绍的View.post() 不靠谱的地方,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程素材网网站的支持!