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

相关文章
|
4月前
|
人工智能 数据挖掘
想让龙虾为你打工?阿里云 4 步搞定 OpenClaw,5 分钟上线 AI 助手
阿里云4步极速部署OpenClaw“龙虾AI助手”:5分钟上线可干活的智能员工!支持9.9元付费部署(含轻量服务器+百炼大模型)或免费试用2小时。告别聊天式AI,真正执行任务——代码、文档、数据分析一肩挑!
511 1
|
前端开发 安全 程序员
测试大姐趁我下班点又提了个bug!前端你咋多个options请求?
测试大姐趁我下班点又提了个bug!前端你咋多个options请求?
279 0
|
13天前
|
消息中间件 安全 测试技术
StateFlow 与 SharedFlow:复杂页面状态管理的边界与落地
本文深入解析StateFlow与SharedFlow在复杂页面状态管理中的分工:StateFlow承载可恢复、需立即获取的UI状态(如列表数据、加载态),SharedFlow处理一次性UI事件(如导航、Toast)。倡导单一不可变UiState建模、原子更新、冷流生命周期感知共享,并结合Compose最佳实践,提升稳定性与可测性。
57 0
|
5天前
|
存储 JSON 缓存
[鸿蒙从零到一] HarmonyOS 网络请求与 JSON 解析实战:类型安全、错误分层与状态联动
本文详解HarmonyOS网络请求最佳实践:基于ArkTS与NetworkKit,构建类型安全、分层清晰的请求链路。涵盖权限配置、HttpClient封装、DTO/Model分离、业务/HTTP错误分层处理、状态驱动UI及防重复请求等核心要点,助你打造健壮可维护的鸿蒙网络模块。
50 0
|
6天前
|
存储 安全 API
ActivityResult API 实战:替代 onActivityResult 的现代回调设计
本文详解 Android ActivityResult API 实战:替代传统 onActivityResult,通过生命周期感知、类型安全的注册式回调,解决 requestCode 冲突、结果解析混乱等问题。涵盖权限申请、拍照选图、Fragment 使用、自定义 Contract 及 ViewModel 协作,助你写出更清晰、稳定、可维护的代码。
84 0
|
7天前
|
XML 数据采集 缓存
RecyclerView 多类型列表实战:稳定刷新、状态恢复与性能治理
本文详解RecyclerView多类型列表实战:通过密封类建模、稳定ID、DiffUtil精准对比、payload局部刷新、状态隔离与嵌套列表恢复等手段,解决闪烁、错位、状态串行等顽疾,兼顾性能、可维护性与扩展性。
45 0
|
8天前
|
存储
[鸿蒙从零到一] ArkUI 组件化实战:构建可复用、可组合的自定义组件
本文详解鸿蒙ArkUI组件化实战,涵盖自定义组件设计原则:合理划分状态边界、使用@Prop传参、事件回调通信、@BuilderParam实现插槽定制,并通过商品卡片案例演示可复用、可组合的组件开发全流程,助力构建高内聚、低耦合的UI体系。
46 0
|
8天前
|
缓存 数据库
Paging 3 RemoteMediator 实战:构建离线优先的分页列表
本文详解Paging 3中RemoteMediator的实战应用,以资讯列表为例,构建离线优先分页方案:UI只观察Room,RemoteMediator协调网络与数据库,确保数据一致性;涵盖远程键设计、事务写入、加载状态处理、缓存策略及常见问题排查。
42 0
|
9天前
|
测试技术 Shell 开发工具
前台服务适配与线上排查:通知权限、启动限制和任务保活
本文详解前台服务的合规实现:涵盖通知权限适配、后台启动限制规避、多版本系统兼容及线上问题排查方法,强调按场景选型(如导航/播放用前台服务,同步用WorkManager),避免滥用保活,助你构建稳定长任务能力。
54 0
|
9天前
|
存储 数据采集 JSON
[鸿蒙从零到一] ArkUI 列表与网格实战:List、Grid 与 LazyForEach
本文详解ArkUI中List与Grid组件的实战应用,涵盖静态列表、网格切换、懒加载(LazyForEach)、滚动定位、空/错/加载态处理等核心场景,并强调稳定key、精准数据通知与性能优化要点,助开发者构建高性能集合页面。
55 0