在 Android 开发里,我们经常会遇到一些不能放在主线程执行的任务,比如文件读写、日志写入、图片处理、轻量级数据库操作等。

这些任务有几个特点:

  • 不能阻塞主线程

  • 不一定需要并发执行

  • 有时希望按顺序执行

  • 有时需要通过 Handler 发送消息

这时候就可以用到 HandlerThread

一句话理解:

HandlerThread 是一个自带 Looper 的子线程,它可以让我们在子线程中使用 Handler 处理消息和任务。


一、普通 Thread 的问题

普通 Thread 可以执行后台任务:

Thread {
    // 子线程执行任务
}.start()

但普通线程有一个特点:

run() 方法执行完,线程就结束了。

如果只是执行一次性任务,普通 Thread 没问题。

但如果我们希望这个线程一直存在,并且不断接收任务,就需要自己维护任务队列、线程循环、退出逻辑。

这就比较麻烦了。

Android 主线程为什么可以不断处理点击事件、绘制事件、消息回调?

因为主线程内部有:

  • Looper

  • MessageQueue

  • Handler

HandlerThread 的作用,就是让子线程也拥有这一套消息循环机制。


二、HandlerThread 是什么

HandlerThread 继承自 Thread

它内部会帮我们创建 Looper,然后开启消息循环。

简单理解:

HandlerThread = Thread + Looper + MessageQueue

它不是四大组件,不是 Service,也不是任务调度框架。

它只是一个线程工具。


三、HandlerThread 的核心结构

flowchart TD A["HandlerThread"] --> B["Thread"] A --> C["Looper"] C --> D["MessageQueue"] E["Handler"] --> D D --> F["HandlerThread 子线程执行任务"]

HandlerThread 创建子线程。
Looper 让这个子线程具备消息循环能力。
MessageQueue 保存任务和消息。
Handler 负责向这个线程投递任务。


四、基本使用方式

使用 HandlerThread 一般分为 5 步。

  1. 创建 HandlerThread

  2. 调用 start() 启动线程

  3. 获取 Looper

  4. 创建绑定到该 LooperHandler

  5. 使用完后调用 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 启动后,内部大致流程是这样的:

flowchart TD A["调用 start()"] --> B["进入 HandlerThread.run()"] B --> C["Looper.prepare()"] C --> D["创建 MessageQueue"] D --> E["保存 Looper"] E --> F["Looper.loop()"] F --> G["不断从 MessageQueue 取消息"] G --> H["交给 Handler 执行"]

核心逻辑可以理解成:

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() 之后才会执行。

所以正确顺序是:

flowchart TD A["new HandlerThread"] --> B["start()"] B --> C["run()"] C --> D["Looper.prepare()"] D --> E["getLooper()"] E --> F["创建 Handler"]

七、HandlerThread 和 Handler 的关系

HandlerThread 本身不直接提交任务。

真正提交任务的是 Handler

例如:

workerHandler.post {
    // 执行 Runnable
}

或者:

val msg = Message.obtain()
msg.what = 1
workerHandler.sendMessage(msg)

如果使用 sendMessage(),可以通过重写 HandlerhandleMessage() 处理不同类型的任务:

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 不用之后,需要主动退出。

常见退出方式有两个:

方法

说明

quit()

立刻退出消息循环,队列中未处理的任务可能被丢弃

quitSafely()

安全退出,会尽量处理已经到期的消息,未到期的延迟消息不会继续执行

一般推荐:

handlerThread.quitSafely()

如果你明确希望马上停止,不关心队列中剩余任务,才考虑 quit()


九、HandlerThread 适合什么场景

HandlerThread 适合下面这些任务:

  • App 进程内的轻量后台任务

  • 希望任务串行执行

  • 任务之间有顺序要求

  • 需要 Handler 消息机制

  • 需要一个长期存在的后台工作线程

  • 任务不要求 App 被杀后继续执行

典型例子:

  • 日志串行写入

  • 文件顺序读写

  • 蓝牙指令队列

  • 相机后台操作线程

  • 传感器数据处理

  • 图片压缩队列

  • 轻量数据库操作

比如蓝牙指令就很适合用 HandlerThread

因为很多蓝牙设备不适合同时发送多条指令,需要一条一条处理:

flowchart TD A["发送连接指令"] --> B["发送认证指令"] B --> C["发送读取数据指令"] C --> D["发送断开指令"]

这类任务需要顺序执行,HandlerThread 就比较合适。


十、HandlerThread 不适合什么场景

HandlerThread 不适合下面这些任务:

场景

更合适的方案

App 退出后仍要执行

WorkManager

设备重启后仍要执行

WorkManager

需要满足网络、充电等条件

WorkManager

大量并发任务

线程池 Executor

和页面生命周期强绑定

Kotlin 协程 + lifecycleScope

长时间运行且用户需要感知

前台服务 ForegroundService

尤其要注意:

HandlerThread 只是进程内线程。App 进程没了,任务也就没了。

所以它不能替代 WorkManager


十一、HandlerThread 和 IntentService 的关系

很多人学习 HandlerThread 时,会发现它的业务场景好像和以前的 IntentService 很像。

这种感觉是对的。

因为 IntentService 内部本来就有类似的思想:

IntentService = Service + HandlerThread + Handler + 串行任务队列

以前我们经常用 IntentService 做:

  • 后台上传

  • 后台同步

  • 串行处理 Intent

  • 执行完自动停止的后台任务

IntentService 已经不推荐使用了。

原因不是 HandlerThread 机制有问题,而是 IntentService 属于 Service,Android 8.0 之后系统加强了后台执行限制,后台 Service 不能再像以前那样随意运行。

所以现在轻量任务可以用:

  • HandlerThread

  • Executor

  • Kotlin 协程

而需要可靠调度的后台任务,应该优先考虑:

  • WorkManager

  • 前台服务


十二、HandlerThread、Executor、协程怎么选

方案

适合场景

HandlerThread

单线程串行任务,并且需要 Looper / Handler

Executor

普通 Java/Kotlin 后台任务,适合线程池管理

Kotlin 协程

现代 Kotlin 项目,适合生命周期管理和异步任务

WorkManager

可靠后台任务,App 退出后仍要尽量执行

ForegroundService

必须立刻执行、耗时较长、用户需要感知的任务

如果只是普通轻量后台任务,现代项目里更常用:

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 任务是串行的吗?

是的。

绑定到同一个 HandlerThreadHandler,发送到同一个 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

普通异步任务

Executor / 协程

页面生命周期内任务

lifecycleScope / viewModelScope

App 退出后仍要执行

WorkManager

必须立即执行且用户可感知

ForegroundService

最后一句话总结:

HandlerThread 不是过时的 IntentService 替代品,而是更底层的线程工具;以前 IntentService 内部使用了类似思想,但现在轻量任务应该回归到 HandlerThreadExecutor 或协程,可靠后台任务则交给 WorkManager