Looper、Handler和HandlerThread
Looper、Handler和HandlerThread:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。
学习目标
- 能沿“Looper、Handler和HandlerThread”的用户事件解释Android组件、状态所有者、线程与销毁边界。
- 能围绕“深入理解 Android 消息队列机制,学会用 Handler 在任意两个线程之间传递消息,并掌握 Handler 内存泄漏的经典解法。”改出一个可运行结果,并用前后状态而非组件数量验收。
- 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
- 能用请求或消息时间线、取消、乱序与失败恢复独立重放结论,并标明第四版机制与现代targetSdk政策的边界。
为什么 Android 需要一套"消息队列"?
想象你是工厂的前台文员(主线程),忙得不可开交——接电话、接待客户、整理文件。每隔 30 秒还得去仓库跑一趟取货。你跑仓库的这 5 分钟里,电话没人接、客户干等着——整个前台停摆。老板让你改进:在仓库那边安排一个搬运工(后台线程),需要取货就把"取货申请单"塞进一个筐子。搬运工从筐子里一份份拿出来处理,货到了放回前台——前台照常运转,拿到货后才展示给客户。
这,就是 Android 消息队列机制的直觉模型。筐子(MessageQueue)是线程间通信的中转站,搬运工(Looper)不停地从筐子里取任务处理,发申请单的(Handler)负责往筐子里投递消息。
没有这套机制,Android 的多线程通信会陷入混乱——你不知道该用什么方式安全地把数据从后台传到前台。
消息队列三件套:Looper、MessageQueue、Handler
一条消息的完整生命
一条消息(Message)从发送到被处理,经历以下路径:
Handler.sendMessage(msg)
│
▼
MessageQueue.enqueueMessage() ← 消息入队,按时间戳排序
│
▼
Looper.loop() ← 无限循环,不停从队列取消息
│
▼
msg.target.dispatchMessage(msg) ← target 就是发送它的 Handler
│
▼
Handler.handleMessage(msg) ← 你在 handleMessage() 中写处理逻辑
第 1 / 5 步 · ① Handler.post(msg):Handler 把一条消息投进队列,从队尾入队(MessageQueue.enqueueMessage)
点击播放,看一条消息走完循环:Handler 入队 → 排队 → Looper 从队头取出 → 分发给 Handler.handleMessage 处理 → 队空则 Looper 阻塞等待。可暂停、单步、拖进度逐帧观察。
每个角色各司其职:
-
↡MessageQueue 是每个有 Looper 的线程内部的消息队列数据结构。它是一个按时间戳排序的优先队列——先到的不一定先处理,核心看时间戳。只负责存和取,不负责执行。
:一个按时间戳排序的优先队列。只有放消息和取消息两个操作,不负责执行。每个有 Looper 的线程自动带一个。
-
↡Looper 是一个线程的消息循环引擎。它调用 Looper.prepare() 初始化自己和 MessageQueue,然后调用 Looper.loop() 进入无限循环——不停地从 MessageQueue 取消息,分发给目标 Handler 的 handleMessage()。没有 Looper 的线程不能处理消息。
:线程的消息循环引擎。
Looper.prepare()初始化消息队列,Looper.loop()进入无限循环——不停地从消息队列里取消息,分发给目标 Handler。 -
↡Handler 是消息发送和处理的门面。它挂载在某个线程的 Looper 上——默认是创建它的那个线程的 Looper。通过它可以往关联线程的消息队列投递 Message 或 Runnable,消息处理回调 handleMessage() 也在关联线程上执行。
:消息发送和处理的门面。你通过它往关联线程的消息队列投递消息,消息到达时
handleMessage()也在那个线程执行。
主线程为什么天然支持消息队列
你在主线程创建 Handler 不需要任何准备工作——因为 Android 在主线程启动时已经帮你调了 Looper.prepareMainLooper()。这也是为什么主线程永远不会退出:Looper.loop() 在无限循环,一旦退出,App 就死了。
// 主线程直接创建 Handler,不需要 prepare
val mainHandler = Handler(Looper.getMainLooper()) { msg ->
when (msg.what) {
1 -> textView.text = msg.obj as String
}
true
}动手看:一条消息从"发出"到"被执行"
猜一猜:在后台线程创建一个不加 Looper 的 Handler,会怎么样?切到最后一步看答案。
① 在后台线程准备消息循环
val backgroundThread = HandlerThread("BackgroundWorker").apply { start() }HandlerThread 是 Android 帮你封装好的——自带 Looper 和 MessageQueue 的后台线程。start() 后线程进入 Looper.loop() 循环,等待消息到来。
代码逐段拆解:跨线程通信的完整案例
场景:后台下载图片,主线程显示
class DownloadActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
private lateinit var downloadThread: HandlerThread
private lateinit var downloadHandler: Handler
private val mainHandler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_download)
imageView = findViewById(R.id.imageView)
// 1. 创建并启动带 Looper 的后台线程
downloadThread = HandlerThread("ImageDownloader").apply { start() }
downloadHandler = Handler(downloadThread.looper) { msg ->
if (msg.what == MSG_DOWNLOAD) {
val url = msg.obj as String
val bitmap = downloadImage(url) // 后台线程下载
// 2. 下载完切回主线程更新 UI
mainHandler.post {
imageView.setImageBitmap(bitmap)
}
}
true
}
}
private fun downloadImage(url: String): Bitmap {
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val bytes = client.newCall(request).execute().body?.bytes()
return BitmapFactory.decodeByteArray(bytes, 0, bytes?.size ?: 0)
}
companion object {
private const val MSG_DOWNLOAD = 1
}
override fun onDestroy() {
super.onDestroy()
downloadThread.quitSafely() // 3. 退出消息循环
}
}这段代码展示了经典的"双 Handler 模式":
downloadHandler挂载在 HandlerThread,负责后台下载mainHandler挂载在主线程,负责 UI 更新- 两个 Handler 通过消息投递完成线程切换——这就是 Android 多线程通信的基本范式
容易踩的坑
小结
- Android 消息队列三件套:
Looper(无限循环取消息)、MessageQueue(按时间排序存消息)、Handler(发送 + 处理消息) - 主线程自带 Looper(
prepareMainLooper),通过 Handler 可以实现任意线程之间的消息传递 HandlerThread是自带 Looper 的后台线程封装,适合需要排队执行的串行后台任务static Handler + WeakReference<Activity>是解决 Handler 内存泄漏的经典范式- 在
handleMessage()里做耗时操作会阻塞整个消息队列——所有消息排队等,包括 UI 绘制
练习
问题 1:“第25章 Loopers, Handlers, and HandlerThread”覆盖哪些正式节点和项目主线?
问题 2:怎样建立本页最小可执行实验?
问题 3:为什么只在正常点击路径运行不能证明完成?
问题 4:怎样设计能推翻当前实现的反例?
问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?
问题 6:本页达到独立交接标准需要什么?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- MessageQueue(消息队列)
每个有 Looper 的线程内部的消息存储结构。它是一个按时间戳排序的优先队列——一条消息什么时候该被执行,取决于它的
when字段。只负责存和取,不负责执行。详见本章"消息队列三件套"一节。- Looper(消息泵)
线程的消息循环引擎。
Looper.prepare()创建 MessageQueue 并绑定当前线程,Looper.loop()进入无限循环——不停从队列取消息,交给目标 Handler 处理。主线程的 Looper 由系统创建且永不退出。详见本章"消息队列三件套"一节。- Handler(消息收发器)
挂载在某个线程 Looper 上的消息发送和处理器。
sendMessage()往关联线程的消息队列投 Message,handleMessage()在关联线程上执行。一个线程可以有多个 Handler。详见本章"消息队列三件套"一节。- HandlerThread
Android 封装好的自带 Looper + MessageQueue 的后台线程。不用手动
Looper.prepare()/Looper.loop()——start()即进入消息循环。适合需要串行排队执行的后台任务。详见本章"HandlerThread"一节。
“Looper、Handler和HandlerThread”不使用未获授权的纸书正文;InformIT出版信息与授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。
为什么“Looper、Handler和HandlerThread”必须回到可观察状态
“Looper、Handler和HandlerThread”的学习结果不是记住类名,而是能预测“深入理解 Android 消息队列机制,学会用 Handler 在任意两个线程之间传递消息,并掌握 Handler 内存泄漏的经典解法。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。
第四版机制逐项深读
25. Loopers, Handlers, and HandlerThread
在“Looper、Handler和HandlerThread”中,“25. Loopers, Handlers, and HandlerThread”的线程安全不由Handler名称保证;共享状态、关闭顺序和回调引用仍需明确所有权。
Preparing RecyclerView to Display Images
在“Looper、Handler和HandlerThread”中,“Preparing RecyclerView to Display Images”的性能由帧时间、绑定分配与diff范围共同决定,不以单次滑动的主观流畅度验收。
Preparing to Download Bytes from a URL
在“Looper、Handler和HandlerThread”中,验证“Preparing to Download Bytes from a URL”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Downloading Lots of Small Things
在“Looper、Handler和HandlerThread”中,“Downloading Lots of Small Things”若依赖第四版时期API,应分开说明原书机制与现代平台政策,并用Android官方文档核对迁移边界。
Assembling a Background Thread
在“Looper、Handler和HandlerThread”中,验证“Assembling a Background Thread”用快速滚动、旋转和重复请求制造乱序,确认旧任务被取消、缓存键正确且主线程无磁盘网络。
Messages and Message Handlers
在“Looper、Handler和HandlerThread”中,分析“Messages and Message Handlers”记录post线程、执行线程、队列时刻和回传目标,后台线程不能直接触碰已销毁View。
Listening to the View Lifecycle
在“Looper、Handler和HandlerThread”中,“Listening to the View Lifecycle”服务于“深入理解 Android 消息队列机制,学会用 Handler 在任意两个线程之间传递消息,并掌握 Handler 内存泄漏的经典解法。”;解释要落到用户事件、Android所有者、状态变化、线程和可观察结果,并给出一个会推翻实现的反例。
Retained Fragments
在“Looper、Handler和HandlerThread”中,“Retained Fragments”的容器替换不是简单换画面;状态保存后提交、重复tag与嵌套管理器都会改变恢复结果。
For the More Curious: Solving the Image Downloading Problem
在“Looper、Handler和HandlerThread”中,“For the More Curious: Solving the Image Downloading Problem”用于推翻“深入理解 Android 消息队列机制,学会用 Handler 在任意两个线程之间传递消息,并掌握 Handler 内存泄漏的经典解法。”的顺利路径:先写预期,再引入一个边界输入、重建或平台差异,并用状态、日志与用户结果解释首个分叉。
For the More Curious: StrictMode
在“Looper、Handler和HandlerThread”中,分析“For the More Curious: StrictMode”记录post线程、执行线程、队列时刻和回传目标,后台线程不能直接触碰已销毁View。
Challenge: Observing View LifecycleOwner LiveData
在“Looper、Handler和HandlerThread”中,“Challenge: Observing View LifecycleOwner LiveData”把Entity模式、DAO合同和SQLite文件连接成单一事实源;编译期SQL校验不能替代迁移和真实数据反例。
Challenge: Improving ThumbnailDownloader's Lifecycle Awareness
在“Looper、Handler和HandlerThread”中,“Challenge: Improving ThumbnailDownloader's Lifecycle Awareness”通过FileProvider URI把写入能力临时交给相机应用,再按目标尺寸解码;验证要覆盖无相机、取消、零字节文件、旋转和内存上限。
Challenge: Preloading and Caching
在“Looper、Handler和HandlerThread”中,验证“Challenge: Preloading and Caching”用快速滚动、旋转和重复请求制造乱序,确认旧任务被取消、缓存键正确且主线程无磁盘网络。
“Looper、Handler和HandlerThread”验收回顾
“Looper、Handler和HandlerThread”只有在请求或消息时间线、取消、乱序与失败恢复能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。
章专属可重放状态实验
先预测“入队、后台下载、主线程回传、滚动复用与 View 销毁”发生后,ThumbnailDownloader、工作 Looper 与 viewLifecycleOwner应怎样改变请求队列、token、目标 holder、bitmap 和取消状态;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。
实验一:所有者—状态—结果合同
选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。
Owner · state · observable result
Looper、Handler和HandlerThread:状态合同
用 Looper、Handler 与 HandlerThread 串行下载缩略图并安全回传主线程
验证场景
第四版正式目录节点
looper-handler · 正常任务
25. Loopers, Handlers, and HandlerThread:固定 SDK、设备配置和初始状态,触发“入队、后台下载、主线程回传、滚动复用与 View 销毁”
冻结入口:25. Loopers, Handlers, and HandlerThread
记录ThumbnailDownloader、工作 Looper 与 viewLifecycleOwner的初始请求队列、token、目标 holder、bitmap 和取消状态
观察:post/execute 线程、token、URL、holder ID、取消与绑定日志中的“25. Loopers, Handlers, and HandlerThread”轨迹
预期:由ThumbnailDownloader、工作 Looper 与 viewLifecycleOwner提交请求队列、token、目标 holder、bitmap 和取消状态,并持续满足“回调只更新仍绑定同一请求的可见 View,销毁后队列可取消”
实验二:事件与生命周期轨迹
沿五次转换逐步执行“入队、后台下载、主线程回传、滚动复用与 View 销毁”。每一步只允许ThumbnailDownloader、工作 Looper 与 viewLifecycleOwner按职责提交状态,并持续核对“回调只更新仍绑定同一请求的可见 View,销毁后队列可取消”。
Deterministic event replay
Looper、Handler和HandlerThread:事件轨迹
不变量:回调只更新仍绑定同一请求的可见 View,销毁后队列可取消
交付证据:post/execute 线程、token、URL、holder ID、取消与绑定日志
实验三:章专属反例与同输入恢复
注入“Holder 已复用给新 URL,旧下载回调把错误图片写入当前行”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有post/execute 线程、token、URL、holder ID、取消与绑定日志一起恢复才算修复。
Fault · cancel · restore
Looper、Handler和HandlerThread:反例与恢复
故障:Holder 已复用给新 URL,旧下载回调把错误图片写入当前行
第 1 次使用相同 SDK、设备配置、初始状态与用户事件
保持正常输入不变,仅注入“Holder 已复用给新 URL,旧下载回调把错误图片写入当前行”
回调只更新仍绑定同一请求的可见 View,销毁后队列可取消
post/execute 线程、token、URL、holder ID、取消与绑定日志