Android 中如何添加一个系统服务
一、这道题主要考察什么
"如何添加一个系统服务"主要考察以下几个方面:
-
是否理解系统服务的使用方式
应用通常通过
Context.getSystemService()获取系统服务。 -
是否理解系统服务的发现机制
服务端需要将自己的 Binder 对象注册到
ServiceManager,客户端才能根据服务名称找到它。 -
是否理解 Binder 跨进程通信
应用进程获取到系统服务的 Binder 代理对象后,通过 Binder IPC 调用服务端。
-
是否理解系统服务的启动位置
系统服务可以运行在
system_server进程中,也可以运行在单独的 Native 进程中。
可以将核心概括为三个关键词:
方便使用、注册开放、Binder IPC
二、系统服务的整体调用链
应用获取和调用系统服务的大致流程是:
text
应用程序
↓ Context.getSystemService(name)
SystemServiceRegistry
↓ 根据 name 查找 ServiceFetcher
创建对应的 Manager 对象
↓
ServiceManager.getService(name)
↓
获得服务端 Binder 代理对象
↓
AIDL 接口代理 Stub.Proxy
↓ Binder IPC
系统服务端
例如应用获取电源服务:
java
PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
底层大致经历:
text
Context.getSystemService(POWER_SERVICE)
↓
SystemServiceRegistry
↓
PowerManager 对应的 ServiceFetcher
↓
ServiceManager.getService("power")
↓
获得 IPowerManager 的 Binder 代理
↓
封装成 PowerManager 返回给应用
三、为什么还要封装一个 Manager 类
客户端从 ServiceManager 中取得的,本质上是系统服务的 Binder 接口代理,例如:
text
IPowerManager
但 Android 通常不会直接把 AIDL 接口暴露给普通应用,而是再封装一层:
text
PowerManager
调用关系为:
text
应用
↓
PowerManager
↓
IPowerManager.Stub.Proxy
↓ Binder IPC
PowerManagerService
PowerManager 相当于客户端代理或者业务封装层,主要作用是:
- 隐藏 Binder 和 AIDL 实现细节;
- 统一处理
RemoteException; - 对外提供更容易使用的业务 API;
- 对参数、权限和兼容性进行额外处理。
所以,添加一个完整的系统服务时,不能只考虑服务端,还应该考虑应用如何方便地使用该服务。
四、ServiceManager 的作用
ServiceManager 可以理解为 Binder 系统中的服务注册中心。
系统服务启动后,需要把自己的 Binder 实体注册到 ServiceManager:
java
ServiceManager.addService("my_service", binder);
客户端使用时,根据服务名称查询:
java
IBinder binder = ServiceManager.getService("my_service");
整体关系为:
text
注册 Binder
系统服务 ----------------------> ServiceManager
↑
│ 根据名称查询
│
客户端 -----------------------------+
客户端并不需要提前知道服务对象所在的进程,只需要知道服务名称。
查询到 Binder 对象后,客户端便可以通过 Binder IPC 调用服务端。
五、如何添加一个系统服务
添加系统服务需要同时处理服务端和客户端。
1. 定义跨进程接口
通常使用 AIDL 定义服务接口:
aidl
interface IMyService {
int calculate(int value);
}
编译后会生成:
text
IMyService
IMyService.Stub
IMyService.Stub.Proxy
其中:
Stub用于服务端接收 Binder 请求;Proxy用于客户端发起 Binder 请求。
2. 实现系统服务
服务端实现 AIDL 生成的 Stub:
java
public class MySystemService extends IMyService.Stub {
@Override
public int calculate(int value) {
return value * 2;
}
}
此时 MySystemService 本身就是一个 Binder 实体对象。
实际系统服务通常还会包含:
- 资源初始化;
- 系统状态监听;
- 权限检查;
- Binder 接口实现;
- 生命周期管理。
3. 选择系统服务运行的位置
系统服务主要有两种运行方式。
方式一:运行在 system_server 中
大多数 Java 系统服务运行在 system_server 进程,例如:
text
ActivityManagerService
PackageManagerService
PowerManagerService
WindowManagerService
这类服务通常在 SystemServer 启动过程中创建。
大致过程:
java
MySystemService service = new MySystemService();
ServiceManager.addService("my_service", service);
如果运行在 system_server 中,不需要自己初始化 Binder 驱动和 Binder 线程池,因为 system_server 已经完成这些工作。
方式二:运行在独立进程中
部分 Native 系统服务运行在独立进程中,例如:
text
SurfaceFlinger
media 服务
ServiceManager
独立进程的服务通常需要:
- 在
init.rc中配置进程启动方式; - 提供进程入口函数;
- 初始化 Binder 通信环境;
- 创建系统服务 Binder 对象;
- 注册到
ServiceManager; - 启动 Binder 线程池并进入循环。
大致流程:
text
init 读取 rc 配置
↓
启动系统服务进程
↓
打开 Binder 驱动
↓
进行 mmap 内存映射
↓
启动 Binder 线程池
↓
创建服务对象
↓
ServiceManager.addService()
↓
进入 Binder 线程循环
4. 注册到 ServiceManager
无论系统服务运行在 system_server,还是运行在独立进程中,都需要向 ServiceManager 注册。
java
ServiceManager.addService("my_service", myService);
注册时传入:
- 服务名称;
- 服务的 Binder 实体对象。
注册之后,其他进程才能通过名称查询:
java
IBinder binder = ServiceManager.getService("my_service");
因此,注册的本质是:
把服务名称和 Binder 对象之间的映射关系保存到 ServiceManager 中。
5. 编写客户端 Manager 类
为了避免应用直接操作 AIDL 接口,通常再封装一个 Manager 类:
java
public class MyManager {
private final IMyService service;
public MyManager(IMyService service) {
this.service = service;
}
public int calculate(int value) {
try {
return service.calculate(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
调用链为:
text
MyManager
↓
IMyService.Proxy
↓ Binder IPC
MySystemService
6. 注册到 SystemServiceRegistry
为了让应用能够像使用其他系统服务一样调用:
java
context.getSystemService("my_service");
还需要在客户端框架层注册对应的 ServiceFetcher。
概念代码如下:
java
registerService(
"my_service",
MyManager.class,
new CachedServiceFetcher<MyManager>() {
@Override
public MyManager createService(ContextImpl context) {
IBinder binder =
ServiceManager.getService("my_service");
IMyService service =
IMyService.Stub.asInterface(binder);
return new MyManager(service);
}
}
);
之后应用便可以通过:
java
MyManager manager =
(MyManager) context.getSystemService("my_service");
获取服务。
这里完成了三层转换:
text
服务名称
↓
IBinder
↓
IMyService
↓
MyManager
六、完整添加流程
完整流程可以总结为:
text
1. 使用 AIDL 定义 Binder 接口
↓
2. 服务端继承 Stub,实现业务逻辑
↓
3. 决定服务运行在 system_server 还是独立进程
↓
4. 在合适的系统启动阶段创建服务
↓
5. 使用 ServiceManager.addService() 注册 Binder
↓
6. 客户端实现对应的 Manager 封装类
↓
7. 在 SystemServiceRegistry 中注册 ServiceFetcher
↓
8. 应用通过 Context.getSystemService() 获取服务
↓
9. 通过 Binder IPC 调用服务端
七、除了代码,还需要处理什么
真正将一个系统服务加入 Android 系统,通常还需要处理:
- Android.bp 等编译配置;
- AIDL 接口编译配置;
- SELinux 权限和策略;
service_contexts服务名称映射;- Binder 调用权限检查;
- 系统 API 的公开或隐藏配置;
- 独立进程对应的
init.rc; - 系统启动顺序和依赖关系;
- 服务异常和 Binder 死亡处理。
因此,添加系统服务不仅是写一个 Binder 类,还涉及:
Framework、Binder、SystemServer、ServiceManager、编译系统以及 SELinux。
八、逻辑梳理
Android 中添加一个系统服务,需要同时考虑服务端注册、Binder 通信和客户端使用方式。
首先,可以通过 AIDL 定义跨进程接口,然后在服务端继承 AIDL 生成的 Stub,实现具体业务逻辑。
接着要确定服务运行的位置。如果服务运行在 system_server 中,可以在 SystemServer 启动系统服务的阶段创建它;如果运行在独立进程中,还需要通过 init.rc 启动进程,并初始化 Binder 驱动、内存映射和 Binder 线程池。
服务创建完成后,需要调用 ServiceManager.addService(),将服务名称和 Binder 实体注册到 ServiceManager。这样客户端才能通过 ServiceManager.getService() 根据名称获得 Binder 代理对象,并通过 Binder IPC 调用服务。
为了让应用使用起来更方便,通常还会编写一个 Manager 类,对 AIDL 接口进行封装,并在 SystemServiceRegistry 中注册对应的 ServiceFetcher。这样应用就可以像使用其他系统服务一样,通过 Context.getSystemService() 获取该服务。
此外,真正加入 Android 系统还需要处理编译配置、SELinux 权限、service_contexts、接口权限检查和系统启动顺序等问题。
九、十句话以内的面试回答
添加 Android 系统服务,首先要使用 AIDL 定义跨进程接口,并在服务端继承 Stub 实现业务逻辑。
然后确定服务运行在 system_server 进程,还是运行在独立进程中。
如果运行在 system_server 中,通常在 SystemServer 启动系统服务的阶段创建。
如果运行在独立进程中,还需要配置 init.rc,并初始化 Binder 线程池。
服务创建后,通过 ServiceManager.addService() 注册服务名称和 Binder 实体。
客户端通过 ServiceManager.getService() 根据名称取得 Binder 代理。
之后通过 AIDL 生成的 Proxy 发起 Binder IPC 调用。
为了方便应用使用,还应封装一个对应的 Manager 类。
再在 SystemServiceRegistry 中注册,使应用可以通过 Context.getSystemService() 获取。
最后还需要配置编译规则、SELinux、service_contexts 和调用权限。
十、最核心的一句话
添加系统服务的本质,是创建一个 Binder 服务,将它注册到 ServiceManager,并在客户端提供通过 Context.getSystemService() 获取和调用该服务的封装。
第一是添加时机。如果服务运行在 system_server 中,就在 SystemServer 启动系统服务的阶段创建;如果运行在独立进程中,就需要配置 init.rc,并提供进程入口函数。
第二是服务端工作。服务端需要支持 Binder 通信、完成自身初始化,并通过 ServiceManager.addService() 注册自己的 Binder 对象。如果运行在 system_server 中,Binder 环境已经由系统初始化;如果运行在独立进程中,则需要自己启动 Binder 线程池。
第三是应用端工作。为了让应用能够像使用其他系统服务一样,通过 Context.getSystemService() 获取服务,需要在 SystemServiceRegistry 中注册对应的 ServiceFetcher,并封装客户端 Manager 类。