Android并发基石:从Thread、Looper到HandlerThread的演进之路

293 阅读4分钟

一句话总结:

Thread 是“干活的工人”,Handler 是“传话的秘书”,HandlerThread 是“自带秘书的工人”——三者协作让 Android 应用既能高效干活,又能安全更新界面!


一、一切的起点:为何需要这套复杂机制?

Android 的 UI 工具包是单线程的。这意味着所有界面的更新操作都必须在主线程(UI 线程)中执行。如果在主线程执行网络请求、文件读写等耗时操作,就会导致界面卡顿甚至 ANR(应用无响应)。

  • 问题一:如何将耗时操作移出主线程?

    • 答案:使用 Thread(工人)。
  • 问题二:子线程完成任务后,如何安全地通知主线程更新 UI?

    • 答案:使用 Handler(秘书)及背后的消息队列机制。

二、核心“铁三角”:Handler、Looper 与 MessageQueue 的化学反应

Handler 并非独立存在,它是一个精巧系统的前台 API。这个系统的核心是“铁三角”:

  1. Thread - 舞台

    • 所有代码的执行都必须在某个线程这个“舞台”上。
  2. Looper & MessageQueue - 引擎与传送带

    • MessageQueue:一个先进先出(FIFO)的任务传送带,存放着由 Handler 发送过来的各种消息(Message)或任务(Runnable)。
    • Looper:一个永不停止的引擎。它通过 Looper.loop() 方法,死循环地从 MessageQueue 上取任务,然后执行它。
    • 关键:默认情况下,普通 Thread 没有这个引擎和传送带。你需要手动调用 Looper.prepare() 来创建它们,并通过 Looper.loop() 启动引擎。
  3. Handler - 秘书

    • 职责:作为面向开发者的接口,负责发送任务处理任务
    • handler.post(runnable) / handler.sendMessage(msg) :将一个任务或消息放入与其关联的 MessageQueue(传送带)上。
    • handleMessage(msg) :当 Looper(引擎)从传送带上取出消息时,会回调 Handler 的这个方法来处理。

Aha Moment:Handler 如何知道自己的“办公室”在哪?

当你 new Handler() 时,它会自动绑定到当前线程的 Looper。这是通过 ThreadLocal 实现的,Looper.prepare() 会将创建的 Looper 实例存入一个与当前线程绑定的 ThreadLocal 变量中,确保每个线程最多只有一个 Looper。


三、演进与封装:从 ThreadHandlerThread

理解了“铁三角”,再看 ThreadHandlerThread 的关系就一目了然。

阶段一:Thread(单纯的工人)

它只是一个执行单元,run() 方法执行完,线程就结束了。它没有自己的“任务传送带”(MessageQueue),无法持续接收新任务。

阶段二:带 LooperThread (手动组装)

我们可以在一个普通 Threadrun() 方法里手动创建消息循环:

class MyThread extends Thread {
    public Handler handler;
    @Override
    public void run() {
        Looper.prepare(); // 为工人安装传送带和引擎
        handler = new Handler(); // 雇佣一个秘书
        Looper.loop(); // 启动引擎,工人开始等待任务
    }
}

这个工人现在可以接收任务了,但每次都要这么写很繁琐。

阶段三:HandlerThread(自带秘书的工人 - 终极封装)

HandlerThread 就是 Android 官方帮你完成了上述“手动组装”的便捷类。

  • 它继承自 Thread
  • 在它的 run() 方法内部,自动调用了 Looper.prepare()Looper.loop()
  • 你只需启动它,然后通过 handlerThread.getLooper() 获取其 Looper 来创建 Handler,就可以向这个线程发送任务了。
  • 注意:因为它内部是死循环,所以不再使用时必须调用 quit()quitSafely() 来释放资源,否则会造成内存泄漏。(quitSafely 会处理完队列中已有任务再退出,是更推荐的方式)。

四、回归本源:主线程的真面目

Android 应用的主线程,本质上就是一个 HandlerThread。当应用启动时,系统已经为主线程创建好了 LooperMessageQueue,并启动了 loop() 循环。这就是为什么我们可以在主线程直接 new Handler() 而无需手动 Looper.prepare() 的原因。所有的 UI 操作、Activity 生命周期回调,都是作为 Message 被放进主线程的 MessageQueue 中,由主线程的 Looper 依次取出执行的。


五、现代化视角:拥抱 Kotlin 协程

虽然 Handler 这套机制是 Android 并发的基础,但在现代开发中,对于绝大多数“后台执行任务,然后回主线程更新UI”的场景,Kotlin 协程提供了更简洁、更不易出错的方案。

对比:

// HandlerThread 方案
val handlerThread = HandlerThread("worker").apply { start() }
val workerHandler = Handler(handlerThread.looper)
workerHandler.post {
    val data = fetchData() // 在 worker 线程
    mainHandler.post { 
        updateUi(data) // 在 main 线程
    }
}

// 协程方案
lifecycleScope.launch(Dispatchers.IO) { // 启动一个IO协程
    val data = fetchData() // 已在IO线程
    withContext(Dispatchers.Main) {
        updateUi(data) // 切换到Main线程
    }
}

协程通过结构化并发和更直观的线程切换语法,极大地降低了异步编程的复杂度。但理解 Handler 机制,依然是深入理解 Android 框架运行原理的必经之路。