View.post() 不靠谱的地方你知道吗?

版权声明:

本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。

每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章。

未经允许,不得转载。

这篇文章之前发过一遍,但是有读者指出来有些地方描述的有问题,我后来再看的时候也觉得有问题,所以把之前的文章删掉(主线是没有问题的,删掉只是是避免更多的人误会),准备修改勘误之后,再重新发布一遍,这次会补齐描述问题的 Demo 。

有问题继续文章后面留言,再次感谢细心的读者指出文章内的错误。

一、前言

有时候,我们会需要用到 View.post() 方法,来将一个 Runnable 发送到主线程去执行。这一切,看似很美好,它最终会通过一个 Handler.post() 方法去执行,又避免我们重新定义一个 Handler 对象。

但是,在 Android 7.0(Api level 24) 上,View.post() 将不再那么靠谱了,你 post() 出去的 Runnable ,可能永远也不会有机会得到执行。我们先来看看它们的细节。

二、post 在 7.0 的差异

2.1 post 方法的差异

前面提到,这个问题只出现在 Android 7.0 上。那么就先从源码分析 Android 7.0 到底对 View.post() 做了什么改动。

/p-postmethpd.png

用 Diff 看一下它们的差异,左边是 Api Level 24(以下简称 Api24) 的代码,右边是 Api level 23-(以下简称 Api23) 的代码。

很明显的可以看出来,它们只有在 mAttachInfo 为 null 的时候,执行的逻辑才会有差异。

Api24 中,会调用 getRunQueue().post(action),而 Api23 会调用 ViewRootImpl.getRunQueue().post(action) 方法,他们的差异就在这里。

2.2 Api23 post 的细节

先简单理解一下,ViewRootImpl 是什么。

ViewRootImpl 可以理解是一个 Activity 的 ViewTree 的根节点的实例。每个 ViewRootImpl 就是用来管理 DecorView 和 ViewTree。

ViewRootImpl 中,用来承载 Runnable 的队列是 sRunQueues ,它一个静态的变量,也就是说在 App 的生命周期内,ViewRootImpl 中的这个消息队列都是同一个。

再来看看前面提到的 ViewRootImpl.getRunQueue().post() 到底干了什么?

/p-23postqueue.png

post() 方法只是单纯的将它包装成一个 HandlerAction 对象,然后放入 mActions 这个 ArrayList 中。继续追查下去就需要知道 mActions 中添加的 HandlerAction 在何时被消费掉了。

消费 HandlerAction 的地方,是 executeActions() 方法。

/p-23execute.png

它最终,还是调用的 handler.postDelayed() ,这没什么好说的,关键点在于 executeAction() 方法,是在什么时候被调用的。

executeAction() 是被 TraversalRunnable 调用 doTraversa() ,在doTraversa() 方法中,进行调用的。而 TraversalRunnable 又是通过 Choreographer.postCallBack() 去循环调用的。这个 Choreographer 通过 doScheduleCallback() 发送一个 MSG_DO_SCHEDULE_CALLBACK 类型的消息循环调用,间隔就是一个 VSync 的间隔。

关于 Choreographer ,不是本文的重点,有兴趣可以单独了解一下。

而在 Api23 以下,executeAction() 是会被循环调用,基本上其内的 mActions 中,只要有未执行的 Runnable 立刻就会被消费掉。

所以在 Api23 以下的设备上,无论如何 View.post() 基本上是靠谱的,post 出去的 Runnable 都会有机会执行到。

2.3 Api24 的细节

再来看看在 Api24 中的实现细节,在 Api24 中,调用的是 getRunQueue().post() 方法,它操作的是一个 HandlerActionQueue 对象。

/p-24add.png

内部的结构其实和 Api23 很像,也是维护了一个 HandlerAction 的数组 mActions 。

最终消费 mActions 的地方,依然是一个 executeActions() 方法。

/p-24execute.png

回到根本的问题,executeActions() 方法在什么时机会被调用到,继续追查可以看到它在 View.dispatchAttachedToWindow() 方法中,会被调用。

/p-24done.png

既然,executeActions() 方法,在 Api24 及以上,只会在 dispatchAttachedToWindow() 的方法中,才有机会被调用到,而 View.dispatchAttachedToWindow() 方法,只有在这个 View 通过 addView() 方法,或者原本写在页面布局的 xml 中(实际上也是调用的 addView()),加入到一个 ViewGroup 的时候,才会被调用到。

这就导致,如果你只是通过 new 或者使用 LayoutInflater 创建了一个 View ,而没有将它通过 addView() 加入到 布局视图中去,你通过这个 View.post() 出去的 Runnable ,将永远不会被执行到。 这也就是到了 Api24 下,View.post() 表现的现象不一致的缘故。

三、举个例子说明问题

既然只是复现这个问题,秉承最小改动原则,构造一个最简单的场景,单独 new 一个 View 出来,然后通过它去调用 post() 方法,看看执行的结果。

/p-demo.png

可以看到,这里直接 new 了一个 View,然后 post 出去了一个 Runnable ,间隔 10s 之后,将这个 View 加入到根布局中。

看看在 Api 23 下的执行效果:

/p-demo23.png

可以看到,在 Api 23一下,这里是 Api19,新 new 出来的 View 对象,post 出去的 Runnable ,会立即得到执行,不需要等待 addView() 的执行。

再来看看在 Api24 下的执行效果:

/p-demo24.png

从执行时间上可以看出来,post 出去的 Runnable ,并不是立即被执行了,而是等到了 addView() 的调用之后,才被执行的,这个中间正好被间隔了 10s。

据说这个问题,在 Android 8.0 上又被修改回去了,专门找了一款 8.0 的设备试试运行结果,如下图:

此处缺个图。

25 是 Android 8.0 的预览版,这里可以看到,依然是和在 7.0 上的表现一样,会等到最终 addView() 的时候再执行,正式版不知道会不会有所改动,这个还有待验证。

基本上确定,受到影响的是 Android Api 24+,但是依然是开发者需要注意的,毕竟发布出去的 App ,具体运行在什么设备上,这就不是我们能决定的了。

四、小结

View.post() 方法,在不同版本的差异,根本原因还是在于 Api23Api24 中,executeActions() 方法的调用时机不同,导致 View 在没有 mAttachInfo 对象的时候,表现不一样了。

所以我们在使用的过程中需要慎用,区分出实际使用的场景,一般规范自己的代码即可:

  1. 动态创建的 View ,如果视条件去决定是否加入到根布局中,则不要使用它来调用 post() 方法。
  2. 尽量避免使用 View.post() 方法,可以直接使用 Handler.post() 方法来替代。
公众号二维码.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容