一、他们各自是谁?怎么"串"起来?
ini
[Thread] 1:1 [Looper] ------持有------> [MessageQueue] <------被操作------ [Handler] *N
↑ |
└────────包含───────────┘
[Message]
-
Thread :线程本身。只有调用过 Looper.prepare() 的线程才"有 Looper/消息泵"。
-
Looper :每线程唯一(保存在 ThreadLocal)。负责循环取消息并派发。
-
MessageQueue:此线程的消息队列,存放将来要执行的 Message(按 when 时间排序、阻塞等待)。
-
Handler :消息入口。谁创建它,它就绑定哪个 Looper/Queue。sendMessage/post(Runnable) 都是往 Queue 里塞条目。
-
Message :一次投递。包含 what/arg1/arg2/obj/when/target 等,其中 target=投递它的 Handler。
主线程(UI 线程) :在 ActivityThread.main() 里 Looper.prepareMainLooper(); Looper.loop() 已就绪,所以你直接 Handler(Looper.getMainLooper()) 就能发消息到 UI 线程。
子线程若要收消息:必须自己 Looper.prepare() → 创建 Handler → Looper.loop()。
二、一次消息是如何走完的?
-
投递:handler.sendMessage(msg) 或 post { ... }
- MessageQueue.enqueueMessage() 把消息按 when 插入有序单链表;msg.target = handler。
-
取出:Looper.loop() 调 queue.next()
- 若队列为空或最近的 when 未到,阻塞等待(native pollOnce,底层 epoll/kqueue/管道唤醒)。
-
派发 :拿到 msg → msg.target.dispatchMessage(msg)****
- dispatchMessage():优先跑 msg.callback(post(Runnable) 存这里),否则调 Handler.handleMessage(msg)。
-
回收:Message.recycleUnchecked() 放回全局池(Message.obtain() 复用)。
伪码核心(高度概括):
scss
for(;;) {
Message msg = queue.next(); // 可能阻塞
msg.target.dispatchMessage(msg); // 回到发送它的 Handler 实例
msg.recycleUnchecked();
}
三、最小可运行模板
1) 在主线程发到主线程(UI 刷新/切换)
ini
val main = Handler(Looper.getMainLooper())
main.post { textView.text = "Hi" }
2) 子线程自带 Looper(纯手动)
kotlin
class LooperThread : Thread() {
lateinit var handler: Handler
override fun run() {
Looper.prepare() // 绑定到当前 ThreadLocal
handler = object: Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) { /* 处理 */ }
}
Looper.loop() // 开始消息泵(阻塞)
}
}
// 使用
val t = LooperThread().apply { start() }
// 等 handler 初始化完(可用 CountDownLatch)再:
t.handler.sendEmptyMessage(1)
3) 用HandlerThread(封装好了)
scss
val ht = HandlerThread("worker").apply { start() }
val worker = Handler(ht.looper)
worker.post { /* 后台任务 */ }
// 退出
ht.quitSafely()
四、关键细节与进阶
1) 每线程唯一:ThreadLocal
-
Looper.myLooper() 从 ThreadLocal 取;一个线程只能有一个 Looper。
-
主线程在框架里已经 prepareMainLooper(),你不需要再 prepare()。
2) MessageQueue 的阻塞/唤醒
-
enqueueMessage() 发现新消息更早,会通过管道唤醒正在 pollOnce() 的队列。
-
next() 会:
-
先看超时是否到了(when),到了就返回该消息;
-
没到就睡一会(或无限期),被新消息/异步事件唤醒继续。
-
3) 同步屏障 & 异步消息(UI 渲染相关)
-
框架会插入同步屏障 (Sync Barrier),阻塞**"同步消息" ,只放行异步消息**(msg.isAsynchronous())。
-
Choreographer 的 vsync 驱动就利用了异步消息确保输入/动画/绘制在合适的节拍执行。
-
业务一般很少手动用;知道有这层机制即可。
4)post(Runnable) vs sendMessage(Message)
-
post(Runnable):把 Runnable 塞进 Message.callback,到达后直接 run()。
-
sendMessage:走 handleMessage(msg) 分发。
5) 退出 Looper
-
Looper.quit():立刻退出,移除所有待处理消息。
-
Looper.quitSafely():处理到所有已到期的同步消息后退出(更温和)。
-
退出后再发消息会抛 IllegalStateException。
6) IdleHandler:队列空闲时回调
arduino
Looper.myQueue().addIdleHandler {
// 队列暂时空了;做些低优先级任务
false // 返回 true 可反复回调
}
五、常见问题 & 最佳实践
-
泄漏:匿名/非静态内部 Handler 持有外部 Activity;在 onDestroy 调
handler.removeCallbacksAndMessages(null),或把 Handler 写成 static + WeakReference。
-
ANR:主线程 handleMessage/Runnable 里做了重活阻塞了 Looper(>5s)。重活丢给后台线程或异步。
-
跨线程更新 UI :只能投递到 主线程 Looper 的 Handler;直接在子线程改 View 会崩。
-
顺序/延时 :同一个 Handler/Queue 的消息按 when 有序;postAtTime/postDelayed 控制时机。
-
与协程结合:可用 Dispatchers.Main;若必须与 Handler 交互,suspendCancellableCoroutine + Handler.post/removeCallbacks 封装。
六、一句话总览
Thread (线程)里若准备了 Looper ,就有一个MessageQueue 。Handler 绑定该 Looper,负责把 Message 投递到队列 。Looper.loop() 不停从队列取消息,并回调到 msg.target(也就是 Handler) → dispatchMessage/handleMessage 执行。主线程生来就有 Looper,子线程想收消息就自己 prepare/loop。