2、主线程本质

咱们先把话说透——Android 主线程,就是 UI 线程。它干的事很纯粹:处理用户触摸、刷新界面、分发事件、执行 Activity 生命周期。说白了,所有你能看到的、摸到的,都靠它。

但问题也在这儿。主线程一旦被堵住,ANR 就来了。我见过太多因为主线程做 IO 操作或者死循环导致的 ANR,嗯……项目上线前一天才被发现,那叫一个酸爽。

UI 线程的职责

主线程到底要管哪些活?我列个清单你就明白了:

  • 用户输入响应:点击、滑动、长按等事件分发
  • 界面重绘:执行 onDraw、layout、measure
  • 生命周期回调:onCreate、onResume、onDestroy 等
  • 广播接收:BroadcastReceiver 的 onReceive
  • Service 绑定:bindService 的回调
  • Handler 异步消息:来自其他线程的 UI 更新请求
注意:主线程不能做网络请求、大文件读写、复杂计算。这些操作超过 5 秒就会触发 ANR(前台广播是 10 秒,后台稍宽松但也不建议)。

Handler 与 Looper 机制

你可能会问:主线程是怎么知道什么时候该做什么事的?答案就是 Looper 和 Handler 这对搭档。

Looper 是个无限循环。它从 MessageQueue 里取消息,取到一条就交给对应的 Handler 去处理。主线程在 ActivityThread.main() 里就启动了 Looper,所以主线程永远活着,等消息。

我曾经在一个老项目里看到有人自己 new 了一个 Looper 却不调用 quit(),结果线程一直挂在那,内存泄漏。从那以后我对 Looper 的生命周期格外敏感。

Handler 负责两件事:发消息和处理消息。你可以通过 Handler 把任务扔到主线程队列里,也可以让它延迟执行。比如这样:

Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
    @Override
    public void run() {
        // 更新 UI,这里已经在主线程了
        textView.setText("Hello");
    }
});
技巧:我习惯用 View.post() 来简化代码,它本质也是通过 Handler 实现的。但要注意,如果 View 还没 attach 到 Window,消息不会执行。

MessageQueue 模型

MessageQueue 是个单链表,按时间排序。每一条 Message 都携带目标 Handler、what 标识、携带的数据,还可能有一个 Runnable。Looper 循环取消息时,如果消息的 when 时间到了就分发,否则阻塞等待。

你想想看,如果主线程在处理消息的过程中消耗了 3 秒,那 Looper 就卡住了——后续的消息排队等,用户点击、绘制全都延迟。这就是 ANR 的根源。

来看一张完整的内部结构图:

Android 主线程消息模型 主线程 (UI Thread) Looper MessageQueue Handler handleMessage / run 创建 & 启动 循环取消息 发送消息 分发到 Handler 关键流程 1. Handler.post(Runnable) 或 sendMessage → 消息入队 MessageQueue 2. Looper.loop() 不断从 MessageQueue 取出下一条消息 3. 调用 Handler.dispatchMessage → 回调 handleMessage 或执行 Runnable

这张图你看明白了吗?主线程里有一个 Looper,它不停从 MessageQueue 抽消息,MessageQueue 里排队的消息来自各个 Handler(可能是主线程自己发的,也可能是子线程发的)。每一条消息最终交给 Handler 处理,执行的是 handleMessage 或 Runnable。

消息优先级与延迟

消息类型 特点 常见场景
同步消息 (普通 Message) 按时间排序,FIFO 更新 UI、处理点击
异步消息 可被屏障消息跳过优先处理 Choreographer 帧回调
屏障消息 (SyncBarrier) 标记其后只处理异步消息 垂直同步、VSync 应用
延时消息 设置 when = now + delay 自动滚动、超时任务
总结:主线程本质上是个消息泵——Looper 泵取 MessageQueue 里的消息,交给 Handler 执行。任何环节耗时过长(比如 Handler.handleMessage 里做了 IO),泵就会停,ANR 就会来。搞清楚这个模型,你排查问题就有的放矢了。