【全栈第2课】getSystemService 全链路 + 写一个真集成的 Java 系统服务
Android 全栈工程师进阶教程 第02课。getSystemService 全链路 + 写一个真集成的 Java 系统服务。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。
> 本文是《Android 全栈工程师进阶教程》系列第 02 课。完整 26 课见 GitHub 仓库。
【全栈第2课】getSystemService 全链路 + 写一个真集成的 Java 系统服务
0. 这节课你将做出什么
在 AOSP framework 里真正加一个系统服务 HelloManager,做到三件事:
- 开机时
system_server自动把它拉起(不用手动 adb 起,这是和 L1 最大的区别) - App 里一行
getSystemService(Context.HELLO_SERVICE)就能拿到它 - 调用时服务端能拿到调用方 uid
学完你能回答:你天天写的 getSystemService("xxx"),从 App 进程一路穿到 system_server,中间经过了哪些环节?一个系统服务是怎么"长"进系统里的?
1. 背景:L1 的"游离 binary"问题
L1 我们写的 helloserver 能编进镜像,但有个硬伤:它不会自动跑,得手动 adb shell helloserver &。这叫"游离模块"。
真正的系统服务(IMS/AMS)从来不用手动起——开机就在了。一个服务要真正"长进系统",得满足三件事:
| 条件 | L1 做到了吗 | 怎么做到 |
|---|---|---|
| ① 编进镜像 | ✅ | Android.bp / 放进 framework 源码集 |
| ② 开机自动启动 | ❌(要手动起) | Java 服务靠 SystemServer 注册(本课) |
| ③ 能被调用 | ✅(手动起后) | 注册到 servicemanager(L1 已会) |
L1 缺的就是 ②。本课补上,让服务像 IMS 一样开机自动起。
2. 全局图:getSystemService 从 App 到 system_server
【App 进程】 【system_server 进程】
context.getSystemService("hello")
│
▼ ContextImpl.getSystemService(name) ContextImpl.java:2445
│ 查 SystemServiceRegistry 注册表(一个 name→Fetcher 的 map)
▼ CachedServiceFetcher.createService(ctx) SystemServiceRegistry.java
│ ServiceManager.getServiceOrThrow("hello") ──取货──▶ 拿到 BinderProxy
│ new HelloManager(IHelloManager.Stub.asInterface(b))
▼ 返回 HelloManager(里面包着 BinderProxy)
│
▼ manager.sayHello("x")
│ mService.sayHello("x") ── Binder transact ═══════▶ HelloManagerService.sayHello() ← 真正实现
│ Binder.getCallingUid() 取调用方
◀══════════════════════════════════════════════════════ 返回
对照 IMS 的真实集成点(我们照抄的模板):
| 集成动作 | IMS 真实位置 | 本课对应 |
|---|---|---|
| 开机启动 | SystemServer.java:2022 startService(IMS.Lifecycle.class) | 给 SystemServer 加一行 |
| 取货注册 | SystemServiceRegistry.java:657 registerService(INPUT_SERVICE, InputManager.class,...) | 注册一个 Manager |
| 上架 | IMS.Lifecycle.onStart() → publishBinderService("input") | 我们的 Lifecycle.onStart |
3. 某真实机型 真实源码印证
SystemServer 怎么启动 IMS(我们照抄):
SystemServer.java:2022:
inputManager = mSystemServiceManager.startService(
InputManagerService.Lifecycle.class).getService();
startService(Lifecycle.class) → 内部 new Lifecycle(context) → 调它的 onStart()(SystemServiceManager.java:291)。
App 取货的注册块(照抄 SERIAL/INPUT):
SystemServiceRegistry.java:657:
registerService(Context.INPUT_SERVICE, InputManager.class,
new CachedServiceFetcher<InputManager>() {
public InputManager createService(ContextImpl ctx) {
return new InputManager(ctx.getOuterContext());
}});
这就是把 "input" 这个名字,绑到"怎么造一个 InputManager"的工厂。App getSystemService("input") 就走这个工厂。
最简模板 SerialService(services/core/java/com/android/server/SerialService.java):
class SerialService extends ISerialManager.Stub { ... } // 实现继承 Stub
public static class Lifecycle extends SystemService { // 内部 Lifecycle 负责启动注册
public void onStart() { publishBinderService(SERIAL_SERVICE, mService); }
}
我们的 HelloManagerService 就是照这个模板 1:1 写的。
4. 前置准备
- 依赖 L1 的知识:Binder 三角色、AIDL 生成 Stub/Proxy。
- 环境:能改 AOSP
frameworks/base源码(某真实机型 树)。 - 要改/加的文件(共 6 个):
- 新增 3 个:
IHelloManager.aidl、HelloManager.java(App 侧)、HelloManagerService.java(服务端) - 改 3 个 AOSP 核心:
Context.java、SystemServiceRegistry.java、SystemServer.java
- 新增 3 个:
- ⚠️ 改 AOSP 核心文件属"大改动",改前先
git status确认干净、建分支。
5. 动手:一步步做
步骤 1:新增 AIDL 接口 core/java/android/os/IHelloManager.aidl
package android.os;
/** @hide */ // @hide:系统内部接口,不进公开 SDK
interface IHelloManager {
String sayHello(String name);
int add(int a, int b);
}
> 关键:放在 core/java/android/os/ 下,framework 编译会自动收这个 .aidl(像 IClipboard.aidl 一样),不用改 Android.bp。这是少动一个核心文件的技巧。
> @hide 表示这是系统内部接口——三方 App SDK 里看不到,但系统/framework 能用。
步骤 2:新增 App 侧 Manager core/java/android/os/HelloManager.java
这是 App 调 getSystemService 拿到的对象。它包着 BinderProxy,把方法调用转成跨进程 transact。
package android.os;
import android.annotation.SystemService;
import android.content.Context;
@SystemService(Context.HELLO_SERVICE) // 声明这是 HELLO_SERVICE 对应的 Manager
public class HelloManager {
private final IHelloManager mService; // ← 这就是 BinderProxy(指向 system_server)
public HelloManager(IHelloManager service) {
mService = service;
}
public String sayHello(String name) {
try {
return mService.sayHello(name); // ← 跨进程!这一步走 Binder transact 到 system_server
} catch (RemoteException e) {
// RemoteException 是 checked 异常,系统服务调用约定:转成 RuntimeException 抛出
throw e.rethrowFromSystemServer();
}
}
public int add(int a, int b) {
try { return mService.add(a, b); }
catch (RemoteException e) { throw e.rethrowFromSystemServer(); }
}
}
> 为什么要 try-catch RemoteException? 跨进程调用对端可能挂掉(L1 问题2 的 DeadObjectException),AIDL 生成的方法签名带 throws RemoteException。系统服务的惯例是用 rethrowFromSystemServer() 转成非受检异常,让 App 调用方不用每次都 try-catch。
步骤 3:新增服务端实现 services/core/java/com/android/server/HelloManagerService.java
这是真正跑在 system_server 里干活的类。照搬 SerialService 模式:实现 extends Stub + 内部 Lifecycle。
package com.android.server;
import android.content.Context;
import android.os.Binder;
import android.os.IHelloManager;
import android.util.Slog;
public class HelloManagerService extends IHelloManager.Stub { // ← 继承 AIDL 生成的 Stub
private static final String TAG = "HelloManagerService";
private final Context mContext;
public HelloManagerService(Context context) { mContext = context; }
@Override
public String sayHello(String name) {
final int uid = Binder.getCallingUid(); // ← 内核盖的调用方身份(L1 讲的)
final int pid = Binder.getCallingPid();
Slog.i(TAG, "sayHello: name=" + name + ", 调用方 uid=" + uid + " pid=" + pid);
return "Hello, " + name + " (from system_server, caller uid=" + uid + ")";
}
@Override
public int add(int a, int b) { return a + b; }
// Lifecycle:SystemServer 通过它启动并注册服务(照搬 SerialService.Lifecycle)
public static class Lifecycle extends SystemService {
private HelloManagerService mService;
public Lifecycle(Context context) { super(context); }
@Override
public void onStart() {
mService = new HelloManagerService(getContext());
// 上架到 servicemanager,名字 Context.HELLO_SERVICE = "hello"
publishBinderService(Context.HELLO_SERVICE, mService);
}
}
}
> 为什么要内部 Lifecycle 类? SystemServer 只认 SystemService 子类(它调 startService(SomeClass) 时要求传 SystemService 的子类)。但我们的服务实现要 extends IHelloManager.Stub(不能同时 extends 两个类)。所以拆成两个:HelloManagerService 是 Binder 实现,内部 Lifecycle 是 SystemService 适配器,负责"启动时把实现造出来并上架"。这是 AOSP 的标准拆法。
步骤 4:改 Context.java 加服务名常量
/** @hide */
public static final String HELLO_SERVICE = "hello"; // 加在 SERIAL_SERVICE 常量附近
步骤 5:改 SystemServiceRegistry.java 注册 Manager(App 取货桥)
照抄 INPUT_SERVICE 那块,放进 registerServices() 里:
registerService(Context.HELLO_SERVICE, android.os.HelloManager.class,
new CachedServiceFetcher<android.os.HelloManager>() {
@Override
public android.os.HelloManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.HELLO_SERVICE); // 取货
return new android.os.HelloManager(
android.os.IHelloManager.Stub.asInterface(b)); // BinderProxy 转接口 → 包成 Manager
}});
> CachedServiceFetcher 表示这个 Manager 实例会按 Context 缓存(同一个 Context 多次 getSystemService 拿到同一个对象)。
步骤 6:改 SystemServer.java 开机启动它
在 StartInputManagerService 旁边加:
t.traceBegin("StartHelloManagerService");
mSystemServiceManager.startService(
com.android.server.HelloManagerService.Lifecycle.class); // ← 开机自动起
t.traceEnd();
6. 看底层:getSystemService 在 App 端真实走的路
ContextImpl.getSystemService(name) 实际是去 SystemServiceRegistry 的一个静态 map 里按 name 查 ServiceFetcher,然后调它的 getService(this):
- 第一次:走
createService()(我们步骤 5 写的工厂)→getServiceOrThrow取 BinderProxy → 包成 HelloManager → 缓存。 - 之后:直接返回缓存。
所以 getSystemService 不是什么魔法,就是一张注册表 + 一个工厂方法 + 缓存。你步骤 5 注册的就是这张表里的一条。
7. 编译 & 运行(验证状态如实)
# AOSP 根 source/lunch 后:
make framework services # 编 framework.jar + services.jar
# 改了 Context.java 这种 SDK 接口,可能要先:
make update-api # 更新 API 签名(否则报 API check 失败)
# 刷机(或 adb sync system)后看效果
adb logcat | grep HelloManagerService
预期开机 logcat(自动启动生效):
HelloManagerService: Lifecycle.onStart: 创建并上架 hello 服务
HelloManagerService: HelloManagerService 构造完成
App 端测试(getSystemService 链路 + 跨进程):
HelloManager hm = (HelloManager) getSystemService(Context.HELLO_SERVICE);
String r = hm.sayHello("zhoubenliang");
// logcat: HelloManagerService: sayHello: name=zhoubenliang, 调用方 uid=10xxx pid=xxxx
// r = "Hello, zhoubenliang (from system_server, caller uid=10xxx)"
> 验证状态(诚实):本课 IHelloManager.aidl 已用 AOSP aidl 工具生成 java 后端通过(RC=0),生成的 Stub 签名与 HelloManagerService/HelloManager 的实现签名核对一致。完整 make framework 未跑(全量编译小时级)。其余 5 处是往现有文件加标准模板代码,风险低;改 Context.java 真编译时可能需 make update-api。
8. 踩坑提醒
make update-api:改了Context.java(SDK 类)加常量,直接make framework会报 API 不一致;先跑make update-api更新签名文件。- 实现不能同时 extends Stub 和 SystemService:Java 单继承,所以拆成
Service(extends Stub)+ 内部Lifecycle(extends SystemService)。 - .aidl 放对目录:必须在
core/java/下才会被 framework 自动收;放别处不编译。 - SELinux:服务能注册还需要 sepolicy 放行(见 L6),否则
addService时 avc denied,服务起不来。 - @SystemApi vs @hide:三方 App 用不了 @hide 接口;只有系统 App / 反射能调。学习够用,产线对外要 @SystemApi。
9. 常见问题分析与定位(系统服务实战)
9.1 常见问题清单
- 开机后服务没起(logcat 没 onStart 日志)
- App
getSystemService("hello")返回 null - 编译报 API check 失败
- 服务起了但 App 调用 crash(SecurityException/avc)
- 服务 onStart 里调别的服务拿到 null
9.2 分析与定位
① 服务没起(无 onStart 日志)
- 原因:SystemServer 那行 startService 没加对/没编进 / Lifecycle 类路径写错 / 服务在 onStart 抛异常被 SystemServer 抓了
- 定位:
adb logcat | grep -iE "HelloManagerService|SystemServer.*Hello|Starting.*Hello"
adb logcat | grep -iE "Failure starting|onStart.*Exception" # onStart 崩了?
- 修复:确认 startService 用的全限定类名对;onStart 里别抛异常。
② getSystemService 返回 null
- 原因:SystemServiceRegistry 没注册 / 注册的 name 和 Context 常量不一致 / 服务没 addService
adb shell service list | grep hello # 服务上架了吗(服务端 publishBinderService 成功?)
adb shell dumpsys hello # 能 dump 吗
- 修复:核对三处的 "hello" 名字一致:Context.HELLO_SERVICE、registerService、publishBinderService。
③ 编译 API check 失败
- 现象:
make报api-stubs/current.txt不一致 - 修复:
make update-api,把新增的 Context 常量更新进 API 签名。
④ App 调用 crash(SecurityException / avc denied)
- 原因:SELinux 没放行 App 访问 hello_service,或服务里做了权限校验拒了
adb logcat | grep -iE "avc.*hello|SecurityException"
- 修复:补 sepolicy(L6/L11);检查服务里 getCallingUid 校验逻辑。
⑤ onStart 里调别的服务拿到 null
- 原因:你的服务启动早于它依赖的服务(L3 启动顺序问题)
- 修复:依赖别的服务的初始化,别放 onStart,放到 onBootPhase 对应阶段(见 L3)。
9.3 定位决策树
系统服务出问题
├─ 没 onStart 日志 → startService 加对没/onStart 崩没(logcat Failure starting)
├─ getSystemService null → service list 有无 → 无=publishBinderService失败;有=三处name不一致/registerService漏
├─ 编译 API check 失败 → make update-api
├─ App 调用 crash → logcat avc/SecurityException → 补 sepolicy(L6)
└─ onStart 调别的服务 null → 启动顺序问题 → 移到 onBootPhase(L3)
10. 小结
- 能编进镜像 ≠ 真集成:真集成 = 编进 + 开机自动起(SystemServer 注册)+ 能被调用。
- getSystemService 链路:Context → SystemServiceRegistry 注册表 → ServiceManager 取 BinderProxy → 包成 Manager。
- Java 系统服务三件套:
实现 extends IXxx.Stub+内部 Lifecycle extends SystemService+SystemServer 一行 startService。 - 和 L1 同一套 Binder:L1 是 native servicemanager,L2 是 Java 层,机制一样。
- 排查口诀:没起查 startService+onStart;null 查 service list+三处 name;编译挂 update-api;调用挂查 sepolicy。
下节预告
L3:SystemServer 启动流程 + Service 三阶段生命周期 —— 为什么服务启动有先后顺序?onBootPhase 各阶段干嘛?为什么有些初始化不能放 onStart?