Handler 机制 学习笔记

3 阅读4分钟

Handler 机制是什么

Handler 是 Android 中线程间消息通信与主线程任务调度的核心机制。
其本质是 Android Event Loop 模型的核心实现。
整体采用消息驱动模型,基于按 when 升序排列的有序单链表, 实现事件的延时调度与分发。

成员

主要成员有 Handler:消息的发生和处理者, Message:消息的载体,存标记、数据、延迟时间、目标Handler Looper:轮询器,死循环从 MessageQueue 取 Message,分发给Handler MessageQueue:消息队列,按 when 升序的 升序排列的有序单链表。

技术选型

为什么不用BlockingQueue 而是用 链表实现

    普通 BlockingQueue 只能在队列为空时阻塞,
    而 MessageQueue 需要支持“消息未到执行时间时阻塞等待”,
    因此 Android 基于 nativePollOnce + epoll_wait(timeout)实现基于时间的事件调度。

整体工作流程

  1. Handler 通过 post/sendMessage 向MessageQueue 投递 message
  2. Looper 通过loop() 循环从 MessageQueue 取消息,然后丢给msg.target.dispatchMessage处理
  3. Handler 通过 dispatchMessage 按照 msg.callback > handler.callback > handler.handleMessage处理消息
graph TD
    A[UI主线程] --> B[创建Handler实例]
    B --> C[重写handleMessage方法]
    
    D[子线程] --> E[构建Message对象]
    E --> F[handler.sendMessage/ sendEmptyMessage]
    
    F --> G[Message进入MessageQueue消息队列]
    G --> H[Looper无限循环轮询MessageQueue]
    H --> I[取出Message分发给对应Handler]
    I --> J[回调handleMessage处理消息]
    J --> K[更新UI/业务逻辑处理]

关键核心点:

  • 线程绑定
    • 一个线程最多一个 Looper,通过ThreadLocal保证唯一性,
    • 一个Looper持有一个MessageQueue,
    • 一个Handler只能绑定一个 Looper、
    • 一个线程可以有多个Handler
  • 阻塞机制
    • 队列无消息通过 nativePollOnce(-1)阻塞,消息入队 通过nativeWake()唤醒,不耗 CPU。
    • 消息未到执行时间,通过 nativePollOnce(timeout) 实现有限等待,到时自动唤醒
  • 优先级调度
    • 遇到同步屏障时:会阻塞同步消息,让异步消息先执行
    • Choreographer 利用同步屏障+异步消息, 保证 VSYNC 到来时 UI 刷新任务优先执行。
  • 碎片时间利用
    • 通过 IdleHandler 利用 CPU 空闲时间

常见坑:

  • 静态 Handler + 弱引用防内存泄漏(内部handler类 隐式持有Activity 引用 / activity 销毁时,延时消息未执行)

  • 退出页面清空消息: removeCallbacksAndMessages 避免无效消息回调

  • Looper 使用前必须 用 Looper.prepare() 初始化, 主线程在 ActivityThread.main中用 Looper.prepareMainLooper() 初始化过了

  • 通过 Message 池取消息,用完及时回收,避免频繁GC

高级机制

IdleHandler

可以看作消息队列空闲时执行的任务,

执行时机: 1.执行完最后一个消息的下次循环 2.队首消息未到执行时间,新消息入队唤醒

queueIdle() : 返回 True,执行后重新入队,false,不入队

**使用场景:**延迟初始化等、二级页面预加载等低优先级任务

同步屏障:

本质:target == null 的Message

使用postSyncBarrier()/removeSyncBarrier() [@hide API,通过反射获取]

本质是:“阻塞同步消息,但允许异步消息优先执行”的调度机制。

异步消息: isAsynchronous() 返回 true的Message

使用异步消息: 1. msg.setAsynchronous(true) 2. 通过Handler(async: true)的Handler

基于 Handler + Looper 的 ANR / 卡顿监控实现

Printer 卡顿监控

原理 Looper.loop()/loopOnce()中

    final Printer logging = me.mLogging;
    logging.println(">>>>> Dispatching to "...);
    msg.target.dispatchMessage(msg);
    logging.println("<<<<< Finished to "...);

所以: Dispatching = Message 开始执行 、Finished = Message 执行结束 、我们只要记录时间差即可。

最小实现

    class LooperBlockMonitor(
        private val blockThread: Long = 1000L
    ) {
        private var startTime = 0L
    
        fun start() {
            Looper.getMainLooper().setMessageLogging { log ->
                when {
                    log.startsWith(">>>>> Dispatching to ") -> {
                        startTime = SystemClock.uptimeMillis()
                    }
    
                    log.startsWith("<<<<< Finished to ") -> {
                        val cost = SystemClock.uptimeMillis() - startTime
                        if (cost > blockThread) {
                            Log.e(
                                "BlockMonitor",
                                "MainThread Blocked: ${cost}ms"
                            )
                        }
                    }
                }
            }
        }
    }

缺陷: Printer 只能事后统计、无法实时检测主线程已经卡死、而且不知道具体卡在哪了,

WatchDog 方案

原理

定期往主线程 post 一个任务,如果任务长时间没执行,说明主线程 Event Loop 卡死

工作流程

    子线程:
    while(true) {
        1. post 一个 marker 到主线程
        
        2. sleep(5s)
        
        3.marker 还没执行
           => 主线程卡死
    }

为什么 WatchDog 更强? 因为它检测的是: 主线程 Event Loop 是否还能继续消费消息、而不是,单个 Message 的耗时 最小实现

class WatchDog(private val timeout: Long = 5000L) {
    private val mainHandler = Handler(Looper.getMainLooper())

    @Volatile
    private var responded = false

    fun start() {
        Thread {
            while (true) {
                responded = false
                mainHandler.post {
                    responded = true
                }
                try {
                    Thread.sleep(timeout)
                } catch (e: Exception) {
                    e.printStackTrace()
                }

                if (!responded) {
                    Log.e("WatchDog", "ANR Detected")
                }
            }
        }.start()
    }
}

真正工程里必须做的事

仅仅打印一下,发送了ANR 是没有意义的、 真正关键的是:dump 主线程堆栈

dump 主线程堆栈

private fun dumpMainThreadStack() {
    val stackTrace = Looper
        .getMainLooper()
        .thread
        .stackTrace

    val builder = StringBuilder()
    stackTrace.forEach {
        builder.append(it.toString())
            .append("\n")
    }
    Log.e("WatchDog", builder.toString())
}

进一步优化(真正工程化)

真实稳定性框架: BlockCanary 、Matrix 、KOOM 还会做:

  1. 子线程采样:不仅 dump 主线程,还会dump 所有线程
  2. CPU 使用率:用来判断是真卡顿还是 CPU 满载
  3. 主线程 Message 采样:记录 哪个 Message 导致卡顿
  4. 文件落盘:ANR 后写 trace 文件
  5. native stack:真正高级的、native backtrace

基于 Looper + WatchDog 做主线程卡顿检测, 通过 stack sampling 获取主线程调用栈。 线上结合 xCrash 落盘 tombstone, 统一上传后台进行聚类分析。