一句话总结:
- 主线程 → “自带全套流水线,开箱即用” 。
- 子线程 → “毛坯房,需手动装修,但官方提供了精装方案
HandlerThread” 。
一、主线程 Handler:为何能“开箱即用”?
当Android应用启动时,系统在ActivityThread.main()方法中,已经为主线程(UI线程)自动完成了Looper的创建和启动(Looper.prepareMainLooper()和Looper.loop())。因此,主线程天然具备了消息循环机制,我们可以直接创建Handler实例,它会自动关联到主线程的Looper。
// 在主线程中,这样写是安全的
val mainHandler = Handler(Looper.getMainLooper()) // 推荐显式指定Looper
mainHandler.post {
// 这段代码将在主线程的消息队列中排队执行
updateUI()
}
二、揭秘Looper与线程的绑定:ThreadLocal的魔力
Looper是如何精确地与一个线程绑定的?答案是ThreadLocal。
ThreadLocal为每个线程都维护了一个独立的变量副本,线程之间互不干扰。Looper.prepare():这个方法会创建一个新的Looper实例,并将其存入当前线程的ThreadLocal变量中。如果该线程的ThreadLocal中已有Looper,则会抛出异常,这保证了一个线程最多只有一个Looper。Looper.myLooper():该方法会从当前线程的ThreadLocal中获取之前存入的Looper实例。
这就是为什么在子线程中必须先prepare(),否则new Handler()时(内部会调用myLooper())会因为找不到Looper而崩溃。
三、子线程 Handler:从手动搭建到HandlerThread最佳实践
子线程默认没有Looper,因此直接new Handler()会崩溃。
1. 原理演示:手动搭建(不推荐在项目中使用)
为了理解原理,我们可以手动搭建消息循环。这个过程分为三步:创建 -> 绑定 -> 循环。
thread {
// 1. 创建:为当前子线程准备一个Looper
Looper.prepare()
// 2. 绑定:创建一个Handler,它会自动绑定到当前线程的Looper
val workerHandler = Handler(Looper.myLooper()!!) { msg ->
Log.d("Worker", "在子线程处理消息: ${msg.what}")
true
}
// 3. 循环:启动消息循环。这是一个死循环,会阻塞当前线程,直到Looper退出
Looper.loop()
}
2. 官方推荐:HandlerThread(项目首选)
手动管理Looper既繁琐又容易出错。为此,Android提供了HandlerThread,它是一个内置了Looper的Thread。
HandlerThread为你做好了所有事:
- 在
run()方法中自动调用Looper.prepare()和Looper.loop()。 - 提供了安全的
quit()和quitSafely()方法来终止循环。
使用HandlerThread的完整示例:
// 1. 创建并启动HandlerThread
val handlerThread = HandlerThread("MyWorkerThread").apply { start() }
// 2. 创建一个与该子线程Looper绑定的Handler
// handlerThread.looper 会阻塞等待,直到Looper初始化完毕,是线程安全的
val workerHandler = Handler(handlerThread.looper)
// 3. 从任何线程向该子线程发送任务
workerHandler.post {
// 这段代码将在"MyWorkerThread"子线程中按顺序执行
Log.d("HandlerThread", "Executing background task...")
}
// 4. 不再使用时,安全退出
// 在Activity的onDestroy或适当的生命周期中调用
handlerThread.quitSafely()
四、总结对比
| 场景 | 主线程 | 子线程(HandlerThread) |
|---|---|---|
| Looper | 系统自动创建并启动 | HandlerThread自动创建并启动 |
| Handler创建 | Handler(Looper.getMainLooper()) | Handler(handlerThread.looper) |
| 生命周期 | 随应用进程 | 需要手动调用quit()或quitSafely()来终止 |
| 典型用途 | 处理UI更新、响应用户交互 | 执行串行的后台任务(如文件读写、数据库操作)、避免并发问题 |