首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >[Android 从零到一] Handler 与 Looper 消息机制

[Android 从零到一] Handler 与 Looper 消息机制

原创
作者头像
hunter android
发布2026-06-17 17:01:34
发布2026-06-17 17:01:34
690
举报

Handler & Looper 消息机制——你以为的延时操作,背后藏着一整个线程通信系统

> 适合读者:已了解 Activity/Fragment 基础,想搞懂"为什么能在子线程更新 UI"的开发者

一、背景:为什么需要 Handler?

你在子线程里调用 runOnUiThread()View.post() 时,有没有好奇过它是怎么把任务"塞回"主线程的?

答案就是 Handler + Looper + MessageQueue 三件套,它们构成了 Android 最经典的异步消息处理模型。

简单说:线程间不能直接共享对象引用(避免并发问题),于是 Android 设计了一套基于消息队列的通信机制——把任务打包成 Message,排队交给目标线程的 Looper 逐个执行。

二、核心概念速查表

组件

职责

类比

Handler

发送消息(sendMessage)和处理消息(handleMessage)

快递员 + 收件人

Looper

每个线程只有一个,死循环不断从 Queue 取消息派发

流水线工人

MessageQueue

链表结构的消息队列,按时间排序

排队叫号系统

Message

携带数据的小包(what, arg1, arg2, obj, target)

快递包裹

关键规则:

  • 每个线程最多一个 Looper(Looper.prepare() 创建)
  • 主线程(UI 线程)自动创建了 Looper——这就是为什么子线程可以直接 new Handler 操作 UI
  • 非主线程默认没有 Looper,需手动 prepare() + loop()

三、代码实战

场景:点击按钮后在子线程延迟 2 秒弹 Toast
代码语言:javascript
复制
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)
    }
}
进阶:自定义 Looper 线程(后台任务专用)
代码语言:javascript
复制
// 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()
Kotlin 协程等价写法(现代替代方案)
代码语言:javascript
复制
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

四、避坑指南

⚠️ 坑 1:Handler 内存泄漏(经典面试题)

Handler 内部持有一个指向自身对象的强引用链:

代码语言:javascript
复制
MessageQueue → Message → Message.target (Handler) → Handler.this$0 (Activity)

如果 Activity 被销毁但 Message 还在队列中,Activity 无法回收 → 内存泄漏

修复方法:

代码语言:javascript
复制
// ❌ 匿名内部类 —— 隐式持有外部类引用
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 = 移除所有消息和回调
}
⚠️ 坑 2:重复创建 Looper
代码语言:javascript
复制
// ❌ 在非主线程多次调用 prepare() —— 抛异常!
Looper.prepare()
Looper.prepare() // RuntimeException: Only one Looper may be created per thread

每个线程 prepare() 只应调用一次。线程池中的工作线程不能有 Looper(因为线程会复用)。

⚠️ 坑 3:主线程阻塞
代码语言:javascript
复制
// ❌ 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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Handler & Looper 消息机制——你以为的延时操作,背后藏着一整个线程通信系统
    • 一、背景:为什么需要 Handler?
    • 二、核心概念速查表
    • 三、代码实战
      • 场景:点击按钮后在子线程延迟 2 秒弹 Toast
      • 进阶:自定义 Looper 线程(后台任务专用)
      • Kotlin 协程等价写法(现代替代方案)
    • 四、避坑指南
      • ⚠️ 坑 1:Handler 内存泄漏(经典面试题)
      • ⚠️ 坑 2:重复创建 Looper
      • ⚠️ 坑 3:主线程阻塞
    • 五、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档