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 就会退出,导致进程终止。