3、SystemServer的线程模型:SystemServer中的关键线程(UI线程、Binder线程池、Watchdog线程)及其职责
SystemServer启动之后,到底是怎么跑起来的?
很多人以为它就是个普通Java进程,跑个main方法就完事了。其实不是。SystemServer内部有一套非常精巧的线程模型。我当年第一次看这块源码时,说实话有点懵——怎么这么多线程?它们各自在干嘛?
今天我们就来拆开看看。核心就三个:UI线程、Binder线程池、Watchdog线程。搞懂它们,你就掌握了SystemServer的“骨架”。
3.1 UI线程:SystemServer的“主心骨”
先说说UI线程。嗯,这个名字容易让人误会——SystemServer又不是App,哪来的UI?
其实这里的“UI线程”,指的是SystemServer的主线程。它负责初始化所有核心服务,也处理一些非Binder的异步消息。说白了,它就是SystemServer的“大脑”。
我们来看一段关键代码。在SystemServer.java的run()方法里:
private void run() {
// 设置主线程的Looper
Looper.prepareMainLooper();
Looper.getMainLooper().setSlowLogThresholdMs(
SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
// 创建系统上下文
createSystemContext();
// 创建系统服务管理器
mSystemServiceManager = new SystemServiceManager(mSystemContext);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// ... 加载各种服务
startBootstrapServices();
startCoreServices();
startOtherServices();
// 进入消息循环
Looper.loop();
}
看到没?Looper.prepareMainLooper()和Looper.loop(),这就是典型的Android消息循环模式。UI线程就在这里一直转,处理各种内部消息。
重点来了:UI线程不能做耗时操作。一旦阻塞,整个SystemServer就卡住了。我见过一个线上问题,就是因为某个服务的onStart()里做了网络请求,结果ANR了整整30秒。嗯,血的教训。
避坑指南:我曾经在调试一个系统服务时,发现UI线程偶尔会卡顿几百毫秒。查了半天,原来是有个日志打印太频繁,占用了I/O时间。后来我加了个异步日志队列,问题就解决了。所以,UI线程里千万别干重活。
3.2 Binder线程池:SystemServer的“千手观音”
UI线程再强,也扛不住所有App的并发请求。这时候就需要Binder线程池登场了。
Binder是Android的IPC机制。每个App通过Binder调用系统服务时,请求会落到SystemServer的Binder线程池里。默认情况下,这个线程池有16个线程。你可以通过persist.sys.binder_max_threads属性调整。
我们来看看Binder线程池是怎么工作的。在ProcessState.cpp中:
// 默认最大Binder线程数
#define DEFAULT_MAX_BINDER_THREADS 15
// 启动Binder线程池
void ProcessState::startThreadPool() {
AutoMutex _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
每个Binder线程都在等待请求。一旦有IPC调用进来,线程池就会分配一个空闲线程去处理。处理完就回到池子里,继续等下一个。
这里有个细节:Binder线程是同步调用的。也就是说,一个线程处理一个请求时,如果这个请求又去调别的Binder服务,那这个线程就会一直等着。如果嵌套调用多了,线程池很容易被耗尽。
注意:Binder线程池耗尽是个经典问题。我曾经遇到过一个场景:某个服务在Binder线程里做了数据库查询,查询又去调ContentProvider,ContentProvider又回调了另一个服务。结果16个线程全卡住了,系统直接无响应。解决方案?要么加线程池大小,要么把耗时操作异步化。
另外,Binder线程和UI线程之间怎么通信?答案是Handler。Binder线程处理完请求后,如果需要更新UI线程的状态,就通过Handler发个消息。这样既保证了并发,又避免了线程安全问题。
3.3 Watchdog线程:SystemServer的“守护者”
UI线程和Binder线程池再稳定,也架不住死锁或死循环。这时候就需要Watchdog了。
Watchdog是一个独立的监控线程。它每隔30秒检查一次关键线程的状态。如果某个线程超过60秒没有响应,Watchdog就会认为系统卡死了,然后执行一系列操作——包括打印堆栈、生成日志,甚至直接重启SystemServer。
核心代码在Watchdog.java中:
public class Watchdog extends Thread {
// 监控间隔
static final long DEFAULT_TIMEOUT = 60 * 1000;
static final long CHECK_INTERVAL = 30 * 1000;
@Override
public void run() {
boolean waitedHalf = false;
while (true) {
// 等待30秒
mCompleted = SystemClock.uptimeMillis();
long timeout = mCompleted - mOpenStart;
if (timeout > DEFAULT_TIMEOUT) {
// 超时了!执行恢复逻辑
handleCheckerDeadlocked();
// 或者直接重启
ResueKiller.killProcess("SystemServer", ...);
}
// 每30秒检查一次
SystemClock.sleep(CHECK_INTERVAL);
}
}
}
Watchdog监控哪些线程?主要是UI线程、Binder线程池的主线程,以及一些关键服务(比如AMS、WMS)的Handler线程。它会向这些线程发送一个“探针”消息,如果60秒内没有收到回复,就判定为超时。
个人经验:我遇到过Watchdog被误触发的情况。有一次是因为系统负载太高,UI线程处理消息慢了,刚好超过60秒。Watchdog二话不说就杀了SystemServer。后来我们调整了超时阈值,并且加了负载感知逻辑——如果CPU本来就忙,就适当放宽检查。嗯,这招挺管用。
3.4 三个线程如何协同工作?
讲完三个线程,我们来总结一下它们的关系。我画了个简单的逻辑图,你感受一下:
| 线程 | 职责 | 典型问题 | 我的建议 |
|---|---|---|---|
| UI线程 | 初始化服务、处理内部消息 | 耗时操作导致ANR | 异步化,别在主线程做I/O |
| Binder线程池 | 处理App的IPC请求 | 线程池耗尽、死锁 | 避免嵌套调用,必要时扩容 |
| Watchdog线程 | 监控关键线程是否卡死 | 误触发、恢复逻辑不完善 | 加负载感知,合理设阈值 |
说白了,这三个线程各司其职:UI线程是大脑,Binder线程池是手脚,Watchdog是保安。大脑负责决策,手脚负责干活,保安负责盯着别出乱子。
你想想看,如果没有Watchdog,UI线程一旦卡死,整个系统就无声无息地挂了。用户只会觉得手机死机了,但根本不知道发生了什么。有了Watchdog,至少能留下堆栈日志,甚至自动恢复。这就是设计的精妙之处。
一个小技巧:我在调试系统服务时,经常手动触发Watchdog来检查线程状态。方法是向Watchdog的Handler发一个延迟消息,模拟超时。这样就能在不重启手机的情况下,验证恢复逻辑是否正常。嗯,这招对开发效率提升很大。
最后说一句:SystemServer的线程模型,其实反映了Android系统设计的一个核心理念——分工明确,各司其职。UI线程专注内部逻辑,Binder线程池处理外部请求,Watchdog兜底保命。三者配合,才撑起了整个Android系统的稳定运行。
下一章,我们会深入分析ActivityManagerService的启动流程。到时候你会看到,AMS是如何在UI线程中初始化,又如何通过Binder线程池与App交互的。敬请期待。