View.post() 不靠谱的地方你知道吗?
版权声明:
本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。
每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章。
未经允许,不得转载。
一、前言
有时候,我们会需要用到 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()
做了什么改动。
用 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()
到底干了什么?
post()
方法只是单纯的将它包装成一个 HandlerAction 对象,然后放入 mActions 这个 ArrayList 中。继续追查下去就需要知道 mActions 中添加的 HandlerAction 在何时被消费掉了。
消费 HandlerAction 的地方,是 executeActions()
方法。
它最终,还是调用的 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 对象。
内部的结构其实和 Api23 很像,也是维护了一个 HandlerAction 的数组 mActions 。
最终消费掉 mActions 的地方,依然是一个 executeActions()
方法。
回到根本的问题,executeActions()
方法在什么时机会被调用到,继续追查可以看到它在 View.dispatchAttachedToWindow()
方法中,会被调用。
既然,executeActions()
方法,在 Api24 及以上,只会在 dispatchAttachedToWindow()
的方法中,才有机会被调用到,而 View.dispatchAttachedToWindow()
方法,只有在这个 View 通过 addView()
等方法,加入到一个 ViewGroup 的时候,才会被调用到。这就导致写在 Layout 布局中的控件,是不会有机会再调用 addView() 方法的,所以它永远也得不到执行。这也就到时了 Api24 下,View.post() 表现的现象不一致的缘故。
三、小结
View.post()
方法,在不同版本的差异,根本原因还是在于 Api23 和 Api24 中,executeActions()
方法的调用时机不同,导致 View 在没有 mAttachInfo 对象的时候,表现不一样了。
所以我们在使用的过程中需要慎用,区分出实际使用的场景,一般规范自己的代码即可:
- 在 View 已经被显示出来之后,再调用 View.post() 方法(这个时候 mAttachInfo 已经不为空了)。
- 尽量避免使用 View.post() 方法,可以直接使用 Handler.post() 方法来替代。
View.post() 不靠谱的地方你知道吗?的更多相关文章
- UC何小鹏:移动互联网创业需警惕五大“不靠谱
http://tech.qq.com/a/20140121/012443.htm 腾讯科技 启言 1月21日报道 移动互联网创业很容易犯错误,一不小心就陷入“坑”中.UC也是如此.近日,UC创始人何小 ...
- 不靠谱的Paypal及海外网站
昨天因为报名参加某个比赛,需要用到Paypal付款,整整折腾了我一个小时.我先是使用自己的银行储蓄卡,到最后一步需要银行确认,提示我需要安装个插件才能输入密码.安装完插件后需要重启浏览器,整个流程又要 ...
- DateTimePicker.Text不靠谱
DateTimePicker.Text不靠谱 获取时:在DateTimePicker.ValueChanged事件中,获取到的Text有可能是string.Empty!!!,特别当ValueChang ...
- 参加Java培训到底靠不靠谱?
导读 科技越发展,社会越进步,人们越便利,便衍生出更多的人从事程序员这个高大上的职业,可哈尔滨Java培训学校这么多,到底靠不靠谱,会不会处处是陷阱,爱尚实训帮你擦亮眼 随着时代的发展,越来越多的人对 ...
- mysql进阶(十)不靠谱的FLOAT数据类型
今天在设计数据表时,突然发现原来FLOAT原来是很不靠谱的,所以在这里建议大家换成DOUBLE类型, 原因是: 在mysql手册中讲到,在MySQL中的所有计算都是使用双精度完成的,使用float(单 ...
- Shell脚本中字符串判空:使用-z 字符串长度为0时,为真,-n字符串长度不为0,为真。这两个都不靠谱【转】
最近发现使用 -z 和 -n 来判断字符串判空,或不空时,很不靠谱. 使用下面的方法最可靠: if [ "x${value}" == "x" ] ...
- 出租WiFi到底靠不靠谱?
创业是一种心态,也是不断的探索,他融入我们的生活,从日常中积累,从小微处启航. 一.背景交代 最近在换工作,本周搬到新租的单身公寓,空间不大,倒是干净整洁.委托租房中介帮忙开通宽带,告知是电信网最低开 ...
- SimpleDateFormat之后为何多了一年,难道Java API也这么不靠谱?
这一切的背后到底是机器故障,还是程序的bug? 难道Java API也不靠谱 朋友在我博客上发现一时间明显错误,操作时间怎么会是2016年?在同一个for循环输出到页面的时间,唯独这一个时间不对,整整 ...
- DevOps|从特拉斯辞职风波到研发效能中的不靠谱人干的荒唐事
今天发生了一件大事特拉斯辞任英国首相,我想借着这件事情说下我看到的一件研发效能的荒唐事,这其中的关联也许就是「都用了不靠谱的人」. 两件事情 今儿一早就听到,2022年10月20日英国第78任首相伊丽 ...
随机推荐
- Dapper.Rainbow 简单使用
一. Dapper 简介 一个效率比较高的微型ORM. 二 . Dapper.Rainbow Dapper的扩展,在这个扩展里面实现了 Dynamic 的 插入和更新 ...
- 输出a-b之间的随机数并考虑异常
输出a-b之间的随机数并考虑异常 代码如下: package Day05;import java.util.Scanner;import java.util.Random; public class ...
- Android--Intent组件带参传递与返回
Android 是 单例模式: 表示 application 唯一的.每个应用被启动的时候,其实是 application 被创建. Context 上下文: context 是 Applicatio ...
- 浮点数的陷阱--double i != 10 基本都是对的,不管怎么赋值
#include <stdio.h>int main(){ double i; for(i = 10; i != 10, i < 12; i += 0.1) ...
- SQL注入的各种类型的检测方式
#SQL注入各个类型检测方式 http://127.0.0.1/day6/1.php?id=1 union select 1,name,pass from admin 数字型 数字型不用特意加字符,直 ...
- iOS开发实战-时光记账Demo 本地数据库版
现在记账APP也是用途比较广泛 自己写了个简单的demo 欢迎指正 效果 分析 1.思维推导 首先简单的做了下思维推导 2.文件结构 大致框架想好后就可以着手开始准备了 数据库管理:coreData ...
- ubuntu下处理mysql无法启动故障一例
故障现象: mysql无法启动 1: dmesg |grep mysql [101353.820000] init: mysql post-start process (9077) terminate ...
- iOS字典转模型MJExtension使用
如果项目是纯OC的建议使用,MJExtension是一套字典和模型之间互相转换的超轻量级框架,可以轻松完成: 字典(JSON) --> 模型(Model) 模型(Model) --> 字典 ...
- 时间戳,取值问题 and 倒计时的前端处理
JavaScript 获取当前时间戳: 第一种方法: var timestamp = Date.parse(new Date()); 获取的时间戳是把毫秒改成000显示, 结果:12809773300 ...
- Tornado 判断用户登录状态和操作权限(装饰器)
判断是否登录: def authenticated(method): '''''' @functools.wraps(method) def wrapper(self, *args, **kwargs ...