Android HandlerThread 笔记

10 阅读7分钟

什么是 HandlerThread?

简单来说,HandlerThread 是一个内置了 Looper 的线程。它继承自 Thread,在 run() 方法中通过 Looper.prepare()Looper.loop() 创建了消息循环,使得这个线程可以持续处理发送给它的消息或 Runnable 任务。

在Android中,主线程(UI线程)就是一个 Looper 线程,它不断从消息队列中取出消息并执行。但主线程不能做耗时操作,否则会ANR。于是,我们需要在工作线程里处理耗时任务,但如果这个线程没有 Looper,我们就无法通过 Handler 向它发送消息(因为 Handler 需要绑定一个 Looper 才能工作)。

HandlerThread 的出现就是为了解决这个痛点:创建一个自带消息队列的后台线程,让你可以像在主线程一样,通过 Handler 把任务派发给这个线程去执行,并且任务会排队,按顺序处理。

为什么需要 HandlerThread?

HandlerThread 之前,如果你想在子线程中执行一系列任务,可能需要自己实现一个带消息队列的线程,写不少模板代码。而 HandlerThread 封装了这一切,让开发者专注于业务逻辑。

它的核心价值在于:

  1. 串行执行任务:所有通过 Handler 发送的消息(或 Runnable)会按顺序在同一个后台线程中执行,避免了多线程同步问题。
  2. 生命周期可控:可以随时通过 quit()quitSafely() 停止线程,释放资源。
  3. Handler 无缝配合:你只需要获取它的 Looper,然后创建 Handler,就可以像操作主线程一样操作这个后台线程。

如何使用 HandlerThread?

使用 HandlerThread 通常分为四步:创建、启动、创建Handler、使用Handler发送任务,最后在不需要时停止。

class HandlerThreadActivity : AppCompatActivity() {
    private lateinit var handlerThread: HandlerThread
    private lateinit var workerHandler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 1. 创建 HandlerThread,参数是线程名称,方便调试
        handlerThread = HandlerThread("WorkerThread")
        // 2. 启动线程,内部会调用 Looper.prepare() 和 Looper.loop()
        handlerThread.start()

        // 3. 获取 HandlerThread 的 Looper,创建 Handler
        //    这里的 Handler 会将消息发送到 HandlerThread 的消息队列
        workerHandler = Handler(handlerThread.looper) { msg ->
            // 这个回调会在 HandlerThread 线程中执行
            when (msg.what) {
                1 -> {
                    val data = msg.obj as String
                    // 模拟耗时操作
                    Thread.sleep(2000)
                    Log.d("HandlerThread", "处理数据: $data, 当前线程: ${Thread.currentThread().name}")
                }
            }
            true // 返回true表示消息已处理
        }

        // 4. 发送消息到工作线程
        workerHandler.sendMessage(Message.obtain().apply {
            what = 1
            obj = "Hello from MainThread"
        })

        // 也可以直接 post Runnable
        workerHandler.post {
            // 这里也是在工作线程执行
            Log.d("HandlerThread", "Runnable 执行, 当前线程: ${Thread.currentThread().name}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 5. 停止 HandlerThread,必须手动停止,否则线程会一直运行
        handlerThread.quitSafely() // 或者 quit()
        // 等待线程结束,避免内存泄漏
        handlerThread.join(1000)
    }
}

关键细节:

  • handlerThread.start() 必须在获取 Looper 之前调用,否则 handlerThread.looper 可能为 null(因为 Looper 是在 run() 方法中创建的)。
  • 一定要在不需要时(比如 Activity/Fragment 销毁时)调用 quitSafely()quit() 来停止线程。否则线程会一直运行,可能导致内存泄漏(因为 Handler 可能持有外部类的引用)。
  • quit() 会立即清空消息队列并终止循环;quitSafely() 则会等待队列中已有的消息处理完再终止,更安全。

深入原理:源码分析

让我们打开 HandlerThread 的源码(基于 Android API 30),看看它内部到底做了什么:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        // 1. 准备当前线程的 Looper
        Looper.prepare();
        // 2. 持有 Looper 对象,以便 getLooper() 返回
        synchronized (this) {
            mLooper = Looper.myLooper();
            // 3. 唤醒可能在等待 getLooper() 的线程
            notifyAll();
        }
        // 4. 设置线程优先级
        Process.setThreadPriority(mPriority);
        // 5. 回调,子类可以实现
        onLooperPrepared();
        // 6. 开始循环
        Looper.loop();
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 如果 mLooper 为 null,说明 Looper 还没有创建完成,则等待
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit(); // 直接退出,丢弃所有消息
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely(); // 安全退出,处理完已有消息
            return true;
        }
        return false;
    }
}

核心要点:

  1. 线程启动:调用 start() 后,run() 方法会在新线程中执行。
  2. Looper 创建Looper.prepare() 为当前线程(即这个 HandlerThread 线程)创建了一个 Looper,并自动关联了一个 MessageQueue
  3. 线程同步getLooper() 使用了 wait/notify 机制,确保在 Looper 创建完成之前,调用 getLooper() 的线程(通常是主线程)会等待,直到 mLooper 被赋值。这也是为什么 start() 之后必须稍等一会儿才能获取到有效的 Looper
  4. 消息循环Looper.loop() 启动无限循环,不断从消息队列中取出消息并分发处理。这个循环会一直运行,直到 Looperquit() 被调用。
  5. 退出循环quit()quitSafely() 会调用 Looper 的相应方法,终止消息循环,线程的 run() 方法执行完毕,线程自然结束。

HandlerThread 的典型使用场景

虽然现在Kotlin协程大行其道,但 HandlerThread 在某些场景下依然非常有用:

  1. 与需要 Looper 的系统组件配合:有些 Android 系统 API 要求传入一个 Looper 才能正常工作,比如:

    • new Handler(handlerThread.looper, callback) 当然是最直接的。
    • SoundPool 构造函数可以指定 Looper 用于回调。
    • AudioRecordMediaCodecMediaPlayer 的一些方法可能需要在具有 Looper 的线程中调用。
    • HandlerThread 可以为这些组件提供一个专用的后台线程 Looper,避免占用主线程。
  2. 串行处理后台任务:如果有一系列任务需要按顺序执行(比如数据库写操作、文件写入),且不想使用复杂的线程同步,HandlerThread 是一个非常轻量级的解决方案。

  3. 替代 IntentServiceIntentService 内部实际上就是使用 HandlerThread 实现的,它把每次启动的 Intent 封装成消息发送到 HandlerThread 中顺序处理。不过 IntentService 在 Android 11 已被废弃,官方推荐用 WorkManagerJobScheduler,但如果你需要一个简单的、顺序执行的后台任务队列,自己封装一个 HandlerThread 依然简洁高效。

注意事项与最佳实践

  1. 内存泄漏风险:这是最容易踩的坑。因为 Handler 会持有外部类的隐式引用(如果你用内部类或匿名内部类创建 Handler),而 Handler 的消息可能还在队列中未处理,导致外部类无法被回收。解决方法是:

    • Handler 声明为静态内部类,并持有外部类的弱引用。
    • 或者,在 Activity/Fragment 销毁时,调用 handlerThread.quitSafely() 清空消息队列,并移除所有 Handler 的回调和消息。
  2. 线程生命周期管理HandlerThread 一旦启动,会一直运行,直到你主动调用 quit()。务必在合适的时机(比如 onDestroyonStop)停止它。

  3. 不要执行过于耗时的任务:虽然 HandlerThread 是后台线程,但它的任务是串行执行的。如果一个任务耗时很长,会阻塞后续任务。对于长时间占用的任务(如网络监听),建议使用独立的线程或线程池。

  4. 与协程的选择:在新项目中,我通常优先使用 Kotlin 协程。协程更轻量,作用域可控,且能避免回调地狱。但如果你需要与基于 Handler 的老代码交互,或者你更熟悉消息驱动模型,HandlerThread 依然是个可靠的选择。

总结

特性说明
本质自带 LooperThread
用途创建一个有消息队列的后台线程,用于串行处理任务
核心方法start()getLooper()quit() / quitSafely()
优点简单易用,自动处理消息循环,任务串行,无需同步
缺点需手动管理生命周期,任务可能阻塞后续任务,不适合并行处理
适用场景需要 Looper 的系统组件、串行后台任务、替代简单版的 IntentService

总而言之,HandlerThread 是 Android 消息机制的一个经典应用,理解它有助于你更深刻地理解 HandlerLooperMessageQueue 这三驾马车。即使在现代开发中,它可能不再是首选,但遇到特定场景时,它依然是一把锋利的小刀。