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

android源码角度分析Handler机制

源码使用的android2.2的源码

虽然有点古老,但核心源码不会有太大变化,不影响学习

首先简述下我们平常是怎么使用handler的

    // 声明一个类继承handlerclass MyHandler extends Handler{@Overridepublic void handleMessage(Message msg) {// 接收并处理消息}}MyHandler handler = new MyHandler();Message message = Message.obtain();message.what = 1;// 发送消息handler.sendMessage(message);

简单来说,发送消息,处理消息。

一.发送消息

源码路径
“android-2.2_r1/frameworks/base/core/java/android/os/Handler.java”

    public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}// 实际上最终会走到这里public boolean sendMessageAtTime(Message msg, long uptimeMillis){boolean sent = false;MessageQueue queue = mQueue;if (queue != null) {msg.target = this;// 关键代码sent = queue.enqueueMessage(msg, uptimeMillis);}else {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);}return sent;}

因此,handler.sendMessage(message),实际上是往消息队列MessageQueue添加一个消息
那么enqueueMessage里干了什么?

   final boolean enqueueMessage(Message msg, long when) {synchronized (this) {msg.when = when;Message p = mMessages;// 情况A:插入到队列头部(需要唤醒)// 条件1: p == null -> 队列是空的。// 条件2: when == 0 -> 消息需要立即执行。// 条件3: when < p.when -> 新消息的执行时间比当前头消息还要早。if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;this.notify();} // 情况B:插入到队列中间或尾部(通常也需要唤醒,但这里代码似乎漏了?)else {Message prev = null;// 遍历队列,找到第一个执行时间比新消息晚的消息 (p.when > when)// 目的是找到新消息的插入位置:在 p 之前,prev 之后。while (p != null && p.when <= when) {prev = p;p = p.next;}msg.next = prev.next;prev.next = msg;this.notify();}}return true;}

简单来说,往消息队列添加消息时,会根据when参数,将消息插入到队列中,队列顺序是时间顺序。

二.处理消息

首先看下我们在创建handler时的源码是怎么样的

    public Handler() {// 核心mLooper = Looper.myLooper();mQueue = mLooper.mQueue;mCallback = null;}public static final Looper myLooper() {// 返回当前线程的looper// 由于我们在主线程中创建的handler,因此返回的是主线程已创建好的looperreturn (Looper)sThreadLocal.get();}// 主线程是在哪里创建looper的呢?源码位置android-2.2_r1/frameworks/base/core/java/android/app/ActivityThread.javapublic static final void main(String[] args) {SamplingProfilerIntegration.start();Process.setArgV0("<pre-initialized>");// 重点,主线程创建looper的地方Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);// 重点2,启动loop循环Looper.loop();if (Process.supportsProcesses()) {throw new RuntimeException("Main thread loop unexpectedly exited");}thread.detach();String name = (thread.mInitialApplication != null)? thread.mInitialApplication.getPackageName(): "<unknown>";Slog.i(TAG, "Main thread of " + name + " is now exiting");}// Loop.loop()都干了啥?public static final void loop() {Looper me = myLooper();MessageQueue queue = me.mQueue;while (true) {// 从队列中取出消息--重点Message msg = queue.next(); // 这里可能会阻塞if (msg != null) {if (msg.target == null) {// No target is a magic identifier for the quit message.return;}// 消息分发,msg.target其实就是自己创建的handler,最终会走到handleMessage方法,这里dispatchMessage就不展示源码了msg.target.dispatchMessage(msg);msg.recycle();}}}

因此从源码可以得知,"处理消息"是通过Looper无限循环地从消息队列中取出消息然后处理

那么为啥loop不会导致卡死呢?

其实关键在于,这个循环不是一个“忙等待”(busy-waiting)循环,而是一个“等待-唤醒”(wait-notify)机制。它大部分时间并不在消耗 CPU,而是处于休眠状态。

我们来看看queue.next()中都干了什么

final Message next() {// 定义一个标志变量,控制是否尝试执行空闲处理器(Idle Handlers)。初始为 true。boolean tryIdle = true;// 开启一个无限循环,直到找到并返回一个消息,或者发生其他退出条件。while (true) {// 定义一个变量来存储当前时间(基于系统启动后的毫秒数,不包括睡眠时间)。long now;// 定义一个数组来保存当前的空闲处理器列表。Object[] idlers = null;// 第一个同步代码块:访问共享的消息队列数据结构,必须是线程安全的。synchronized (this) {// 获取当前精确的时间戳,用于比较消息的执行时间。now = SystemClock.uptimeMillis();// 核心方法:尝试从队列中取出一个到点(when <= now)的消息。Message msg = pullNextLocked(now);// 如果找到了符合条件的消息,立即返回它给 Looper 进行处理。if (msg != null) return msg;// 如果没找到立即要处理的消息,并且当前允许尝试空闲处理(tryIdle为true),// 且注册的空闲处理器列表不为空,则将这些处理器复制到本地数组 idlers 中。// 注意:复制操作在同步块内完成,但实际执行会在同步块外,以避免死锁和性能问题。if (tryIdle && mIdleHandlers.size() > 0) {idlers = mIdleHandlers.toArray();}} // 结束第一个同步块// 代码执行到这里,说明当前没有需要立即处理的消息。// didIdle 标志:记录在此次循环中是否执行了至少一个空闲处理器。boolean didIdle = false;// 如果存在需要执行的空闲处理器列表(idlers != null)...if (idlers != null) {// 遍历所有的空闲处理器for (Object idler : idlers) {// keep 变量:记录该空闲处理器是否希望继续保持活跃(下次空闲时再次执行)。boolean keep = false;try {// 设置标志,表明我们正在执行空闲任务didIdle = true;// 调用空闲处理器的 queueIdle 方法,并获取其返回值。// 返回值决定这个处理器是只执行一次(false)还是保留(true)。keep = ((IdleHandler)idler).queueIdle();} catch (Throwable t) {// 捕获空闲处理器执行过程中抛出的任何异常,避免崩溃整个消息循环。Log.wtf("MessageQueue", "IdleHandler threw exception", t);}// 如果该处理器返回 false(不希望再执行),则将其从注册列表中移除。if (!keep) {// 移除操作需要同步,因为 mIdleHandlers 是共享资源。synchronized (this) {mIdleHandlers.remove(idler);}}}}// 在执行空闲处理器的过程中,有可能新的消息已经被插入到队列中了// (例如,某个空闲处理器执行了发送消息的操作)。// 因此,如果执行了空闲处理器,我们将 tryIdle 置为 false(防止立即再次执行空闲处理),// 并使用 continue 跳回循环开头,重新检查消息队列。// 这是一个优化,避免在可能有新消息的情况下进入不必要的等待。if (didIdle) {tryIdle = false;continue; // 回到 while(true) 的开头}// 第二个同步代码块:处理等待逻辑。synchronized (this) {// 再次检查队列状态(因为可能在执行完空闲处理器后,又有新消息到来?// 但上面的 continue 应该已经处理了这种情况。这里更可能是为了检查下一个消息的时间)// 尝试再次获取下一个消息(使用最新的 now 时间?但 now 是之前获取的,这里可能有竞态条件,但通常通过 wait/notify 机制解决)// 实际上,更常见的做法是前面的 pullNextLocked 已经判断了,这里主要看是否有消息以及下一个消息何时到期。Message prevMsg = null;// 假设这里会再次检查 mMessages(队列头),但通常直接进入等待逻辑。// No messages, nobody to tell about it...  time to wait!try {// 如果队列不为空(mMessages != null),说明有消息,只是还没到执行时间。if (mMessages != null) {// 计算下一个消息还需要多久到期(nextMessage.when - currentTime)long nextWakeTime = mMessages.when - now;// 如果下一个消息还需要一段时间才到期(nextWakeTime > 0)...if (nextWakeTime > 0) {// 在等待之前,先刷新任何挂起的 Binder 命令。// 这是一个优化,确保在Native层等待之前,所有pending的IPC请求都被处理。Binder.flushPendingCommands();// 调用 Object.wait(timeout) 方法,释放当前同步锁(this),// 并进入计时等待状态。等待时间为下一个消息的延迟时间。// 在等待期间,如果其他线程调用 notify() 或 notifyAll()(通常通过 enqueueMessage),// 或者等待超时,线程会被唤醒。//  这里是重点!!!!this.wait(nextWakeTime);}// 注意:这里没有 else 分支。如果 nextWakeTime <= 0,意味着消息应该已经到期了,// 但为什么没在 pullNextLocked 中被取出?这可能是因为 now 是之前获取的,// 而在这期间没有新消息使队列状态变化。通常循环会继续,下一次 pullNextLocked 就会取出它。// 实际上,为了健壮性,这里可能会再次尝试 pullNextLocked,但代码中似乎没有。} else {// 如果队列完全是空的(mMessages == null),则进入无限期等待,// 直到有消息入队(enqueueMessage)时调用 notify()/notifyAll() 唤醒。Binder.flushPendingCommands();//  这里是重点!!!!this.wait(); // 无限期等待}}// 捕获等待过程中被中断的异常。在Android消息循环中,中断通常用于退出循环,// 但这里选择忽略中断,继续循环,因为Looper有自己专门的退出机制(quit())。catch (InterruptedException e) {// 通常什么都不做,继续循环。或者Looper的退出标志可能在其他地方被检查。}} // 结束第二个同步块// 当代码执行到这里,说明线程从 wait() 中醒来了(可能是因为超时、有新消息入队、或被中断)。// 循环会继续,回到最开始的 synchronized 块,再次尝试获取消息。// 如果是因为新消息入队而唤醒,pullNextLocked 很可能就会返回这个消息。// 如果是因为超时而唤醒,pullNextLocked 应该能取出那个到期的消息。// 如果是因为中断,可能会在下一次循环中通过其他方式处理(比如检查退出标志)。} // 结束 while (true) 循环
} // 结束 next() 方法

通过next()的源码,我们发现当消息为空的时候,调用了wait()方法。
调用wait方法会使当前线程(通常是主线程/UI线程)进入等待状态,并释放其持有的 MessageQueue 对象锁。这时,线程会被挂起,不再消耗任何 CPU 时间片。操作系统会将它移出可调度队列,直到特定条件发生

被唤醒:当其他线程(例如,用户输入线程、网络回调线程、其他应用线程)通过 Handler 向这个 MessageQueue 发送消息时,会在 enqueueMessage 方法中调用 notify 或 notifyAll。

因此Loop的死循环是“有活就干,没活就睡”的状态,睡眠不占用cpu资源,因此是不会卡死的。

Android 应用是事件驱动的。主线程的所有工作都是由消息触发的,例如:
输入事件: onTouchEvent, onKeyDown
UI 绘制: measure, layout, draw (VSYNC 信号会发送一个绘制消息)
生命周期回调: onCreate, onResume
定时任务: Handler.postDelayed(...)
在没有这些事件时,主线程本来就应该“休息”。这个“死循环”恰恰完美地实现了这一点:有活就干,没活就睡。如果没有这个循环,主线程一旦执行完 onCreate 和 onResume 就会退出,导致进程终止。

文章转载自:

http://wdXCnE8O.pphbn.cn
http://tq5MgvmJ.pphbn.cn
http://0b7PQCcM.pphbn.cn
http://c6YwYcEA.pphbn.cn
http://ZAnU9q1r.pphbn.cn
http://U2puVbyX.pphbn.cn
http://jTAzJd5v.pphbn.cn
http://59KXLwN8.pphbn.cn
http://w9zkMd80.pphbn.cn
http://0sG6wiAw.pphbn.cn
http://5R2Szgcc.pphbn.cn
http://KOMkszeO.pphbn.cn
http://cXrlQx3T.pphbn.cn
http://7ltSq1iG.pphbn.cn
http://AVyu1dvX.pphbn.cn
http://Y7MOVBYk.pphbn.cn
http://B9bw43od.pphbn.cn
http://iFXEGYl9.pphbn.cn
http://lYD5lTUe.pphbn.cn
http://ZGEXuMzm.pphbn.cn
http://cvMHbQHO.pphbn.cn
http://Jk0hEgcZ.pphbn.cn
http://4rgRX63I.pphbn.cn
http://fahYhRm5.pphbn.cn
http://XzdSFpAM.pphbn.cn
http://yvoDGHYm.pphbn.cn
http://Ffdl9Tp0.pphbn.cn
http://zcL7SqAP.pphbn.cn
http://SgJXIXHf.pphbn.cn
http://jSu4sauY.pphbn.cn
http://www.dtcms.com/a/367628.html

相关文章:

  • 25高教社杯数模国赛【E题保姆级思路+问题分析】
  • 政务级数据安全!小陌GEO引擎的私有化部署实践指南
  • 卫星通信+地面网络融合 Sivers半导体毫米波技术打通智慧交通最后一公里
  • 理解进程栈内存的使用
  • C4.5决策树(信息增益率)、CART决策树(基尼指数)、CART回归树、决策树剪枝
  • 前端vue常见标签属性及作用解析
  • Vue基础知识-脚手架开发-子传父-props回调函数实现和自定义事件($on绑定、$emit触发、$off解绑)实现
  • 铭记抗战烽火史,科技强企筑强国 | 金智维开展抗战80周年主题系列活动
  • 无人机信号防干扰技术难点分析
  • 企业白名单实现【使用拦截器】
  • 硬件(二) 中断、定时器、PWM
  • 11 月广州见!AUTO TECH China 2025 汽车内外饰展,解锁行业新趋势
  • 【multisim汽车尾灯设计】2022-12-1
  • 工业人形机器人运动速度:富唯智能重新定义智能制造效率新标准
  • 惊爆!耐达讯自动化RS485转Profinet,电机连接的“逆天神器”?
  • Android 权限管理机制
  • MATLAB平台实现人口预测和GDP预测
  • jQuery的$.Ajax方法分析
  • 实现自己的AI视频监控系统-第三章-信息的推送与共享4
  • Vben5 封装的组件(豆包版)
  • 研发文档更新滞后的常见原因与解决方法
  • AI工具深度测评与选型指南 - Lovart专题
  • 卡方检验(独立性检验)
  • 【C语言】第四课 指针与内存管理
  • Mac开发第一步 - 安装Xcode
  • Full cycle of a machine learning project|机器学习项目的完整周期
  • AES介绍以及应用(crypto.js 实现数据加密)
  • 四十岁编程:热爱、沉淀与行业的真相-优雅草卓伊凡
  • 【数据分享】中国城市营商环境数据库2024(296个城市)(2017-2022)
  • 结合prompt分析NodeRAG的build过程