Handler 与 Looper 消息机制——Android线程通信的核心

简介: Android Handler 是主线程通信核心机制,通过 Handler(发送/处理消息)、Looper(轮询分发)与 MessageQueue(有序队列)协同实现线程安全异步通信。需注意内存泄漏、Looper单例限制及协程替代趋势。掌握它,是理解UI更新与源码的关键。(239字)

一、背景:为什么需要 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

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 线程(后台任务专用)

// 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 协程等价写法(现代替代方案)

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 内部持有一个指向自身对象的强引用链:

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 = 移除所有消息和回调
}

⚠️ 坑 2:重复创建 Looper

// ❌ 在非主线程多次调用 prepare() —— 抛异常!
Looper.prepare()
Looper.prepare() // RuntimeException: Only one Looper may be created per thread

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

⚠️ 坑 3:主线程阻塞

// ❌ 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

相关文章
|
Linux API
设备树知识小全(八):中断连接
设备树知识小全(八):中断连接
581 0
|
7月前
|
人工智能 数据挖掘
想让龙虾为你打工?阿里云 4 步搞定 OpenClaw,5 分钟上线 AI 助手
阿里云4步极速部署OpenClaw“龙虾AI助手”:5分钟上线可干活的智能员工!支持9.9元付费部署(含轻量服务器+百炼大模型)或免费试用2小时。告别聊天式AI,真正执行任务——代码、文档、数据分析一肩挑!
624 1
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
14182 1
|
3月前
|
存储 人工智能 API
小龙虾安装教程TopClaw中文版,OpenClaw AI免费部署指南
本文手把手教你零代码安装“小龙虾”(OpenClaw AI中文版TopClaw):兼容Win10/11、Mac全芯片,3分钟一键安装;内置中文界面、模型一键下载、本地运行保隐私;支持API调用、VS Code/Obsidian接入,免费、安全、易上手。
623 1
|
3月前
|
存储 自然语言处理 测试技术
[鸿蒙从零到一] HarmonyOS 通知与提醒实战:消息发布、点击跳转与定时触达
本文详解鸿蒙HarmonyOS通知与提醒实战:区分NotificationKit(即时通知)与ReminderAgent(定时提醒),涵盖权限申请、通知槽管理、点击跳转、进度更新、定时触发、ID去重、错误处理及真机测试要点,助开发者构建稳定可靠的生产级消息触达能力
462 0
|
3月前
|
存储 缓存 API
[鸿蒙从零到一] HarmonyOS 后台任务与定时能力实战:短时任务、长时任务与延迟调度
本文系统讲解HarmonyOS后台任务实战,涵盖短时任务(收尾保护)、长时任务(用户可感知持续运行)、延迟任务(Work Scheduler按条件调度)及代理提醒(准时通知),结合ArkTS代码、权限配置、异常处理与测试要点,助开发者构建稳定、合规、低耗的后台能力
264 0
|
10月前
|
监控 安全 芯片
3.7V锂电池充电芯片精选,PW4054H/PW4057H/PW4056HH/PW4213一站式解决方案
3.7V锂电池充电芯片精选,PW4054H/PW4057H/PW4056HH/PW4213一站式解决方案
|
5月前
|
人工智能 运维 Serverless
作为一名独立开发者,我为什么放弃了本地 GPU,转向 Serverless 部署 AI 模型?
本文以真实经历切入,剖析本地部署AI模型的四大痛点(环境配置难、噪音电费高、利用率低、弹性差),揭示独立开发者面临的硬件、运维与成本三重困境。重点推介Serverless GPU方案——按调用付费、极致弹性、一键部署预置AI模板(如Flux、GPT-Sovits),大幅降低MVP验证门槛。理性指出其适用边界,倡导“调用能力”替代“拥有显卡”的新范式。(239字)
|
4月前
|
索引 Windows
Everything-1.3.2.643b使用步骤详解(附离线搜索与硬盘文件秒搜教程)
Everything是一款超轻量单机文件搜索工具,安装即用、无需联网,秒搜本地硬盘文件。支持通配符(*.jpg)、路径限定(D:\项目)、排除关键词(报告 !草稿)等高效检索方式,并可集成右键菜单、自定义快捷键与字体,大幅提升查找效率。(239字)
|
7月前
|
Web App开发 前端开发 安全
Docker快速搭建 file-transfer-go:极简文件传输工具部署教程
Docker快速搭建 file-transfer-go:极简文件传输工具部署教程
590 8
Docker快速搭建 file-transfer-go:极简文件传输工具部署教程

热门文章

最新文章