Android HandlerThread:一个带 Looper 的后台线程
在 Android 开发里,我们经常会遇到一些不能放在主线程执行的任务,比如文件读写、日志写入、图片处理、轻量级数据库操作等。
这些任务有几个特点:
不能阻塞主线程
不一定需要并发执行
有时希望按顺序执行
有时需要通过
Handler发送消息
这时候就可以用到 HandlerThread。
一句话理解:
HandlerThread是一个自带Looper的子线程,它可以让我们在子线程中使用Handler处理消息和任务。
一、普通 Thread 的问题
普通 Thread 可以执行后台任务:
Thread {
// 子线程执行任务
}.start()
但普通线程有一个特点:
run()方法执行完,线程就结束了。
如果只是执行一次性任务,普通 Thread 没问题。
但如果我们希望这个线程一直存在,并且不断接收任务,就需要自己维护任务队列、线程循环、退出逻辑。
这就比较麻烦了。
Android 主线程为什么可以不断处理点击事件、绘制事件、消息回调?
因为主线程内部有:
LooperMessageQueueHandler
而 HandlerThread 的作用,就是让子线程也拥有这一套消息循环机制。
二、HandlerThread 是什么
HandlerThread 继承自 Thread。
它内部会帮我们创建 Looper,然后开启消息循环。
简单理解:
HandlerThread = Thread + Looper + MessageQueue
它不是四大组件,不是 Service,也不是任务调度框架。
它只是一个线程工具。
三、HandlerThread 的核心结构
HandlerThread 创建子线程。Looper 让这个子线程具备消息循环能力。MessageQueue 保存任务和消息。Handler 负责向这个线程投递任务。
四、基本使用方式
使用 HandlerThread 一般分为 5 步。
创建
HandlerThread调用
start()启动线程获取
Looper创建绑定到该
Looper的Handler使用完后调用
quitSafely()退出线程
示例代码:
class DemoActivity : AppCompatActivity() {
private lateinit var handlerThread: HandlerThread
private lateinit var workerHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handlerThread = HandlerThread("worker-thread")
handlerThread.start()
workerHandler = Handler(handlerThread.looper)
workerHandler.post {
Log.d("HandlerThread", "当前线程:${Thread.currentThread().name}")
Thread.sleep(2000)
runOnUiThread {
Log.d("HandlerThread", "任务完成,回到主线程更新 UI")
}
}
}
override fun onDestroy() {
super.onDestroy()
handlerThread.quitSafely()
}
}
这里要注意:
workerHandler.post {}里面的代码运行在子线程UI 更新必须切回主线程
不用时一定要退出
HandlerThread
五、HandlerThread 的执行流程
HandlerThread 启动后,内部大致流程是这样的:
核心逻辑可以理解成:
override fun run() {
Looper.prepare()
// 保存当前线程的 Looper
mLooper = Looper.myLooper()
// 开启消息循环
Looper.loop()
}
真实源码会比这个复杂一些,比如线程优先级、同步锁、onLooperPrepared() 回调等,但核心思想就是:
在子线程里创建 Looper,然后让 Looper 开始循环处理消息。
六、为什么要先 start 再 getLooper
使用 HandlerThread 时通常这样写:
val thread = HandlerThread("worker")
thread.start()
val handler = Handler(thread.looper)
不能只创建不启动:
val thread = HandlerThread("worker")
val handler = Handler(thread.looper)
因为 Looper 是在 HandlerThread.run() 里面创建的。
而 run() 只有在线程 start() 之后才会执行。
所以正确顺序是:
七、HandlerThread 和 Handler 的关系
HandlerThread 本身不直接提交任务。
真正提交任务的是 Handler。
例如:
workerHandler.post {
// 执行 Runnable
}
或者:
val msg = Message.obtain()
msg.what = 1
workerHandler.sendMessage(msg)
如果使用 sendMessage(),可以通过重写 Handler 的 handleMessage() 处理不同类型的任务:
private lateinit var handlerThread: HandlerThread
private lateinit var workerHandler: Handler
fun startWorker() {
handlerThread = HandlerThread("message-worker")
handlerThread.start()
workerHandler = object : Handler(handlerThread.looper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> {
Log.d("HandlerThread", "处理任务 1")
}
2 -> {
Log.d("HandlerThread", "处理任务 2")
}
}
}
}
workerHandler.sendEmptyMessage(1)
workerHandler.sendEmptyMessage(2)
}
post() 更适合直接执行一段代码。sendMessage() 更适合任务类型比较明确、需要根据 what 区分逻辑的场景。
八、quit 和 quitSafely 的区别
HandlerThread 不用之后,需要主动退出。
常见退出方式有两个:
一般推荐:
handlerThread.quitSafely()
如果你明确希望马上停止,不关心队列中剩余任务,才考虑 quit()。
九、HandlerThread 适合什么场景
HandlerThread 适合下面这些任务:
App 进程内的轻量后台任务
希望任务串行执行
任务之间有顺序要求
需要
Handler消息机制需要一个长期存在的后台工作线程
任务不要求 App 被杀后继续执行
典型例子:
日志串行写入
文件顺序读写
蓝牙指令队列
相机后台操作线程
传感器数据处理
图片压缩队列
轻量数据库操作
比如蓝牙指令就很适合用 HandlerThread。
因为很多蓝牙设备不适合同时发送多条指令,需要一条一条处理:
这类任务需要顺序执行,HandlerThread 就比较合适。
十、HandlerThread 不适合什么场景
HandlerThread 不适合下面这些任务:
尤其要注意:
HandlerThread只是进程内线程。App 进程没了,任务也就没了。
所以它不能替代 WorkManager。
十一、HandlerThread 和 IntentService 的关系
很多人学习 HandlerThread 时,会发现它的业务场景好像和以前的 IntentService 很像。
这种感觉是对的。
因为 IntentService 内部本来就有类似的思想:
IntentService = Service + HandlerThread + Handler + 串行任务队列
以前我们经常用 IntentService 做:
后台上传
后台同步
串行处理 Intent
执行完自动停止的后台任务
但 IntentService 已经不推荐使用了。
原因不是 HandlerThread 机制有问题,而是 IntentService 属于 Service,Android 8.0 之后系统加强了后台执行限制,后台 Service 不能再像以前那样随意运行。
所以现在轻量任务可以用:
HandlerThreadExecutorKotlin 协程
而需要可靠调度的后台任务,应该优先考虑:
WorkManager前台服务
十二、HandlerThread、Executor、协程怎么选
如果只是普通轻量后台任务,现代项目里更常用:
Executors.newSingleThreadExecutor()
或者:
lifecycleScope.launch(Dispatchers.IO) {
// 后台任务
}
如果你明确需要 Handler 消息机制,比如延迟消息、消息队列、和 Android 某些组件配合,那么 HandlerThread 依然很有价值。
十三、常见问题
1. HandlerThread 是不是线程池?
不是。
HandlerThread 本质上是一个单线程。
它的任务是串行执行的。
如果你需要多个任务并发执行,更适合用线程池。
2. HandlerThread 会自动销毁吗?
不会。
你创建并启动之后,它会一直等待消息。
所以不用时要调用:
handlerThread.quitSafely()
否则可能造成线程泄漏。
3. HandlerThread 可以更新 UI 吗?
不可以。
HandlerThread 是子线程,不能直接更新 UI。
需要切回主线程:
runOnUiThread {
textView.text = "任务完成"
}
或者使用主线程 Handler:
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
textView.text = "任务完成"
}
4. HandlerThread 任务是串行的吗?
是的。
绑定到同一个 HandlerThread 的 Handler,发送到同一个 MessageQueue 的任务,会在这个线程里一个一个执行。
这也是它适合处理顺序任务的原因。
十四、一个更完整的封装示例
实际开发中,可以简单封装一下:
class WorkerThread(name: String) {
private val handlerThread = HandlerThread(name).apply {
start()
}
private val workerHandler = Handler(handlerThread.looper)
fun post(task: () -> Unit) {
workerHandler.post {
task()
}
}
fun postDelayed(delayMillis: Long, task: () -> Unit) {
workerHandler.postDelayed({
task()
}, delayMillis)
}
fun stop() {
workerHandler.removeCallbacksAndMessages(null)
handlerThread.quitSafely()
}
}
使用:
class DemoActivity : AppCompatActivity() {
private val workerThread = WorkerThread("demo-worker")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
workerThread.post {
Log.d("WorkerThread", "执行后台任务")
}
}
override fun onDestroy() {
super.onDestroy()
workerThread.stop()
}
}
这个封装适合简单场景。
如果任务很复杂,还需要考虑异常处理、任务取消、生命周期绑定等问题。
十五、总结
HandlerThread 是 Android 中一个很经典的线程工具。
它的核心价值是:
让子线程也拥有 Looper 和 MessageQueue,从而可以通过 Handler 串行处理任务。
它适合进程内的轻量级串行后台任务。
但它不是可靠后台任务框架,也不能保证 App 被杀后继续执行。
简单记忆:
最后一句话总结:
HandlerThread不是过时的IntentService替代品,而是更底层的线程工具;以前IntentService内部使用了类似思想,但现在轻量任务应该回归到HandlerThread、Executor或协程,可靠后台任务则交给WorkManager。
Android HandlerThread:一个带 Looper 的后台线程
https://lautung.com/archives/vT0yX5Ro
评论