如何定义一个 AIDL 接口并实现跨进程调用?需要哪些步骤?
解读
在国内面试中,这道题几乎 100% 会出现在“Framework 层”环节,用来快速筛掉只写过 UI 的“API 调用工程师”。面试官真正想听的是:
- 你对 Binder 本质的理解(一次拷贝、内核驱动、C/S 架构);
- 你是否亲手写过 AIDL、编译过 stub、处理过 RemoteException;
- 你是否知道 Android 11 以后“包可见性”对隐式启动 Service 的影响;
- 你是否能把“线程安全”“Parcelable 序列化”“死亡代理”这些细节讲清楚。
回答时切忌只背“四步走”,而要围绕“定义→编译→实现→暴露→调用→异常→优化”七个关键词展开,让面试官听到你“踩过坑”。
知识点
- AIDL 语法:支持 Java 基本类型、String、CharSequence、List、Map、Parcelable、AIDL 接口本身;自定义 Parcelable 必须写同名 .aidl 文件 createFromParcel。
- Binder 线程池:客户端调用会挂起,服务端默认运行在 Binder 线程池,若需主线程需手动 Handler 切回。
- 死亡代理:DeathRecipient,服务端进程被杀后客户端可快速降级,避免卡死。
- 版本管理:服务端 oneway、in/out/inout 方向性、接口版本号字段,防止新老客户端混用出现 TransactionTooLargeException。
- 安全校验:国内厂商 ROM 对“后台启动 Service”限制极严,需显式 Intent + 包名 + 权限声明,Android 11+ 还要加 queries 标签。
- 性能陷阱:Binder 缓冲区 1 MB,复杂对象务必分页或匿名共享内存(Ashmem);避免在循环里调用同步接口。
- 替代方案:若仅单向传数据,优先考虑 Broadcast + Intent、Messenger、ContentProvider;AIDL 只在“双向、并发、低延迟”场景下使用。
答案
步骤一:定义接口
- 在服务端 module 的 main/aidl/包名/ 目录下新建 ICompute.aidl
package com.example.aidldemo; interface ICompute { int add(int a, int b); } - 若参数含自定义 Bean,需让 Bean 实现 Parcelable,并在同级目录写 Bean.aidl 声明 parcelable Bean;
步骤二:编译生成 Binder 桩
Gradle 同步后,build/generated/aidl_source_dir 下自动生成 ICompute.java,内含 Stub 抽象类与 Proxy 实现。
步骤三:实现服务端
- 新建 ComputeService 继承 Service,在 onBind 返回 ICompute.Stub 匿名实现:
private val binder = object : ICompute.Stub() { override fun add(a: Int, b: Int) = a + b } override fun onBind(intent: Intent) = binder - 在 AndroidManifest.xml 显式注册并加 exported="true",Android 11+ 需配套权限:
<service android:name=".ComputeService" android:exported="true" android:permission="com.example.COMPUTE"> <intent-filter> <action android:name="com.example.COMPUTE_SERVICE"/> </intent-filter> </service> - 定义权限文件:
<permission android:name="com.example.COMPUTE" android:protectionLevel="signature"/>
步骤四:客户端调用
- 将服务端 aidl 目录完整拷贝到客户端 main/aidl,保持包名一致;
- 在 Activity 中绑定服务:
private lateinit var computeService: ICompute private val connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { computeService = ICompute.Stub.asInterface(service) // 注册死亡代理 service.linkToDeath({ /* 降级逻辑 */ }, 0) } override fun onServiceDisconnected(name: ComponentName) { // 服务端异常被杀 } } bindService( Intent("com.example.COMPUTE_SERVICE").apply { `package` = "com.example.aidldemo" }, connection, Context.BIND_AUTO_CREATE ) - 调用远程方法需捕获 RemoteException,且不能在主线程做耗时调用,可用 Kotlin Coroutine + Dispatchers.IO 包裹。
步骤五:验证与签名
- 服务端与客户端必须使用同一签名证书,否则 protectionLevel="signature" 权限校验失败;
- 通过 adb shell service list | grep compute 查看是否注册成功;
- 用 adb shell am startservice 模拟隐式启动,确认国内 ROM 没有“后台弹出”拦截。
拓展思考
-
如果业务需要“服务端主动推送”怎么办?
可在 AIDL 里再定义一个 registerCallback(IComputeCallback cb) 接口,服务端持有 RemoteCallbackList<IComputeCallback>,利用 beginBroadcast / finishBroadcast 实现一对多推送;注意 RemoteCallbackList 内部自动处理 DeathRecipient,防止内存泄漏。 -
跨进程大数据传输如何优化?
超过 500 KB 时考虑匿名共享内存:服务端创建 MemoryFile,返回 ParcelFileDescriptor;客户端通过 SharedMemory 映射读取,避免 Binder 事务过大导致 TransactionTooLargeException。 -
如何做单元测试?
在 androidTest 里使用 ServiceTestRule 启动真实 Service,或通过 Mockito-inline 伪造 Binder,配合 Kotlin Coroutine 测试远程调用超时场景,确保客户端在 3 秒内能降级到本地缓存。 -
与 HIDL/AIDL 稳定化接口的区别?
Android 10 以后 Google 推出“稳定 AIDL”(Stable AIDL),接口一经发布即冻结,用于替换 HIDL。若你做的是系统服务(如 CameraService),需使用 stable AIDL 并写 aidl_interface 模块,通过 Soong 编译成 .so 供 vendor 与 system 双向调用,这与应用层 AIDL 的“源码拷贝”模式完全不同,面试时可作为加分项抛出。