4. HandlerThread 与 Looper 封装:自定义 Looper 消息循环、优先级设置、避免死锁、与 Native 层的桥接

本章深入 AOSP 中 HandlerThread 与 Looper 的底层机制,从源码角度拆解如何构建自定义消息循环、控制线程优先级、规避典型死锁场景,并实现 Java 层与 Native 层(C++)之间的回调桥接。掌握这些技术是构建高性能模块边界的关键前提。

4.1 原理:HandlerThread 与 Looper 的生命周期

HandlerThread 本质是一个持有 Looper 的专用线程。其生命周期包含四个阶段:

  • 构造与启动:调用 start() 后,线程进入 run() 方法,内部调用 Looper.prepare() 创建消息队列,然后执行 Looper.loop() 进入消息循环。
  • 消息分发:通过 Handler 向该线程的 MessageQueue 投递消息,由 Looper 单线程顺序处理。
  • 退出循环:调用 quitSafely() 或 quit() 终止 Looper,释放消息队列。
  • 线程终结:消息循环结束后 run() 返回,线程自然结束。

自定义 Looper 消息循环通常指重写或包装 HandlerThread,添加额外的初始化、清理、优先级调整逻辑。

4.2 实现流程:自定义 Looper 消息循环

以下是一份完整的自定义封装示例,实现带名称、优先级和退出回调的工作线程:

public class PriorityHandlerThread extends Thread {
    private Looper looper;
    private Handler handler;
    private int priority;

    public PriorityHandlerThread(String name, int priority) {
        super(name);
        this.priority = priority;
    }

    @Override
    public void run() {
        // 设置线程优先级(1~10,5为默认)
        android.os.Process.setThreadPriority(priority);
        Looper.prepare();
        synchronized (this) {
            looper = Looper.myLooper();
            handler = new Handler(looper);
            notifyAll(); // 唤醒等待获取looper的线程
        }
        Looper.loop();
        // loop退出后执行清理
        synchronized (this) {
            looper = null;
            handler = null;
        }
    }

    public Looper getLooper() {
        if (!isAlive()) return null;
        synchronized (this) {
            while (isAlive() && looper == null) {
                try { wait(); } catch (InterruptedException e) { }
            }
            return looper;
        }
    }

    public Handler getHandler() {
        if (handler == null) getLooper();
        return handler;
    }

    public boolean quitSafely() {
        Looper l = getLooper();
        if (l != null) {
            l.quitSafely();
            return true;
        }
        return false;
    }
}
要点:
- 使用 synchronized + wait/notify 模式安全暴露 Looper 对象。
- android.os.Process.setThreadPriority() 接受 THREAD_PRIORITY_DEFAULT (0) 或负值(如 -10 表示高优先级),不要与 Java 的 Thread.setPriority() 混淆。
- quitSafely() 会处理完已排队的消息后再终止循环,quit() 则立即终止。

4.3 优先级设置与线程调度策略

AOSP 中线程优先级与调度策略密切关联。下表列出了常用优先级常量及其对响应性的影响:

优先级常量 值 nice 值(Linux) 适用场景
THREAD_PRIORITY_DEFAULT 0 0 普通后台线程
THREAD_PRIORITY_LOWEST 19 19 批处理、非实时
THREAD_PRIORITY_BACKGROUND 10 10 低优先级后台任务
THREAD_PRIORITY_FOREGROUND -2 -2 前台 UI 辅助线程
THREAD_PRIORITY_URGENT_DISPLAY -8 -8 显示刷新、高帧率关键路径
经验法则: 避免使用 -20 或 THREAD_PRIORITY_URGENT_AUDIO(-16)等极高优先级处理非关键业务,否则可能引发系统级调度抖动。

4.4 避免死锁:Looper 场景下的典型陷阱

在 HandlerThread 中使用 Handler 发送消息时,如果发送者线程与消息处理线程之间存在循环依赖,极易产生死锁。以下列出三种常见模式及解决方案:

  • 模式A:同步跨线程调用 —— HandlerThread 中的处理逻辑调用了发送线程的同步方法,而发送线程又在等待 HandlerThread 完成消息处理。
  • 模式B:双重锁定 —— 在 synchronized 块内通过 Handler 向同一线程发送消息,该消息处理时又尝试获取同一把锁。
  • 模式C:quit 时的回调阻塞 —— Looper 退出前触发的清理回调尝试向自身发送新消息,导致死循环。

通用对策:

  • 使用 post() 而非 sendMessage() 并配合 runOnUiThread() 避免显式同步。
  • 在 Handler 内部使用 HandlerThread 的 quit() 之前,先移除所有待处理消息:handler.removeCallbacksAndMessages(null)。
  • 设置超时检测:对 Looper.loop() 无法退出的情况,利用 Handler 定期发送心跳并检查循环状态。

4.5 与 Native 层的桥接:从 Java Looper 到 C++ Looper

AOSP 中 Native 层(C++)也拥有完整的 Looper 机制(android::Looper),允许 Java 层将消息投递到 Native 线程,反之亦然。桥接的关键是 MessageQueue 的 nativePollOnce 和 nativeWake 方法。

桥接步骤:

  1. 在 Java 层创建 HandlerThread 并获取其 Looper。
  2. 通过 JNI 创建 android::Looper 对象,并注册 messageReceived 回调。
  3. 在 Java 侧 Handler 的 handleMessage 中,调用 JNI 函数将消息内容转发至 Native 层。
  4. Native 层使用 android::Looper::addFd 监控文件描述符,实现事件驱动。
典型 JNI 桥接代码片段(C++ 侧):
// native_bridge.cpp
#include 
#include 

static ALooper* gNativeLooper = nullptr;

extern "C" JNIEXPORT void JNICALL
Java_com_example_NativeBridge_init(JNIEnv* env, jobject thiz, jobject looperObj) {
    // 将 Java Looper 注册到 ALooper_forThread()
    // 注意:需要确保当前线程已关联 Java Looper
    ALooper* nativeLooper = ALooper_forThread();
    // 注册回调
    ALooper_addFd(nativeLooper, -1, ALOOPER_EVENT_INPUT, &nativeCallback, nullptr);
    gNativeLooper = nativeLooper;
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_NativeBridge_sendMessageToNative(JNIEnv* env, jobject thiz, jint msg) {
    if (gNativeLooper) {
        // 通过 ALooper_wake 或直接投递消息
        // 实际应用中会使用管道或 eventfd
    }
}

static int nativeCallback(int fd, int events, void* data) {
    // Native 层处理逻辑
    return 1; // 持续监听
}

4.6 调试技巧与常见错误

调试方法:

  • 使用 adb shell dumpsys activity threads 查看所有 HandlerThread 的状态。
  • 在 HandlerThread 的 run() 中打印线程 ID 和 Looper 哈希,确认消息循环属于正确线程。
  • 利用 StrictMode 检测跨线程锁:StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls().build())。

常见错误:

现象原因解决方案
Handler 回调未执行 Looper 尚未创建或已退出 使用 getLooper() 并确保线程已经 start() 且 Looper.prepare() 已调用
死锁导致 ANR HandlerThread 内部同步等待自身消息 重构为异步方式或使用 post() 并返回回调
Native 层收不到消息 ALooper_forThread() 返回 nullptr 确保在已创建 Java Looper 的线程中调用此函数,或使用 ALooper_prepare()

4.7 性能与稳定性优化

  • 消息池复用:避免每次 obtainMessage() 都分配新对象,考虑使用 Handler.obtainMessage() 从池中获取。
  • 批量处理:对于高频率消息(如传感器数据),在 handleMessage() 内尽量合并处理,减少线程唤醒次数。
  • 优先级继承:从 Native 层桥接回来的消息应携带原始请求的优先级,防止低优先级消息被高优先级线程处理导致延迟反转。
  • idleHandler 使用:当 MessageQueue 空闲时,可以利用 IdleHandler 执行低优先级清理任务,避免单独开线程。
注意: 在 IdleHandler 内执行长时间操作会导致 Looper 无法及时处理下一个正常消息,引发延迟。

4.8 整体架构图

下面的 SVG 展示了 Java Looper 与 Native Looper 桥接时的消息流与线程关系:

Java Looper → Native Looper 桥接架构 Java HandlerThread Looper + MessageQueue Handler.dispatchMessage() JNI 桥接层 nativePollOnce / nativeWake Native Looper ALooper + fd 监控 nativeCallback() eventfd / 管道触发 关键说明 1. Java HandlerThread 通过 MessageQueue.nativePollOnce() 进入休眠等待。 2. Native 层可以通过 ALooper 的 addFd 注册事件源,唤醒 Java 层。 3. 双向桥接须保证线程关联性:Native 侧的 Looper 必须在 Java 侧已 prepare 的线程中创建。

4.9 总结