Handler(三):主线程、HandlerThread、Binder 线程到底怎么区分?

0 阅读12分钟

Handler(三):主线程、HandlerThread、Binder 线程到底怎么区分?

前两篇解决的是 Handler 机制本身:

第一篇:
    post 之后,Runnable 为什么会在目标线程执行。
​
第二篇:
    Message 入队以后,为什么不是马上执行。

这篇把机制放回 Android 系统线程里。

因为真实阅读源码和排障时,很多问题不是 post()next() 看不懂,而是线程概念混了:

主线程是不是 Handler?
HandlerThread 是不是 Handler?
普通 Thread 中直接 new Handler() 为什么可能崩溃?
Binder 线程和 Handler 线程是什么关系?
system_server 里为什么有 main、android.ui、android.display、android.io?

如果这些边界不清楚,很容易把所有问题都说成:

主线程卡了。
system_server 卡了。
Handler 卡了。

这些说法都太粗。它们没有告诉你目标 Looper 是哪条线程,也没有告诉你这条线程设计上应该做什么。

所以这篇只回答一个问题:

哪些线程长期运行 Looper?
哪些线程负责执行服务端 Binder 入口?
Binder 入口为什么又经常把后续任务交给 Handler?

先放核心结论:

主线程:
    Thread + MainLooper。
​
普通线程:
    默认只是 Thread,没有 Looper。
​
HandlerThread:
    Thread + Looper.prepare() + Looper.loop()。
​
Handler:
    持有某个 Looper 引用,是向其 MessageQueue 投递任务和取出后分发消息的入口。
​
Binder 线程:
    从 Binder 驱动接收事务,并在当前线程执行服务端 Binder 入口;
    入口代码可以直接处理,也可以把后续状态处理交给 Handler。
​
system_server 专用线程:
    多条带 Looper 的 ServiceThread,用来串行处理不同系统模块的状态。

本文源码以 Android 16 QPR2 的 android-16.0.0_r4 tag 为参考,正文只讨论 Handler 的稳定机制,不展开版本实现差异。

image.png

这张图按两个问题读:

第一问:
    这条线程是不是在跑 Looper?
​
第二问:
    如果在跑 Looper,它负责哪个系统域的串行状态处理?

这张图只画了 system_server 中长期运行 Looper 的专用线程。Binder 线程池不依靠 Looper.loop() 接收事务,因此没有画进这组 Handler 线程;第五节会单独展开它怎样执行服务端 IPC 入口。

一、主线程为什么天然有 Looper?

应用主线程的入口在 ActivityThread.main()

主线是:

ActivityThread.main()
  -> Looper.prepareMainLooper()
  -> new ActivityThread()
  -> thread.attach(false, startSeq)
  -> Looper.loop()

源码入口:

这说明主线程不是普通 Thread 加一个名字。它启动时就准备了 MainLooper,并进入 Looper.loop()

所以:

Handler mainHandler = new Handler(Looper.getMainLooper());

这行代码的意义不是“创建主线程”,而是:

创建一个投递入口;
这个入口持有 MainLooper 引用;
MainLooper 与应用主线程绑定。

主线程天然有 Looper,是因为 Android 在进程启动时替你准备好了。

二、普通线程为什么默认没有 Looper?

普通 Java 线程只负责执行 run()。它不会自动创建消息队列,也不会自动进入循环。

new Thread(() -> {
    Looper.myLooper(); // 默认是 null
}).start();

如果你在这种默认没有 Looper 的线程里隐式创建 Handler:

new Handler();

构造函数读取到的 Looper.myLooper()null,因此会直接抛出异常。如果线程此前手动执行过 Looper.prepare(),无参构造虽然不会崩溃,却会隐式绑定当前线程的 Looper;无参 Handler 构造被废弃,正是因为这种隐式选择容易造成崩溃、任务丢失、竞态或绑定错误线程。

但“普通线程默认没有自己的 Looper”不等于“普通线程不能创建任何 Handler”。它仍然可以显式持有其他线程的 Looper:

new Thread(() -> {
    Handler mainHandler = new Handler(Looper.getMainLooper());
    mainHandler.post(() -> updateUi());
}).start();

这里创建 Handler 的是普通线程,Handler 持有的却是 MainLooper 引用,回调仍由应用主线程执行。真正受限制的是“在没有 Looper 的线程上用无参构造隐式选择当前线程 Looper”,不是 new Handler(...) 这个动作本身。

源码入口:

所以普通线程和 Handler 线程的差别不是“是不是子线程”,而是:

普通线程:
    执行完 run() 就结束,没有消息循环。
​
带 Looper 的线程:
    prepare 后进入 loop,可以持续接收 Message。

三、HandlerThread 到底是什么?

HandlerThread 不是 Handler。

它是一个带 Looper 的 Thread。

源码主线很短:

HandlerThread.run()
  -> mTid = Process.myTid()
  -> Looper.prepare()
  -> mLooper = Looper.myLooper()
  -> notifyAll()
  -> Process.setThreadPriority(mPriority)
  -> onLooperPrepared()
  -> Looper.loop()

源码入口:

把它写成公式就是:

HandlerThread = Thread + Looper.prepare() + Looper.loop()

它解决的是:

我需要一条可以接收 Handler 消息的工作线程。

而 Handler 解决的是:

我需要一个入口,把任务投到某个 Looper 的 MessageQueue。

两者常一起用:

HandlerThread thread = new HandlerThread("camera-state");
thread.start();
​
Handler handler = new Handler(thread.getLooper());

这里对象关系是:

thread:
    真正执行任务的线程。
​
thread.getLooper():
    这条线程里的消息循环。
​
handler:
    投递到这条消息循环的入口。

所以不要说“HandlerThread 处理消息”。更准确的是:

HandlerThread 里的 Looper 取消息;
消息通过 target 回到 Handler;
最后由 Handler.dispatchMessage 分发回调。

四、HandlerThread 是线程池吗?

不是。

一个 HandlerThread 同一时间仍然只 dispatch 一个 Message。

Message A 正在执行:
    Message B 只能等。
​
Message A 做了慢 I/O:
    后面所有 Message 都被拖住。
​
Message A 等锁:
    这条 HandlerThread 上的串行状态机也一起停住。

这就是 HandlerThread 最容易被误用的地方。它适合承载需要串行化的状态处理,比如:

设备状态机;
显示状态变更;
权限状态同步;
某个模块内部必须按顺序处理的事件。

它不适合承载大量互不相关的耗时任务。

如果任务彼此独立、可以并发、主要是计算或 I/O,应该考虑:

Executor;
ExecutorService;
线程池;
协程;
专门的异步框架。

HandlerThread 的官方注释也建议:如果不是必须和 Handler API 对接,优先考虑 ExecutorExecutorService 或 Kotlin coroutine。

所以判断一个 HandlerThread 是否合适,要问:

这些任务是否必须在同一条线程按顺序执行?
这条线程是否允许被某个任务长时间占住?
后续消息被排队等待时,系统表现是否可接受?

五、Binder 线程是 Handler 线程吗?

不是。

Binder 线程和 Handler 线程解决的是两类问题。

Binder 线程:
    执行服务端 Binder 入口代码。
​
Handler 线程:
    串行处理某个模块的内部状态。

典型链路是:

App 进程
  -> Binder transact
  -> Binder 驱动把事务交给 system_server 的 Binder 池线程
  -> Binder.execTransact() / Binder.onTransact()
  -> AIDL Stub 分发到具体服务方法
  -> 服务端入口做参数校验、权限检查、身份处理
  -> 入口代码选择:
       直接在 Binder 线程完成处理;
       post 到 Handler 后返回;
       post 到 Handler 后同步等待结果;
       或继续同步调用其他 Binder 服务。

源码入口:

Handler 不是 Binder 调用的必经下一层。只有当服务端入口选择把后续工作转给 Handler 时,才需要继续区分两个关键分叉:

服务内部异步投递模式:
    Binder 线程把任务投递到 Handler 后很快返回;
    App 通过后续回调、状态变化、广播或下一次查询看到结果。
​
服务内部同步等待模式:
    Binder 线程把任务投递到 Handler 后,还要等待 Handler 线程处理结果;
    如果 Handler 线程积压或阻塞,Binder 线程也会被拖住;
    App 看到的就是同步 Binder 调用变慢。

这里的“异步投递 / 同步等待”描述的是服务端入口是否等待 Handler 结果,不等同于 AIDL oneway 与普通同步 Binder 事务的区分。

这个分叉很重要。看到“服务内部 post 到 Handler”,不能立刻认为 Binder 线程已经完全脱身。要继续看它是投递后返回,还是投递后同步等待结果。

为什么不在 Binder 线程里把所有业务做完?

因为 Binder 线程池是共享资源。如果一个 Binder 线程长时间处理业务逻辑、等待锁、做 I/O 或同步调用别的服务,就会拖住调用方,也会消耗 system_server 的 Binder 线程池容量。

很多系统服务会把真正的状态变更投递到自己的 Handler 线程,是为了获得几个好处:

串行化:
    同一个模块的状态变更按顺序处理,减少锁复杂度。
​
服务内部异步投递时解耦调用方:
    Binder 线程在投递被接受后尽快返回,耗时处理留给目标 Handler 线程。
​
隔离职责:
    Binder 线程执行 IPC 入口,Handler 线程处理需要串行化的模块状态。
​
便于排障:
    可以具体定位到某条 Looper 线程,而不是笼统说 system_server 慢。

服务内部同步等待模式只能保留“业务状态由目标线程串行处理”这个好处,不能说已经解耦调用方。Binder 线程仍在等待结果,目标 Handler 的排队、锁等待和慢回调会沿同步调用链传播给 App。

但这不代表“post 到 Handler 就一定没问题”。如果 Handler 线程自己积压或阻塞,问题只是换了位置:

异步投递模式下,Binder 入口很快把消息投出并返回;
但 Handler 所在线程一直没处理;
用户看到的效果仍然迟迟不发生。

这就是为什么后续排障必须同时看 Binder 线程和目标 Handler 线程。

六、system_server 为什么不只有一条主线程?

system_server 是进程,不是一条线程。

它里面有主线程,也有 Binder 线程池,还有多条专用 Handler 线程。

system_server 主线程也会准备 MainLooper:

SystemServer.run()
  -> Looper.prepareMainLooper()
  -> startBootstrapServices()
  -> startCoreServices()
  -> startOtherServices()
  -> Looper.loop()

源码入口:

但很多系统任务不会全部压在主线程上。Framework 里有多条专用线程,常见的包括:

线程本质常见职责
system_server mainsystem_server 主 Looper服务启动、部分系统服务主线任务
android.uisystem_server 内部 UI 相关 ServiceThread需要避开系统主线程的短时 UI 相关工作
android.display显示相关 ServiceThreadDisplayManager、WindowManager、InputManager 等显示/窗口/输入相关任务
android.anim动画相关 ServiceThread窗口动画、和动画时序相关任务
android.ioI/O 相关 ServiceThread允许短暂阻塞的系统 I/O 任务
android.perm权限相关 ServiceThreadpermission、appops 等同步状态
Binder 线程Binder 线程池工作线程执行服务端 Binder 入口;入口可以直接处理或选择转交任务

这里的 android.uisystem_server 进程内部的 UiThread,不是 com.android.systemui 应用进程的 main 线程。两者进程、Looper 和职责都不同,排障时不能混为一谈。

源码入口:

这些线程名字不是装饰。它们决定系统服务把某些任务投到哪里串行处理。

例如窗口、显示、输入相关问题,不能只盯着 App 主线程或 SurfaceFlinger。很多状态变更会先进入 system_server,再被投到 android.display 或相关 Handler 线程处理。

七、排障时为什么不能只说 system_server 慢?

因为 system_server 只是进程名。

你至少要继续问:

是 system_server main 慢?
是 android.ui 慢?
是 android.display 慢?
是 android.io 慢?
是 Binder 线程慢?
还是某个服务自己创建的 HandlerThread 慢?

每个答案对应的排查方向不一样。

如果是 Binder 线程慢:

看跨进程调用、调用方等待、Binder 线程池是否耗尽、同步调用链是否过长。

如果是 android.display 慢:

看窗口、显示、输入相关回调是否耗时,是否有锁等待、同步 Binder、native/HAL 阻塞。

如果是 android.io 慢:

I/O 是否超出预期,是否把本应异步或可取消的工作塞进了串行 Looper。

如果是某个 HandlerThread 慢:

看这个线程是否被当成线程池滥用,是否有多个互不相关的耗时任务排队。

所以可定位的说法应该像这样:

目标消息投递到了 system_server 的 android.display Handler;
android.display 当前正在执行前一个窗口回调;
后续显示状态更新在该 Looper 队列中等待。

而不是:

system_server 卡了。

八、看到一个 Handler,应该先问哪三件事?

不要先问“它是不是主线程”。

先问三件事:

1. 这个 Handler 持有哪个 Looper?
​
2. 这个 Looper 与哪条 Thread 绑定?
​
3. 这条 Thread 在系统设计里负责什么任务?

这三问能把很多概念混淆拆开。

例子一:

Handler handler = new Handler(Looper.getMainLooper());

答案:

持有 MainLooper 引用;
MainLooper 与主线程绑定;
这条线程负责 UI 和应用主线回调。

例子二:

Handler handler = new Handler(DisplayThread.get().getLooper());

答案:

持有 DisplayThread 的 Looper 引用;
这个 Looper 与 android.display 线程绑定;
这条线程负责显示、窗口、输入相关系统任务。

例子三:

Handler handler = new Handler(handlerThread.getLooper());

答案:

持有某个自建 HandlerThread 的 Looper 引用;
这个 Looper 与这条自建线程绑定;
职责取决于创建它的模块。

这才是 Handler 排障的入口。

九、核心区分表

把这一篇压缩成一张表:

线程类型本质常见误解准确说法
主线程Thread + MainLooperHandler 就是主线程主线程天然有 MainLooper,Handler 是持有它的投递与分发入口
普通线程Thread默认能用无参 Handler 绑定自己默认没有自己的 Looper,但仍可显式绑定其他线程的 Looper
HandlerThreadThread + Looper它是 Handler 或线程池它是带 Looper 的线程,同一时间仍然只 dispatch 一个消息
Binder 线程Binder 线程池工作线程它只是接收或转发调用它会在当前线程执行服务端 Binder 入口;入口可直接处理,也可选择转交 Handler
system_server mainsystem_server 主 Loopersystem_server 只有这一条线system_server 还有 Binder 线程和多条专用 ServiceThread
android.uisystem_server 内部 UI Handler 线程它就是 SystemUI 进程主线程它是 system_server 的 UiThread,与 com.android.systemui 的 main 线程不同
android.display显示/窗口/输入相关 Handler 线程display 问题都在 SurfaceFlingerFramework 显示状态也可能在 system_server 线程
android.io系统 I/O Handler 线程后台线程可以无限阻塞它仍是串行 Looper,阻塞会造成后续消息积压

十、这一篇的结论

Handler 排障首先是线程定位。

看到一个 Handler,不要先说“它是主线程”或“它是子线程”。应该先问:

这个 Handler 持有哪个 Looper?
这个 Looper 与哪条 Thread 绑定?
这条 Thread 在系统设计里负责什么任务?

只有线程定位清楚,后面讨论“消息为什么没执行”“是不是 slow delivery”“是不是 slow dispatch”才有意义。

三篇到这里形成了一条链:

任务怎么走:
    Handler.post -> Message -> enqueue -> next -> dispatch。
​
队列怎么等:
    when、同步屏障、nativePollOnce、nativeWake、quit。
​
线程怎么分:
    主线程、HandlerThread、Binder 线程、system_server 专用 Looper。

下一篇进入实战:

Handler 卡顿怎么定位?从 Slow delivery 到 Slow dispatch。

延伸阅读