46. 系统服务:SystemServer 进程如何利用 Binder 提供系统服务?
嗯,这个问题问得很到位。SystemServer 是 Android 系统的“大管家”,几乎所有你叫得上名字的系统服务——ActivityManagerService、WindowManagerService、PackageManagerService——都跑在这个进程里。那它到底是怎么通过 Binder 把这些服务“发布”出去,让其他 App 能调用的呢?
说白了,就是一套“注册-发布-调用”的流程。我当年刚接触这部分源码时,觉得挺绕的。后来自己动手写过一个小型系统服务,才真正搞明白。今天咱们就把这层窗户纸捅破。
SystemServer 的启动入口
SystemServer 进程由 Zygote fork 出来,入口在 SystemServer.main() 方法。它干的第一件事就是启动系统服务。
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
// 1. 准备主线程 Looper
Looper.prepareMainLooper();
// 2. 加载 native 库
System.loadLibrary("android_servers");
// 3. 创建系统上下文
createSystemContext();
// 4. 启动各项服务
startBootstrapServices(); // 引导服务
startCoreServices(); // 核心服务
startOtherServices(); // 其他服务
// 5. 进入消息循环
Looper.loop();
}
你看,它把服务分成了三类:引导服务、核心服务、其他服务。这个分层是有讲究的——有些服务必须在最前面启动,比如 Installer(负责安装 APK),没有它后面啥也干不了。
服务是如何注册到 ServiceManager 的?
每个系统服务启动后,都会调用 ServiceManager.addService() 把自己注册进去。我举个例子,看看 ActivityManagerService 是怎么注册的:
// 在 startBootstrapServices() 中
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class);
// 注册到 ServiceManager
ServiceManager.addService(Context.ACTIVITY_SERVICE, mActivityManagerService);
这里有个细节:ServiceManager.addService() 其实是一个 Binder 调用。它向 servicemanager 进程(一个独立的 native 进程)发送一条 ADD_SERVICE 事务。servicemanager 内部维护了一张哈希表,key 是服务名称(比如 "activity"),value 是 Binder 引用的句柄。
核心流程:
- SystemServer 创建服务对象(如 AMS)
- 调用 ServiceManager.addService(name, binder)
- servicemanager 将 (name, handle) 存入哈希表
- 客户端通过 ServiceManager.getService(name) 获取 Binder 代理
Binder 服务对象的创建方式
系统服务的 Binder 对象创建,通常有两种方式。我个人更推荐第二种,因为它更清晰。
| 方式 | 说明 | 典型例子 |
|---|---|---|
| 继承 Binder 类 | 直接继承 android.os.Binder,实现 onTransact() | 老版本的服务,如 VibratorService |
| 使用 AIDL | 定义 .aidl 接口,自动生成 Stub 类 | 大多数现代服务,如 IActivityManager |
我记得有一次排查一个系统服务 ANR 问题,发现对方用的是继承 Binder 的方式,onTransact() 里直接做了耗时操作。嗯,这就是典型的“图省事”埋下的坑。用 AIDL 的话,默认会在 Binder 线程池里处理,反而能避免这种问题。
客户端如何获取服务?
客户端 App 调用 getSystemService() 时,最终会走到 ServiceManager.getService()。这个过程我画了一张图,你一看就明白:
你看,客户端拿到 Binder 代理后,后续的调用就直接走 Binder 驱动了,不再经过 ServiceManager。这也是 Binder 设计的高明之处——ServiceManager 只负责“引荐”,不参与实际通信。
服务注册的底层细节
咱们再深入一层。ServiceManager.addService() 最终会调用到 BinderInternal.addService(),这是一个 native 方法。它向 Binder 驱动发送 BC_TRANSACTION 命令,code 是 ADD_SERVICE_TRANSACTION。
// frameworks/native/libs/binder/IServiceManager.cpp
virtual status_t addService(const String16& name, const sp<IBinder>& service) {
Parcel data, reply;
data.writeString16(name);
data.writeStrongBinder(service); // 将 Binder 对象扁平化写入
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err;
}
这里有个关键点:writeStrongBinder() 会把 Binder 对象的引用传递给 servicemanager。Binder 驱动会在内核中为这个服务创建一个 binder_node,并分配一个 handle。servicemanager 拿到 handle 后,存到自己的服务列表里。
避坑指南:
我曾经遇到过一个诡异的问题:某个系统服务注册成功了,但客户端调用时总是返回 null。查了半天,发现是服务注册时传的 Binder 对象是一个临时变量,被 GC 回收了。嗯,记住:注册到 ServiceManager 的 Binder 对象必须保持强引用,否则 Binder 驱动会认为服务已死亡,自动清理掉 handle。
系统服务的线程模型
SystemServer 启动后,会创建多个 Binder 线程。默认情况下,Binder 驱动会为每个进程分配 16 个 Binder 线程(上限可配置)。这些线程专门用来处理 incoming 的 Binder 调用。
我建议你记住这个原则:不要在 Binder 线程里做耗时操作。为什么?因为 Binder 线程池是共享的,一个服务卡住了,其他服务的调用也得排队等着。我之前排查过一个 ANR,就是某个系统服务的 onTransact() 里做了数据库查询,把整个 Binder 线程池堵死了。
注意事项:
- 耗时操作请另起线程,或者使用 Handler 抛到工作线程
- SystemServer 的主线程是 Looper 线程,用于处理 AMS 等服务的内部消息
- Binder 调用默认是同步的,客户端会阻塞等待返回
服务死亡通知机制
系统服务也是会挂的。如果 SystemServer 中的某个服务进程崩溃了(虽然这种情况极少),Binder 驱动会检测到并通知所有持有该服务代理的客户端。客户端可以注册 DeathRecipient 来监听:
// 客户端注册死亡通知
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 服务挂了,尝试重连
Log.w(TAG, "服务挂了,尝试重新获取");
// 重新调用 getService()
}
};
binder.linkToDeath(dr, 0);
这个机制在系统服务中很常用。比如 ActivityManagerService 如果挂了(虽然几乎不可能),Launcher 会收到死亡通知,然后尝试重新获取服务引用。
总结一下
SystemServer 利用 Binder 提供系统服务的核心思路,其实就是“注册-发现-调用”三步走。注册阶段把服务对象交给 ServiceManager 托管,发现阶段客户端通过名称查找,调用阶段直接走 Binder 驱动进行跨进程通信。
我个人觉得,理解这个流程的关键在于搞清楚两个角色:ServiceManager 是“黄页”,Binder 驱动是“邮局”。黄页帮你找到人,邮局负责送信。一旦你拿到了对方的地址,后续通信就不需要再查黄页了。
嗯,如果你正在开发自己的系统服务,记住我刚才说的:保持 Binder 对象的强引用,不要在 Binder 线程里做耗时操作,注册死亡通知。这三点做到位,基本不会出大问题。