一句话总结:
Looper 是线程的“快递员”,负责从“包裹站”(消息队列)里不断取包裹(消息)给 Handler 处理。
Handler 是“收发室”,既能发消息到其他线程的包裹站,也能处理自己线程的包裹。
线程 是“快递员的工作区域”,主线程(UI 线程)自带快递员,子线程需要自己雇一个。
1. 三者的核心关系
| 角色 | 比喻 | 职责 |
|---|---|---|
| 线程 | 快递员的“工作区域” | 执行任务的场所(主线程处理 UI,子线程处理耗时任务)。 |
| Looper | 快递员 | 不断从“包裹站”(消息队列)里取包裹(消息),并分发给对应的 Handler。 |
| Handler | 收发室 | 发送消息到其他线程的包裹站,或处理本线程的包裹(如更新 UI)。 |
| MessageQueue | 包裹站 | 按顺序存放消息的队列,Looper 会依次取出处理。 |
2. 主线程的默认配置
-
主线程(UI 线程) :
- 天生自带一个 Looper(快递员)和一个 MessageQueue(包裹站)。
- 所有 UI 操作必须在这里处理,所以系统自动配置好了“快递员”。
- 开发者可以直接在主线程创建 Handler(收发室),无需手动初始化 Looper。
代码示例:
// 主线程直接创建 Handler(默认绑定主线程的 Looper)
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post { updateUI() } // 发消息到主线程的包裹站
3. 子线程如何工作?
-
子线程默认没有快递员:
如果想让子线程处理消息,必须手动雇一个“快递员”(Looper)并启动包裹站:thread(name = "子线程") { // 1. 雇一个快递员(初始化 Looper 和包裹站) Looper.prepare() // 2. 创建收发室(Handler),绑定子线程的快递员 val subHandler = Handler(Looper.myLooper()!!) { msg -> Log.d("子线程", "处理消息:${msg.what}") true } // 3. 快递员开始工作(循环取包裹) Looper.loop() } -
子线程发消息到主线程:
// 子线程中发送消息到主线程的包裹站 thread { val msg = Message.obtain().apply { what = 1 } mainHandler.sendMessage(msg) // 主线程的 Handler 处理消息 }
4. 为什么主线程的 Looper 不会卡死?
- 快递员会偷懒:
当包裹站没有包裹时,快递员(Looper)会睡觉(线程休眠),不占用 CPU。 - 有包裹时才干活:
新包裹到达时(如用户点击、定时任务),系统会叫醒快递员处理,处理完继续睡觉。 - 主线程的职责:
处理 UI 渲染、点击事件等消息,所有任务按顺序执行,保证流畅性。
5. 常见问题
Q1:子线程能直接更新 UI 吗?
-
不能!UI 操作必须在主线程处理。
-
解决:通过主线程的 Handler 发送消息:
// 子线程中更新 UI thread { // 耗时操作... mainHandler.post { textView.text = "更新成功" } // 通过主线程的 Handler }
Q2:Handler 导致内存泄漏怎么办?
-
原因:Handler 持有 Activity 引用,若 Activity 销毁但消息未处理完,会导致泄漏。
-
解决:
-
用静态内部类 + 弱引用。
-
在 Activity 销毁时移除所有消息:
override fun onDestroy() { handler.removeCallbacksAndMessages(null) super.onDestroy() }
-
总结
-
Looper:线程的“快递员”,没有它,线程不会处理消息。
-
Handler:线程的“收发室”,负责发消息和处理消息。
-
线程:主线程自带快递员,子线程需要自己雇一个。
-
口诀:
- 主线程快递员已配齐,子线程需手动雇人跑腿。
- Handler 跨线程发消息,主线程 UI 更新要牢记。