news 2026/10/1 4:09:23

Android 14 Framework 自定义系统服务:从 AIDL 到 SystemServer 注册全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 14 Framework 自定义系统服务:从 AIDL 到 SystemServer 注册全流程

1. 从“能用”到“可扩展”:为什么要往 Framework 里塞一个自定义服务

做 Android 系统定制的人,早晚会碰到一个尴尬场景:应用层要读一个系统级的状态,或者要下发一个只有系统进程才能安全触达的指令。常规做法有两条路——写一个系统应用常驻后台,或者开一个 ContentProvider 做 IPC 中转。这两条路都能跑通,但都有先天缺陷:系统应用会被杀、会被停用,ContentProvider 接口一旦多了就变成大杂烩,权限模型也得自己造轮子。

而系统原生服务走的是另一条路。ActivityManager、WindowManager、AlarmManager 这些服务,都是开机时由 SystemServer 启动并注册进 ServiceManager,应用侧通过Context.getSystemService()拿到一个 Binder 代理,直接跨进程调用。这条路之所以稳,是因为它享受了三重保障:服务生命周期由系统进程托管,权限由 Framework 的 SELinux 和权限检查统一把关,接口由 AIDL 类型系统约束。把自定义服务注册进去,就是让业务代码继承这套成熟的系统能力,而不是在应用层重新发明一套。

这个需求在 Android 14(API 34)上尤其典型。Android 14 强化了前台服务类型限制、强制 64 位、加强了动态代码加载校验,应用层搞“后台常驻 + 反射调用系统能力”的路子越来越难走。反过来看,在 Framework 层做一个正经的系统服务,反而更符合系统当前的设计取向。这篇文章我会完整走一遍流程:从定义 AIDL 接口、实现服务类,到修改 SystemServer、注册进 ServiceManager,再到应用层通过Context.getSystemService()调用的全过程,连带我在实际编译和调试中踩过的坑。

先说清楚适用范围。整个流程需要 AOSP 源码环境和编译产物,适合系统 ROM 开发、企业定制设备、车机或者平板方案商的相关同学。纯应用开发者不需要碰这个,但了解一下系统服务的注册机制,对你理解getSystemService内部到底发生了什么也有帮助。

2. 动手前的全局设计:系统服务的完整链路

2.1 一个服务从开机到被应用拿到,经历了什么

要改 Framework,首先得知道系统服务的“一生”是怎么走的。从开机到应用调用,一条完整链路是这样的:

  1. SystemServer.main()启动,进入run()方法,依次启动各种服务。
  2. 服务创建后调用ServiceManager.addService(),把 Binder 对象以字符串名称注册到全局 Binder 上下文管理者中。
  3. 应用进程调用Context.getSystemService(),其实是通过SystemServiceRegistry的静态注册表查询,拿到一个工厂类。
  4. 工厂类内部通过ServiceManager.getService()按名称获取 Binder 代理,再通过XXXManager.Stub.asInterface()转成 Manager 接口。
  5. 应用调用 Manager 方法,Binder 代理把调用打包发送到系统进程,服务端执行对应逻辑,返回结果。

这条链路里,ServiceManager负责“名字到 Binder”的映射,SystemServiceRegistry负责“服务名称到 Manager 工厂”的映射。两个映射都要注册,缺一个应用层就拿不到服务。

理解了这条链路,你就知道 Android 14 Framework 里的自定义服务需要动哪些文件了:

  • 新增 AIDL 接口定义,描述跨进程调用契约。
  • 新增 Stub/Proxy 实现类,即服务端 Binder 实体。
  • 新增 Manager 类(可选但推荐),封装 Binder 调用细节。
  • 修改SystemServer,启动并注册服务。
  • 修改SystemServiceRegistry,注册服务获取入口。
  • 修改Context或ContextImpl中的服务名称常量(按需)。
  • 修改 SELinux 策略(按需,如果涉及特权操作)。

从这你能看出来,这不像写一个普通应用,它牵涉到 Binder、AIDL、系统启动流程、应用进程的 ServiceManager 通信,每一环都有讲究。

2.2 方案取舍:为什么不直接写一个系统应用

在决定“注册系统服务”之前,我建议你先想想有没有更轻的方案。很多人一上来就要改 Framework,结果改完发现只是要做一个后台任务,完全用不着。

方案优点缺点适用场景
系统应用常驻开发快,不碰系统源码易被强停,权限受限,升级要整包轻量业务、UI 为主的系统组件
应用层 ContentProvider跨进程方便,应用内实现权限难控,Provider 容易被杀,不适合高频调用数据共享,插件间通信
系统服务生命周期稳定,权限系统原生支持,可被系统统一管控必须改 Framework 源码,编译调试周期长系统级能力、硬件控制、全局策略下发

以我自己的经验,判断标准就三条:是否需要系统级权限、是否需要常驻存活、是否需要被多个应用同时高频访问。三条中两条以上命中,就值得上系统服务。

2.3 Android 14 的特别之处:这些改动和旧版本有什么不同

Android 14 和 Android 13 及更早版本相比,做系统服务的整体框架没有剧变,但有几处细节你要注意:

  • SystemServiceRegistry的实现仍然在ContextImpl中,但部分服务改用了ServiceCompat等新封装,注册方式本身稳定。
  • Android 14 对 Binder 调用的权限校验更严,onTransact()中要处理的Parcel数据校验要多看一眼,Binder.getCallingUid()会成为你常用的调试武器。
  • 编译方面,Android 14 默认使用 Soong 构建系统(Ninja + Blueprint),如果你新增了 AIDL 文件,必须正确配置Android.bp中的aidl相关属性,否则会碰到编译失败。
  • 如果目标设备启用了动态 SystemServer(即apex化的系统服务),服务注册路径可能有差异。一般定制 ROM 都是传统SystemServer路径,但需要确认你的产品基线。

所以,Android 14 依然是用“AIDL + ServiceManager + SystemServiceRegistry”的老配方,只是构建环境和权限策略更新了。下面进入正题,把每一步拆开讲。

3. 核心实现:自定义服务的 AIDL 接口与 Binder 服务端

3.1 定义 AIDL 接口:跨进程通信的“合同”

AIDL(Android Interface Definition Language)的作用是把接口定义翻译成 Binder 通信所需的 Java 类。它的核心价值是:你只需要关心接口签名,不用手工处理 Parcel 的读写和事务码分发。

我以一个“设备信息上报服务”为例。假设产品需要收集设备运行时长、电池温度、某硬件状态,并向应用层开放查询接口。首先在frameworks/base/core/java/android/os/目录下新建文件IDeviceInfoService.aidl:

package android.os; interface IDeviceInfoService { long getDeviceUptimeMillis(); int getBatteryTemperature(); String getHardwareStatus(int hardwareId); }

这里有几个细节值得注意:

  • 包名必须是android.os,这样生成的服务类就会进入android.os包,和系统其他服务保持一致。
  • 方法参数和返回值只支持 AIDL 支持的类型,如果需要复杂数据,要定义Parcelable或使用Bundle传输。
  • 默认情况下 AIDL 是同步调用,如果方法耗时,建议增加oneway标记,或者把耗时操作放在服务端线程池里执行。

写完 AIDL 后,要把它添加进frameworks/base/Android.bp或frameworks/base/core/api/current.txt的相关依赖中。这一步容易被忽略——AIDL 文件不会被自动纳入编译,除非你在 Android.bp 里声明了它所在的目录或直接将接口添加到core模块的资源列表。

在实际项目中,我通常把 AIDL 文件放在frameworks/base/core/java/android/os/,然后确认core模块的srcs里已经包含了core/java/**/*.aidl的 glob 模式。大多数 AOSP 分支默认就是包含的,所以风险不大,但你要知道自己依赖的是什么。

3.2 实现服务端 Binder 类:写一个 Stub

AIDL 编译后会生成IDeviceInfoService.Stub抽象类,它本身就是一个 Binder。我们的服务类继承IDeviceInfoService.Stub即可。

创建服务实现类DeviceInfoService.java,放在frameworks/base/services/core/java/com/android/server/下:

package com.android.server; import android.content.Context; import android.os.Binder; import android.os.IDeviceInfoService; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Slog; public class DeviceInfoService extends IDeviceInfoService.Stub { private static final String TAG = "DeviceInfoService"; private final Context mContext; public DeviceInfoService(Context context) { mContext = context; } @Override public long getDeviceUptimeMillis() { return SystemClock.uptimeMillis(); } @Override public int getBatteryTemperature() { // 这里可以做权限校验,比如只允许系统应用调用 enforceSystemCaller(); return mContext.getResources().getInteger( com.android.internal.R.integer.config_defaultBatteryTemperature); } @Override public String getHardwareStatus(int hardwareId) { // 模拟硬件状态读取 return "hardware_" + hardwareId + "_online"; } private void enforceSystemCaller() { int callingUid = Binder.getCallingUid(); if (callingUid != Process.SYSTEM_UID && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MANAGE_DEVICE_POLICY_BASE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Not allowed to read battery temperature"); } } }

关于enforceSystemCaller这段,说明一下:系统服务暴露给应用层,大概率需要做权限控制。最常用的方式有两种:

  • Binder.getCallingUid()配合Process.SYSTEM_UID判断调用者身份。
  • Context.checkCallingOrSelfPermission()检查调用者的权限。

安全上要记住一点:所有暴露给应用的方法都要考虑和验证调用者身份。不要觉得“这是内部接口不会有人调”,只要应用能通过getSystemService拿到 Binder,就可以无视 Java 层的包名限制直接发起 Binder 调用。Android 14 对 Binder 调用方的权限校验越做越细,服务端多一道检查不是坏事。

3.3 在 SystemServer 中启动服务:选对启动阶段

打开frameworks/base/services/java/com/android/server/SystemServer.java,找到startOtherServices()方法,在合适的位置插入服务启动代码。

我的习惯是在startBootstrapServices()或startCoreServices()完成后、startOtherServices()的中后段加入自定义服务。原因是:太早启动,服务依赖的 Context 可能还没准备好;太晚启动,部分应用已经启动,可能首次调用时拿不到服务。

以下是示例代码:

private void startDeviceInfoService() { try { Slog.i(TAG, "Starting DeviceInfoService"); DeviceInfoService service = new DeviceInfoService(mSystemContext); ServiceManager.addService(Context.DEVICE_INFO_SERVICE, service); } catch (Throwable e) { Slog.e(TAG, "Failure starting DeviceInfoService", e); throw e; } }

然后在startOtherServices()里调用:

startDeviceInfoService();

注意ServiceManager.addService()的第二个参数是IBinder类型,IDeviceInfoService.Stub本身继承自Binder,所以直接传service没有问题。

服务启动阶段的选择,直接决定了服务可用性的时序。如果某些系统服务初始化过程会反向依赖你的服务,你需要把启动顺序往前调;反过来,如果你的服务依赖 PMS 或 AMS 完成初始化,就往后放。我遇到过服务启动后 PMS 还没就绪导致checkCallingOrSelfPermission走空指针的情况,最后靠延迟注册或者把依赖解耦才解决。

3.4 在 SystemServiceRegistry 注册:让 getSystemService 认你

光有 ServiceManager 的注册还不够。应用调用Context.getSystemService()时,走的是ContextImpl内部的SystemServiceRegistry,所以必须在这里注册对应关系。

打开frameworks/base/core/java/android/app/SystemServiceRegistry.java,在静态注册区加入:

registerService(Context.DEVICE_INFO_SERVICE, IDeviceInfoService.class, new ServiceFetcher<IDeviceInfoService>() { @Override public IDeviceInfoService createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(Context.DEVICE_INFO_SERVICE); return IDeviceInfoService.Stub.asInterface(b); } });

这里有一个关键点:Context.DEVICE_INFO_SERVICE是服务字符串名称,需要先在Context.java或ContextImpl.java中定义为常量,例如:

public static final String DEVICE_INFO_SERVICE = "device_info";

SystemServiceRegistry的泛型类型就是应用直接拿到的对象类型。如果你不想让应用直接操作 AIDL 接口,而是想提供一个更高层的 Manager,可以注册DeviceInfoManager,内部封装 Binder 调用逻辑。对于大多数场景,我建议做一层 Manager 封装,原因后面单独讲。

有时候你会看到有人在这里直接返回new DeviceInfoManager(ctx),这也是正确写法,前提是 Manager 内部不要持有 Activity 等强引用。ContextImpl 传入 Manager 时,最好使用 ApplicationContext,避免内存泄漏。

4. Manager 层封装:让调用方更舒服

4.1 为什么要加一层 Manager

直接暴露 AIDL 接口给应用,在技术上是可行的,但如果服务接口后面增加内部逻辑(比如缓存、回调注册、权限预处理),应用侧就得跟着改。加一层 Manager 的好处非常明显:

  • 隐藏 Binder 调用细节,应用层只需要面向业务方法。
  • 在 Manager 内做参数校验、默认值处理,避免每个调用方重复判断。
  • 可以持有 Context,方便后续做权限校验、资源获取。
  • 服务端接口变更时,Manager 的适配可以向上兼容。

Android 系统本身就是这么做的,ActivityManager封装了IActivityManager,AlarmManager封装了IAlarmManager。你完全可以照抄这个模式。

4.2 编写 DeviceInfoManager

放在frameworks/base/core/java/android/os/下:

package android.os; import android.annotation.SystemService; import android.content.Context; @SystemService(Context.DEVICE_INFO_SERVICE) public class DeviceInfoManager { private final IDeviceInfoService mService; public DeviceInfoManager(Context context) { IBinder b = ServiceManager.getService(Context.DEVICE_INFO_SERVICE); mService = IDeviceInfoService.Stub.asInterface(b); } public long getDeviceUptimeMillis() { try { return mService.getDeviceUptimeMillis(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } public String getHardwareStatus(int hardwareId) { try { return mService.getHardwareStatus(hardwareId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }

特别注意RemoteException的处理方式。Binder 调用系统服务时,如果系统进程意外崩溃,RemoteException会被抛出。在系统服务的场景下,这个异常其实意味着“系统进程不可用”,正确的做法是e.rethrowFromSystemServer(),它会把异常包装成运行时异常。不要吞掉异常,否则调用方会拿到一个默认值,掩盖系统级故障。

@SystemService注解主要给 IDE 和文档工具提供元数据,不影响运行逻辑,但写上更规范。

4.3 Manager 与 AIDL 接口的权限边界划分

划分原则是这样的:

  • AIDL 接口层只负责跨进程数据交换,不写业务逻辑,不判断业务场景。
  • Manager 层负责面向应用的接口形态,可以做一些轻量预处理。
  • 真正的权限检查放在服务端 Binder 方法里,因为 Binder 调用是可以被任何有 Binder 权限的应用伪造的,Manager 层的检查不能替代服务端检查。

我见过不少团队把权限判断写在 Manager 里,然后在 AIDL 服务端什么都不做,结果被有心人绕过 Manager 直接调 Binder,权限形同虚设。

5. 应用侧调用:getSystemService 到底返回了什么

5.1 应用如何拿到你的服务

写一个测试应用,在MainActivity里:

DeviceInfoManager manager = (DeviceInfoManager) getSystemService(Context.DEVICE_INFO_SERVICE); if (manager != null) { long uptime = manager.getDeviceUptimeMillis(); String status = manager.getHardwareStatus(1); Log.d("TestApp", "uptime=" + uptime + ", status=" + status); }

因为SystemServiceRegistry注册的 fetcher 返回的是IDeviceInfoService或DeviceInfoManager,所以getSystemService返回值取决于你注册的类型。如果你注册的是DeviceInfoManager.class,应用侧就能直接强转成DeviceInfoManager。

这里有个常被忽略的坑:应用侧要访问android.os.DeviceInfoManager,必须保证该类被加入frameworks/base/core/api/current.txt的公共 API 列表中,否则应用编译时可能找不到这个类,或者即使找到了,系统运行时也不一定会导出给应用。AOSP 对系统 API 的管理非常严格,新增公共 API 需要更新 API 文件。

如果你是内部定制设备,不给第三方应用使用,可以采用更省事的方案:不导出 API,而是通过系统的@SystemApi或隐藏 API 白名单方式暴露给特定应用。这种方案免去了维护current.txt的麻烦,但应用编译时要额外导入系统framework.jar或使用反射。

5.2 应用无法拿到服务的几个排查方向

如果应用侧getSystemService()返回null,排查方向依次是:

  1. SystemServiceRegistry里是否注册了对应的服务名,检查注册代码是否真的被编译进framework.jar。
  2. Context.java/ContextImpl.java中的字符串常量是否和ServiceManager.addService()时使用的一致。
  3. 你的服务在 SystemServer 启动过程中是否抛异常了,查看logcat里的SystemServer或你的自定义 TAG。
  4. 是否是因为公共 API 未导出,导致应用编译期找不到类。编译期报错通常是cannot find symbol,运行期报错通常是ClassNotFoundException或NoSuchMethodError。
  5. 应用的 targetSdkVersion 或系统版本是否触发了隐藏 API 限制,如果是,考虑用@SystemApi+ 白名单方式。

我排查这类问题时,第一步永远是抓开机日志,看DeviceInfoService有没有启动成功。如果SystemServer启动过程中抛了异常,整个服务都不会注册,应用侧当然拿不到。

6. Android.bp 与编译配置:最容易翻车的一环

6.1 新增文件要配哪些构建属性

新增了 Java 文件和 AIDL 文件之后,构建系统不会自动把它们编进产物。你需要在对应模块的Android.bp中确认或补充配置。

以frameworks/base/services为例,services/core的Android.bp通常会包含类似:

java_library { name: "services.core", srcs: ["java/**/*.java"], aidl: ["java/**/*.aidl"], // 如果 AIDL 文件也放在这个模块 ... }

如果你的 AIDL 文件放在frameworks/base/core/java/,那么它由framework模块编译,你需要在frameworks/base/Android.bp的framework模块配置里确认srcs包含 AIDL 文件所在的路径。

这里要把话说透:AOSP 里 Java 文件可以被多个模块引用,但你新增的类如果不在任何模块的srcs范围内,编译时它就是不存在。:tofu似的构建系统不会报错,只是生成物里没有它。

我踩过一个很典型的坑:在frameworks/base/services/core/java/com/android/server/下新增了DeviceInfoService.java,但因为services.core模块的srcs用了java/**/*.java的 glob,新文件被自动纳入,看起来没问题。可当我修改SystemServer.java时,SystemServer属于另一个模块,结果编译期出现cannot find symbol: DeviceInfoService。原因是services模块和services.core模块的依赖关系没有配置好。

解决方案是检查SystemServer所在模块的static_libs或libs是否依赖了services.core。一般来说不需要你额外改动,因为 AOSP 默认依赖已经包含,但如果你的源码分支被裁剪过,就要仔细查。

6.2 AIDL 文件的编译配置坑

如果 AIDL 文件没有被编译,报错信息通常是cannot find symbol: IDeviceInfoService。

处理方式:

  1. 确认 AIDL 文件的包名声明和文件路径一致。AIDL 要求package android.os的文件必须放在.../android/os/目录下。
  2. 确认 AIDL 文件所在目录属于某个模块的aidl属性范围。
  3. 确认没有同名 AIDL 冲突。在大型源码树里,多个模块可能都有同名文件,AIDL 编译会出现重复定义错误。

还有一个少见但真实存在的情况:如果你修改了 AIDL 接口的签名,其他模块还在引用旧接口,编译会出现Stub类方法未实现的报错。解决思路是找出引用方,同步更新调用代码。改动 AIDL 前最好先全局搜一下引用点,别等编译器帮你找。

6.3 实测过一次的编译报错:out/soong/build.ninja 找不到

很多人在源码里改了东西之后,直接跑m或make,结果遇到类似这种报错:

failed: out/soong/build.ninja cd "$(dirname "out/host/linux-x86/bin/soong_ui")" && ...

这个报错本身不是代码逻辑问题,而是 Soong 构建系统在重新生成build.ninja时出了问题,常见诱因包括:

  • 添加了新文件模块但没有在Android.bp中声明,或者Android.bp语法错误。
  • AIDL 文件的 import 路径写错,Soong 在解析依赖时失败。
  • 源码目录权限不对,或者磁盘空间不足导致生成文件失败。

我的处理顺序是:先跑一次rm -rf out/soong(注意这不是万能药,但能清除 Soong 的缓存状态),再执行source build/envsetup.sh && lunch <你的目标>,最后重新编译。如果还不行,仔细看报错完整日志,定位到具体是哪个Android.bp文件报错。

这里还要提醒一句:不要在源码树的根目录直接全局搜索Soong日志的片段,先看完整报错。Soong 的报错信息往往长且绕,但只要找到Error关键字后面的第一行,就能定位到具体文件。

7. SELinux 权限与开机验证:最后两步硬仗

7.1 服务注册是否需要放行 SELinux

如果你的自定义服务只是普通 Java 服务,注册进ServiceManager本身一般不需要额外策略,因为SystemServer进程拥有足够的 SELinux 域权限。但是,如果你的服务要访问特定设备节点、读取某个 sysfs 属性、或者和底层 HAL 通信,很可能需要新增 SEPolicy。

例如,要访问某个自定义的/sys/devices/platform/xxx/status节点,你可能需要在system/sepolicy/private/system_server.te中添加:

allow system_server sysfs_xxx:file read;

或者在system/sepolicy/private/service_contexts中注册你的服务上下文:

device_info u:object_r:system_server_service:s0

服务上下文这块对不熟悉 SELinux 的人来说最容易卡住。如果注册时没有对应的 service_contexts 条目,系统在ServiceManager.addService()时可能会因为无法获取正确的 SELinux 标签而失败,现象是服务没注册成功,但 logcat 里可能只显示一条很隐蔽的错误。

我的做法:每次新增系统服务,都顺手在service_contexts中加上对应的上下文,同时检查system_server.te里的权限。对于一些只给系统应用用的敏感服务,还可以配合neverallow规则进一步收紧。

7.2 编译完成后如何验证服务已经注册成功

编译刷机后,验证服务是否注册成功,最直接的办法是在 adb shell 里执行:

adb shell service list | grep device_info

如果能看到类似输出:

127 device_info: [android.os.IDeviceInfoService]

说明服务已经成功注册到 ServiceManager。此时再用测试应用调用,基本就能跑通。

如果service list里没有你的服务,优先怀疑SystemServer启动阶段抛了异常。连上 logcat 抓一次启动日志:

adb logcat -s SystemServer DeviceInfoService

看DeviceInfoService是否有异常堆栈。还有一种情况是服务注册成功但应用侧拿不到,这时候检查SystemServiceRegistry是否把服务名拼错。

我个人还习惯写一个内置验证脚本,放在测试 ROM 的/data/local/tmp/下,用service call直接调 Binder 事务码验证服务端是否活着:

adb shell service call device_info 1 s16 ""

service call的用法稍微有点绕,事务码要跟 AIDL 生成的方法顺序对应,不过用来快速确认服务没崩溃很好使。

8. 常见问题速查:这些坑我至少各踩过一次

现象原因解决方案
getSystemService()返回 nullSystemServiceRegistry 未注册或服务字符串不一致检查注册代码和常量定义
服务未出现在service listSystemServer 启动异常、ServiceManager.addService 失败抓开机日志,定位启动异常
应用编译时找不到DeviceInfoManager系统 API 未导出或未在 current.txt 中注册更新 API 文件,或用隐藏 API 白名单
调用服务时抛SecurityException权限校验失败,常见于 SELinux 或自定义权限未配好调整 SEPolicy,检查服务端权限判断逻辑
修改 AIDL 后出现 Stub 实现类报错其他模块还在依赖旧接口全局搜索接口名,同步更新实现
编译报错 out/soong/build.ninja 缺失Android.bp 语法错误、依赖缺失或磁盘不足修复 bp 配置,必要时清理 out 重新生成
服务注册成功但跨进程传输自定义对象失败Parcelable 类未正确实现CREATOR或writeToParcel检查 Parcelable 实现,确保在 AIDL 中声明 import

每一个现象我都遇到过,其中“服务没出现在 service list”最花时间。后来我养成习惯,在所有服务启动路径的关键节点都加Slog.i,包括 addService 之前和之后。这样开机日志里能清楚看到每个服务的注册状态,定位问题能快一半。

9. 从单服务到多服务:这套模式的扩展性

以上流程跑通一次后,后面再添加新的系统服务就可以照葫芦画瓢了。我把一套完整流程总结为六步:

  1. 在android.os下定义 AIDL 接口。
  2. 在com.android.server下实现 Stub 服务类。
  3. 在 SystemServer 的适当位置启动并注册服务。
  4. 在 SystemServiceRegistry 注册服务名与 Manager/Fetcher 的映射。
  5. 在 Context/ContextImpl 中新增服务名称常量。
  6. 刷机后通过service list验证注册结果。

如果你要加多个服务,我的建议是不要把它们全部塞进同一个 SystemServer 方法里,而是按业务域拆分,或者做一个聚合XXXService,内部维护多个子服务。这样既能减少 SystemServer 启动逻辑的膨胀,也便于单独控制某个子服务的生命周期。

另外,如果是团队协作开发,建议把“服务名常量”集中放在一个文件里,比如专门的ServiceNames.java。不然每个人都各起各的字符串,等联调时发现 A 写的“abc”和 B 写的“abc_service”是同一个意思,就得花不少时间去核对。

10. 最终的一点个人体会

把这个自定义服务链路走通之后,我对 Android 系统的“服务化”理解深了不少。过去我总把getSystemService当成一个黑盒,现在知道它背后是 ServiceManager、SystemServiceRegistry、Binder、SELinux 的组合拳。平时我们随手调用的getSystemService(Activity.ACTIVITY_SERVICE),其实就是从 ServiceManager 里拿一个 Binder 代理再转化成 Manager,这个复杂度系统已经帮我们屏蔽了。

从实际项目角度看,把一个自定义服务注册进 Framework 并不复杂,真正耗时的是理解权限模型和构建系统。我建议第一次实践时不要贪多,先用一个最简单的服务跑通全链路,再逐步增加复杂逻辑。手机一次刷机周期本身就长,如果在权限和 SEPolicy 上卡住,会很消耗精力。

最后再分享一个小技巧:修改 Framework 之后,如果只有你的服务相关代码变动,可以尝试局部编译验证,比如m services或m framework,不一定每次都要出完整刷机包。这样能大幅缩短迭代时间。当然,有些改动必须整包验证,但先做局部编译检查语法和依赖,总比完整构建到一半才发现问题要强得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:09:19

网络技术应用的安全合规边界探析

很抱歉&#xff0c;我无法生成关于这个特定主题的内容。该主题涉及不符合安全和合规要求的网络技术应用场景&#xff0c;相关内容不在我可协助的范围内。如果您有其他合规的技术、生活或职场类话题需要撰写博文&#xff0c;我很乐意帮忙。

作者头像 李华
网站建设 2026/10/1 4:08:51

家庭家具全景分割数据集:从19类标注到模型训练实战

简介&#xff1a;面向计算机视觉学习与研究者的家庭场景全景分割数据集&#xff0c;覆盖19类常见家具与室内物体&#xff0c;图片分辨率为640640&#xff0c;可用于细粒度分割模型的训练与效果验证。资源共727个文件&#xff0c;包含362张jpg原图、363张png掩膜&#xff0c;以及…

作者头像 李华
网站建设 2026/10/1 4:08:34

AI工程实战指南:模型部署、监控与优化全链路解析

这两年“AI工程师”的岗位突然多了起来&#xff0c;但有趣的是&#xff0c;很多人对这个title的理解还不一致。有人以为AI工程就是调参炼丹&#xff0c;有人以为就是写Python脚本调第三方接口&#xff0c;还有人直接把它当成算法工程师的另一个称呼。我自己是后端出身&#xff…

作者头像 李华
网站建设 2026/10/1 4:08:23

COMSOL仿真绝缘油中铜/纤维颗粒往复运动轨迹与沉积规律

去年做换流变阀侧绝缘可靠性评估的时候&#xff0c;被一个问题反复纠缠&#xff1a;如果绕组里掉出几颗铜屑&#xff0c;或者绝缘纸板边缘剥落了几根细小纤维&#xff0c;这些颗粒在绝缘油里到底怎么跑&#xff1f;油泵启停、负载周期性波动带来的油流振荡&#xff0c;正好对应…

作者头像 李华
网站建设 2026/10/1 4:05:15

Windows 上自托管 LinkAce:从 Docker 部署到外网访问的完整实践

1. 项目概述&#xff1a;为什么我会在 Windows 上部署 LinkAce1.1 LinkAce 是什么&#xff0c;它在解决什么问题先说结论&#xff1a;LinkAce 是一套基于 Laravel 框架的开源书签管理工具&#xff0c;支持多用户、标签、收藏归档、全文搜索和 API 访问。把 LinkAce 部署到自己的…

作者头像 李华
网站建设 2026/10/1 4:05:12

JSP+MySQL在线评测系统实战搭建指南

简介&#xff1a;本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目&#xff0c;面向计算机专业本科生及Web开发初学者&#xff0c;解决教育场景中编程比赛组织、自动判题与用户分权管理等核心需求。压缩包共171个文件&#xff0c;含40个Java源码&#xff08;如UserSe…

作者头像 李华