AI 技术博客
Android全栈13 分钟阅读10155

【全栈第2课】getSystemService 全链路 + 写一个真集成的 Java 系统服务

Android 全栈工程师进阶教程 第02课。getSystemService 全链路 + 写一个真集成的 Java 系统服务。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。

> 本文是《Android 全栈工程师进阶教程》系列第 02 课。完整 26 课见 GitHub 仓库

【全栈第2课】getSystemService 全链路 + 写一个真集成的 Java 系统服务


0. 这节课你将做出什么

在 AOSP framework 里真正加一个系统服务 HelloManager,做到三件事:

  1. 开机时 system_server 自动把它拉起(不用手动 adb 起,这是和 L1 最大的区别)
  2. App 里一行 getSystemService(Context.HELLO_SERVICE) 就能拿到它
  3. 调用时服务端能拿到调用方 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.aidlHelloManager.java(App 侧)、HelloManagerService.java(服务端)
    • 改 3 个 AOSP 核心:Context.javaSystemServiceRegistry.javaSystemServer.java
  • ⚠️ 改 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. 踩坑提醒

  1. make update-api:改了 Context.java(SDK 类)加常量,直接 make framework 会报 API 不一致;先跑 make update-api 更新签名文件。
  2. 实现不能同时 extends Stub 和 SystemService:Java 单继承,所以拆成 Service(extends Stub) + 内部 Lifecycle(extends SystemService)
  3. .aidl 放对目录:必须在 core/java/ 下才会被 framework 自动收;放别处不编译。
  4. SELinux:服务能注册还需要 sepolicy 放行(见 L6),否则 addService 时 avc denied,服务起不来。
  5. @SystemApi vs @hide:三方 App 用不了 @hide 接口;只有系统 App / 反射能调。学习够用,产线对外要 @SystemApi。

9. 常见问题分析与定位(系统服务实战)

9.1 常见问题清单

  1. 开机后服务没起(logcat 没 onStart 日志)
  2. App getSystemService("hello") 返回 null
  3. 编译报 API check 失败
  4. 服务起了但 App 调用 crash(SecurityException/avc)
  5. 服务 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 失败

  • 现象:makeapi-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. 小结

  1. 能编进镜像 ≠ 真集成:真集成 = 编进 + 开机自动起(SystemServer 注册)+ 能被调用。
  2. getSystemService 链路:Context → SystemServiceRegistry 注册表 → ServiceManager 取 BinderProxy → 包成 Manager。
  3. Java 系统服务三件套:实现 extends IXxx.Stub + 内部 Lifecycle extends SystemService + SystemServer 一行 startService
  4. 和 L1 同一套 Binder:L1 是 native servicemanager,L2 是 Java 层,机制一样。
  5. 排查口诀:没起查 startService+onStart;null 查 service list+三处 name;编译挂 update-api;调用挂查 sepolicy。

下节预告

L3:SystemServer 启动流程 + Service 三阶段生命周期 —— 为什么服务启动有先后顺序?onBootPhase 各阶段干嘛?为什么有些初始化不能放 onStart?

评论