当前位置: 首页 > news >正文

Android Handler完全解读

一,概述

Handler在Android中比较基础,本文笔者将对此机制做一个完全解读。读者可简单参考上述类图与时序图,便于后续理解。

二,源码解读

1,主线程伊始

众所周知,通过Zygote的fork方式,新创建的子进程通过反射获取到ActivityThread的main静态方法,作为caller在Zygote中使用,

我们跟进到ActivityThread#main

调用prepareMainLooper创建主线程looper,

很简单,通过ThreadLocal保证了线程唯一,

笔者在此啰嗦下ThreadLocal原理,Thread内部默认存在一个ThreadLocalMap,线程唯一。set处,通过将ThreadLocal对象作为key设置进Thread#threadLocalMap,下次get时从threadLocalMap将ThreadLocal对象作为key传入,便能获取到set的值。

我们继续分析Looper构造方法,

很简单,创建了一个消息队列,因此MessageQueue也是线程唯一。参数quitAllowed为false,主线程不允许退出。接下来的核心是loop方法,我们跟进。

此处存在无限循环,当loopOnce返回false时,才退出此循环。后面读者会知道,通过主动调用quite方法,此处将返回false。

2,消息的处理

MessageQueue#next是一个阻塞方法,当mQueue返回null时退出,否则会调用到如下逻辑

target是handler,跟进

msg.callback是通过handler.post方法设置的,因此handleCallback简单run,如果设置了mCallback,显然mCallback优先级高于handleMessage方法。

3,消息的到来

回到前述过程,MessageQueue#next方法,这非常重要,我们看下实现。

记住nextPollTimeoutMillis参数,这个是在下文计算nextPollTimeoutMillis值(队头的when字段与当前时间now作差值)。其实整个Looper的底层阻塞实现类似object.wait或condition.await方法,是通过epoll的epoll#await方法实现,epoll#await接收一个参数,当为0时无限等待,否则是一个超时阻塞方法,直到存在事件会唤醒,感兴趣的读者可以去主动了解下linux下的epoll多路复用机制。不过此处,读者简单理解为Object.wait/notify即可。

如果已经唤醒,检查到mMessage(消息头)存在target==null的情况,这就遇到了消息屏障,接下来的逻辑是往后遍历,直到发现一个异步消息,优先处理异步消息。而消息屏障的插入方法在MessageQueue#postSyncBarrier,通常是系统调用,如VIewRootImpl#performTrasfer方法。

笔者假设MessageQueue插入了一个延迟消息,这时MessageQueue内部调用nativeWake方法,nativePollOnce返回,但由于消息延迟,因此计算出nextPollTimeoutMillis重新进入超时阻塞,标记mBlocked为true。否则,返回此消息,标记mBlocked为false,因此此时MessageQueue已经退出阻塞状态,

因此完成了一轮消息处理,直到下次再调用到nativcePollOnce方法进入阻塞。

4,quit相关

通过设置mQuittig为true,然后调用nativeWake将阻塞状态的Queue唤醒,

返回null,进而让Looper#loopOnce返回false,进而退出looper,笔者在这里解释下safe参数。

当safe为true时,只移除msg.when>SystemClock.updateMillis(now)的消息,即当前的消息在执行完毕后才退出,否则移除全部消息,直接退出。

三,相关热门问题的回答

1,主线程Looper.loop无限阻塞不会产生ANR吗?

不会,anr的本质是处理消息超时,此处的阻塞还没有新消息,怎么可能ANR。那消息是怎么到来的呢?一般是用户操控了手机,引发传感器逻辑,system_service进行处理,将要执行的事务通过Binder通知给对应App进程,如ActivityThread#H这个handler,通过Looper发送一个消息,而引发了消息处理的过程。

2,quit和quitSafey区别

是否执行当前when字段满足条件的消息,safe为true时执行,否则不执行。

3,消息屏障是什么?

target为null的消息,优先让异步消息执行。

4,Looper线程唯一吗?

唯一,通过ThreadLocal实现。

5,MessageQueue内部的队列是什么形式?

单链表的优先级队列,Message#when字段作为权重。

如下,从队头开始向后遍历,找到第一个大于when字段的消息A,插入到A的前面。

相关文章:

  • C语言 | 求最大/小值小技巧:fmax、fmin函数
  • 正则表达式 文本三剑客
  • 2024 年, Web 前端开发趋势
  • JAVA项目扩展-多数据库连接(实现一个简单的数据库jdbc连接池)
  • 第十章 单调栈part02(● 503.下一个更大元素II ● 42. 接雨水 )
  • R语言学习case7:ggplot基础画图(核密度图)
  • Google Chrome RCE漏洞 CVE-2020-6507 和 CVE-2024-0517 流程分析
  • CSS Transition详解:优雅实现动画效果的利器
  • 阿里云幻兽帕鲁服务器4核16G配置报价
  • 批处理相关总结
  • Docker 安装与基本操作
  • qt 坦克大战游戏 GUI绘制
  • electron-builder vue 打包后element-ui字体图标不显示问题
  • 计算机软件能力认证考试CCF-202312-1 仓库规划
  • vit细粒度图像分类(六)TransFC学习笔记
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • 【剑指offer】让抽象问题具体化
  • cookie和session
  • C学习-枚举(九)
  • GitUp, 你不可错过的秀外慧中的git工具
  • happypack两次报错的问题
  • Mac转Windows的拯救指南
  • MQ框架的比较
  • Vue官网教程学习过程中值得记录的一些事情
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 手写一个CommonJS打包工具(一)
  • 算法---两个栈实现一个队列
  • 新版博客前端前瞻
  • 用jQuery怎么做到前后端分离
  • elasticsearch-head插件安装
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​【已解决】npm install​卡主不动的情况
  • ​油烟净化器电源安全,保障健康餐饮生活
  • # Java NIO(一)FileChannel
  • (第27天)Oracle 数据泵转换分区表
  • (转)linux下的时间函数使用
  • .Net mvc总结
  • .NET 常见的偏门问题
  • .net开发引用程序集提示没有强名称的解决办法
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .pings勒索病毒的威胁:如何应对.pings勒索病毒的突袭?
  • .pyc文件是什么?
  • [ Algorithm ] N次方算法 N Square 动态规划解决
  • [20171106]配置客户端连接注意.txt
  • [AIGC] Java 和 Kotlin 的区别
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [C#]手把手教你打造Socket的TCP通讯连接(一)
  • [DevEpxress]GridControl 显示Gif动画
  • [DevOps云实践] 彻底删除AWS云资源
  • [leetcode] 3Sum
  • [LeetCode]-225. 用队列实现栈-232. 用栈实现队列
  • [SAP] ABAP注释快捷键修改
  • [TLSR8266] 1、搭建tlsr8266编译框架在win服务器中
  • [UI5 常用控件] 06.Splitter,ResponsiveSplitter