1) 投递方式:时间如何决定“何时执行”
时间基准:SystemClock.uptimeMillis()(设备睡眠停走)。
一个 Looper = 一个 MessageQueue;同一 Looper 下的所有 Handler 都把任务塞进同一个队列。
投递 API 与效果:
-
post {} / sendMessage()
→ 立即任务:when = now(按当前队列头部时间排位)。
-
postDelayed(r, delay) / sendMessageDelayed(msg, delay)
→ 延时任务:when = now + delay。
-
postAtTime(r, uptime) / sendMessageAtTime(msg, uptime)
→ 绝对时间任务:when = uptime。
-
sendMessageAtFrontOfQueue(msg)
→ 插到队头(when 设成当前、并放到链表首位)。注意:只影响本次入队位置,不改变“消息类型”(仍是同步消息,见第 3 节)。
-
IdleHandler(MessageQueue.addIdleHandler)
→ 队列空闲(无到期消息、或刚处理完最后一条且下一条未到时)才回调,适合低优先级收尾。
结论:何时执行 = 取队头到期消息的时刻。延时、定时、队头插入只是改变 when 或链表位置。
2) 优先级与顺序:队列如何出队
MessageQueue 是按 when 升序的单链表;取消息规则(简化):
-
先看队头:
- 如果是普通消息且 msg.when <= now → 立刻取出;
- 如果 msg.when > now → 休眠到到期或被新消息/唤醒打断。
-
相同 when(同一毫秒)按入队先后(FIFO)执行。
-
多个 Handler 共用同一队列,谁先到期谁先执行,与属于哪个 Handler 无关。
-
sendMessageAtFrontOfQueue 插入队头(高于所有“when>=now”的消息);但见第 3 节——可能仍被同步屏障拦下。
-
removeCallbacks/ removeMessages 会从链表中删除尚未执行的项;已经取出的正在执行的不能取消。
小结优先级(从高到低,常见情形):
(在没有屏障的前提下)
队头插入 > 立即/到期消息 > 延时未到期消息 > IdleHandler
3) 同步屏障(Sync Barrier)与异步消息(Async)
3.1 是什么
-
同步屏障:MessageQueue 里一种特殊节点(target == null),用来暂时阻塞所有“同步消息” ,只放行**“异步消息”**。
- 框架在主线程做一帧绘制前会插入屏障(ViewRootImpl/Choreographer),确保输入、动画、绘制这类异步工作优先执行,降低卡顿。
-
异步消息:Message.isAsynchronous() == true 的消息;或用 异步 Handler 发送的消息(new Handler(looper, callback, /async=/true))。
-
框架把**输入(Input)/动画(Animation)/遍历绘制(Traversal)**标记为异步。
-
3.2 有屏障时如何取消息(核心逻辑)
-
若队头是屏障:队列会向后扫描,寻找第一条异步消息;
- 找到 → 立刻取出执行;
- 找不到 → 休眠,直到有异步消息入队或屏障被移除。
-
同步消息在屏障之后会被卡住;在屏障之前的同步消息仍能执行(例如你后来用了队头插入把同步消息塞到了屏障前面)。
3.3 实践要点
- 正常业务不要自己插/移屏障(那是框架级 API),以免破坏调度节拍。
- 不要滥用“异步 Handler” :把普通业务都标成异步会插队到渲染前,引发掉帧或优先级紊乱。
- sendMessageAtFrontOfQueue 不会自动变异步;若队头有屏障,且你的消息被插在屏障后,就仍会被挡住;若插在屏障前,则会绕过屏障(这也是不建议在主线程乱用“队头插入”的原因)。
4) 组合示例(验证时序)
val q = Looper.getMainLooper().queue
val h = Handler(Looper.getMainLooper())
val asyncH = Handler(Looper.getMainLooper(), null, /*async=*/ true)
h.postDelayed({ log("A @+500ms 同步") }, 500)
asyncH.postDelayed({ log("B @+500ms 异步") }, 500)
// 框架在一帧开始前加了同步屏障(示意):q.enqueueSyncBarrier(now)
// 到 500ms:若屏障还在 → B 会先执行,A 被卡住;屏障移除后 A 才执行。
验证顺序要点:改成 h.sendMessageAtFrontOfQueue(msgA) 可能让 A 在当前就执行(甚至在屏障前),这就是“队头插入可能绕过屏障”的实际体现——慎用。
5) FAQ(高频易错)
-
延时是按什么时钟算?
uptimeMillis(睡眠停走)。深度睡眠期间到期的消息会等设备醒来才执行。
-
多个 Handler 会不会抢顺序?
不会。大家共用一个队列,按 when 与入队先后决定全局顺序。
-
为什么我 post() 了却很晚才跑?
可能前面有大量到期任务/长耗时回调;或主线程被阻塞(ANR 风险)。
-
怎么做低优先级任务?
用 IdleHandler or 较长延时;不要标异步去“插队”。
-
如何取消?
handler.removeCallbacks(r) / removeMessages(what) / removeCallbacksAndMessages(tokenOrNull)。
6) 一页小抄
-
何时执行:取队头到期消息 → dispatchMessage。
-
顺序:when 升序;同 when FIFO;AtFrontOfQueue 插到头;IdleHandler 在空闲时。
-
屏障:挡同步、放异步;慎用“队头插入”与“异步 Handler”。
-
时间基:uptimeMillis;睡眠期间不前进。
把这三点吃透,你就能准确预判“这条消息会在什么时候跑”,并且在需要时用正确手段(延时、队头、异步/同步、IdleHandler)控制时序而不破坏主线程的绘制节奏。