1. 课程导入
在 Android 系统开发中,应用进程与系统服务进程天然隔离。普通应用通过 Context.getSystemService() 拿到的大多是封装后的 Manager,而真正承载能力的 SystemServer 服务往往运行在独立进程里。要让两个进程像调用本地对象一样协作,就需要一套跨进程通信(IPC)契约,AIDL(Android Interface Definition Language)正是 Android 官方为 Binder 机制提供的接口描述语言。本课程围绕“用 AIDL 开发可落地的系统服务”展开,从接口定义、Binder 原理、服务注册、权限校验到工程化调试,形成一条完整的实践路线。
一、学习目标:从会写到能上线
本课程不是单纯讲语法,而是以“能独立交付一个系统服务”为终点。学完后你应当具备四类能力:
- 接口设计能力:能用 AIDL 定义方法、参数、返回值,理解
in、out、inout的数据流向差异,能区分基本类型、Parcelable、List、Map 与接口回调的写法。 - Binder 原理能力:理解 Stub/Proxy 的生成结构、transact 与 onTransact 的调用链、Parcel 的序列化过程,能解释一次跨进程调用到底发生了什么。
- 系统服务集成能力:掌握在 SystemServer 中注册服务、通过 ServiceManager 获取服务、使用
addService与getService的时机与线程模型。 - 工程与安全能力:会做权限校验、调用方 UID/PID 检查、异常与死亡回调处理,并能用日志、dumpsys、Binder 统计等手段排障。
二、核心概念:把 AIDL 放回 Binder 体系
AIDL 只是“接口描述”,真正干活的是 Binder 驱动与 Binder 对象。一次调用中,客户端持有的是 Proxy,服务端暴露的是 Stub;Proxy 把方法调用打包成 Parcel,通过 transact 交给 Binder 驱动,驱动把数据送到服务端线程池,由 onTransact 解包并调用真实实现,返回值再沿原路返回。理解这条链路,才能明白为什么 AIDL 方法默认是同步的、为什么回调要注册 RemoteCallbackList、为什么大对象传输会触发 TransactionTooLargeException。
另一个关键概念是“系统服务”与“普通 Service”的区别。普通 Service 运行在应用进程,生命周期由 ActivityManager 管理;系统服务通常运行在 SystemServer 进程,随系统启动而常驻,通过 ServiceManager 以字符串名称注册,任何有权限的进程都能获取。AIDL 在这里承担的是“跨进程契约”的角色,而不是业务逻辑本身。
三、方法流程:一次 AIDL 调用的完整生命周期
把上面的框架落到一次具体调用,可以拆成七个阶段。理解每个阶段的输入输出,排障时就能快速定位问题出在哪一层。
- 接口定义:在
src/main/aidl下按包名创建IUserService.aidl,声明方法签名与参数方向。 - 编译生成:构建系统调用 aidl 工具生成 Java 文件,内部包含
Stub(服务端抽象类)与Proxy(客户端代理)。 - 服务实现:服务端继承
Stub,实现业务方法,注意运行在 Binder 线程池而非主线程。 - 注册服务:在 SystemServer 启动阶段调用
ServiceManager.addService("user.service", binder)。 - 获取代理:客户端通过
ServiceManager.getService("user.service")拿到 IBinder,再用IUserService.Stub.asInterface()转为接口。 - 跨进程调用:Proxy 将参数写入 Parcel,transact 经驱动送达,Stub.onTransact 解包并调用实现。
- 结果返回与清理:返回值回传;若服务进程死亡,客户端通过
linkToDeath收到通知并做重连或降级。
// IUserService.aidl
package com.example.system;
interface IUserService {
int getUserId(String name);
void registerCallback(IUserCallback cb);
}
// 服务端注册(SystemServer 中)
ServiceManager.addService("user.service", new UserService());
// 客户端获取
IBinder b = ServiceManager.getService("user.service");
IUserService svc = IUserService.Stub.asInterface(b);
int id = svc.getUserId("alice");
四、案例与场景:从“能调通”到“敢上线”
假设我们要为定制系统增加一个“用户画像服务”,供设置、桌面、账号等多个模块查询用户标签。第一版往往只关注接口能调通:定义方法、注册服务、客户端拿到代理并返回数据。但真正上线前,至少要补三件事:
- 权限校验:在
onTransact或方法入口检查Binder.getCallingUid(),只允许系统签名或持有特定权限的调用方访问。 - 线程安全:Binder 线程池并发调用,共享数据必须加锁或用线程安全容器,避免偶发数据错乱。
- 死亡回调:客户端注册
IBinder.DeathRecipient,服务重启后重新获取代理,避免长期持有失效 Binder。
oneway 异步调用,避免阻塞调用方线程。
五、常见误区与复盘清单
| 误区 | 表现 | 正确做法 |
|---|---|---|
| 把 AIDL 当普通接口用 | 在方法里做耗时 IO,导致调用方卡顿 | 耗时逻辑放服务端异步线程,接口只做轻量交互 |
| 忽略参数方向 | out 参数在客户端拿不到值 |
明确 in/out/inout,必要时用返回值替代 out |
| 不做权限校验 | 任意应用都能调用系统服务 | 校验 calling UID/PID 与权限,最小暴露接口 |
| 直接传大 Bitmap 或长列表 | TransactionTooLargeException | 改用文件描述符、分页查询或 Ashmem |
| 不处理服务死亡 | 服务重启后客户端一直报 DeadObjectException | linkToDeath + 重连 + 降级策略 |