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 统计等手段排障。
学习主线可以概括为:定义接口 → 生成 Binder 骨架 → 实现服务 → 注册到系统 → 客户端获取代理 → 跨进程调用 → 权限与稳定性加固。这条主线贯穿全部章节。

二、核心概念:把 AIDL 放回 Binder 体系

AIDL 只是“接口描述”,真正干活的是 Binder 驱动与 Binder 对象。一次调用中,客户端持有的是 Proxy,服务端暴露的是 Stub;Proxy 把方法调用打包成 Parcel,通过 transact 交给 Binder 驱动,驱动把数据送到服务端线程池,由 onTransact 解包并调用真实实现,返回值再沿原路返回。理解这条链路,才能明白为什么 AIDL 方法默认是同步的、为什么回调要注册 RemoteCallbackList、为什么大对象传输会触发 TransactionTooLargeException。

另一个关键概念是“系统服务”与“普通 Service”的区别。普通 Service 运行在应用进程,生命周期由 ActivityManager 管理;系统服务通常运行在 SystemServer 进程,随系统启动而常驻,通过 ServiceManager 以字符串名称注册,任何有权限的进程都能获取。AIDL 在这里承担的是“跨进程契约”的角色,而不是业务逻辑本身。

AIDL 系统服务开发整体知识框架 接口契约 · Binder 通信 · 系统注册 · 工程加固 客户端进程 业务代码调用接口 Ixxx.Stub.asInterface() Binder 驱动 / Parcel transact 打包数据 onTransact 解包分发 SystemServer 进程 Stub 实现真实逻辑 ServiceManager 注册 调用 转发 返回 结果 工程实践路线(七步闭环) 1 定义接口 .aidl 文件 2 生成骨架 Stub / Proxy 3 实现服务 继承 Stub 4 注册系统 addService 5 获取代理 getService 6 权限校验 UID / PID 7 调试 dumpsys 关键支撑:Parcel 序列化 · 线程模型 · 死亡回调 · 异常处理 · SELinux 与权限 交付标准:接口稳定、调用可追踪、权限可审计、异常可恢复

三、方法流程:一次 AIDL 调用的完整生命周期

把上面的框架落到一次具体调用,可以拆成七个阶段。理解每个阶段的输入输出,排障时就能快速定位问题出在哪一层。

  1. 接口定义:在 src/main/aidl 下按包名创建 IUserService.aidl,声明方法签名与参数方向。
  2. 编译生成:构建系统调用 aidl 工具生成 Java 文件,内部包含 Stub(服务端抽象类)与 Proxy(客户端代理)。
  3. 服务实现:服务端继承 Stub,实现业务方法,注意运行在 Binder 线程池而非主线程。
  4. 注册服务:在 SystemServer 启动阶段调用 ServiceManager.addService("user.service", binder)。
  5. 获取代理:客户端通过 ServiceManager.getService("user.service") 拿到 IBinder,再用 IUserService.Stub.asInterface() 转为接口。
  6. 跨进程调用:Proxy 将参数写入 Parcel,transact 经驱动送达,Stub.onTransact 解包并调用实现。
  7. 结果返回与清理:返回值回传;若服务进程死亡,客户端通过 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 + 重连 + 降级策略
排障时优先看三层:客户端是否拿到非空 IBinder、服务端是否收到 onTransact、Parcel 读写顺序是否与接口声明一致。多数“调不通”都出在这三层之一,而不是 Binder 驱动本身。