> 适合读者:已了解 Activity/Fragment 基础,想搞懂"为什么能在子线程更新 UI"的开发者
你在子线程里调用 runOnUiThread() 或 View.post() 时,有没有好奇过它是怎么把任务"塞回"主线程的?
答案就是 Handler + Looper + MessageQueue 三件套,它们构成了 Android 最经典的异步消息处理模型。
简单说:线程间不能直接共享对象引用(避免并发问题),于是 Android 设计了一套基于消息队列的通信机制——把任务打包成 Message,排队交给目标线程的 Looper 逐个执行。
组件 | 职责 | 类比 |
|---|---|---|
Handler | 发送消息(sendMessage)和处理消息(handleMessage) | 快递员 + 收件人 |
Looper | 每个线程只有一个,死循环不断从 Queue 取消息派发 | 流水线工人 |
MessageQueue | 链表结构的消息队列,按时间排序 | 排队叫号系统 |
Message | 携带数据的小包(what, arg1, arg2, obj, target) | 快递包裹 |
关键规则:
class MainActivity : AppCompatActivity() {
// ① 声明 Handler —— 绑定当前线程的 Looper
// 在主线程 new → 收到来自主线程的消息
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.btnDelay).setOnClickListener {
// ② 发一条延时消息到主线程
handler.postDelayed({
// 这段代码会在 2000ms 后回到主线程执行
Toast.makeText(this, "延时完成!", Toast.LENGTH_SHORT).show()
}, 2000)
}
}
override fun onDestroy() {
super.onDestroy()
// ③ 销毁前清理,防止内存泄漏
handler.removeCallbacksAndMessages(null)
}
}// WorkerThread —— 自带 Looper 的后台线程
class WorkerThread : Thread() {
private lateinit var looper: Looper
lateinit var handler: Handler
override fun run() {
// ④ 为当前线程准备 Looper
Looper.prepare()
looper = Looper.myLooper()!!
handler = Handler(looper) { msg ->
// ⑤ 在这里处理消息
when (msg.what) {
1 -> println("收到任务: ${msg.obj}")
2 -> println("下载进度: ${msg.arg1}%")
}
true // 表示已处理
}
// ⑥ 开启消息循环 —— 这是一个死循环!
Looper.loop()
}
fun quit() {
looper.quit() // 优雅退出循环
}
}
// 使用方式
val worker = WorkerThread().apply { start() }
// 从任意线程发消息给工作线程
worker.handler.sendMessage(Message.obtain().apply {
what = 1
obj = "拉取数据"
})
// 用完记得关掉
worker.quit()import kotlinx.coroutines.*
btnDownload.setOnClickListener {
lifecycleScope.launch { // 自动绑定生命周期
launch(Dispatchers.IO) {
// 模拟耗时操作
delay(2000)
val result = downloadData()
// 切回主线程更新 UI
withContext(Dispatchers.Main) {
textView.text = result
}
}
}
}> 协程本质上是 Handler 的高级抽象,底层很多实现仍然用了 Looper。但在绝大多数业务场景中,优先用协程,少手写 Handler。
Handler 内部持有一个指向自身对象的强引用链:
MessageQueue → Message → Message.target (Handler) → Handler.this$0 (Activity)如果 Activity 被销毁但 Message 还在队列中,Activity 无法回收 → 内存泄漏。
修复方法:
// ❌ 匿名内部类 —— 隐式持有外部类引用
private val leakyHandler = object : Handler() {
override fun handleMessage(msg: Message) { ... }
}
// ✅ 方法 1:静态类 + WeakReference
class SafeHandler(activity: Activity) : Handler(
WeakReference(activity).get()?.application?.mainExecutor
) {
companion object {
private class Ref(val activity: MainActivity) : WeakReference<MainActivity>(activity)
}
override fun handleMessage(msg: Message) {
// 先检查 Activity 是否存活
}
}
// ✅ 方法 2:用 removeCallbacksAndMessages 清理
override fun onDestroy() {
handler.removeCallbacksAndMessages(null) // null = 移除所有消息和回调
}// ❌ 在非主线程多次调用 prepare() —— 抛异常!
Looper.prepare()
Looper.prepare() // RuntimeException: Only one Looper may be created per thread每个线程 prepare() 只应调用一次。线程池中的工作线程不能有 Looper(因为线程会复用)。
// ❌ Looper.loop() 是死循环,在它之后写的代码永远不会执行
Looper.loop()
Toast.makeText(this, "永远不会弹", Toast.SHORT).show()
// ✅ 如果要等某个操作完成再继续,用同步屏障或 countDownLatch要点 | 说明 |
|---|---|
一句话理解 | Handler 负责发消息和处理消息,Looper 负责轮询取消息,MessageQueue 负责排队 |
什么时候用 | 低版本兼容、需要精确控制消息时序、自定义线程通信 |
什么时候不用 | 日常异步任务 → 直接用协程 / Executors |
面试高频 | Handler 原理、内存泄漏原因及解法、主线程 Looper 的创建时机 |
记住这个公式:
子线程干活,主线程展示 = Handler 搭桥
掌握了它,后面看 Retrofit 回调、Glide 加载、甚至源码级别的 ViewRootImpl 都不会懵。
下篇预告:Android 动画系统 — Property Animation vs View Animation
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。