34. 同步与异步:Binder 的同步调用与异步调用(oneway)的区别
Binder 的同步和异步,说白了就是「等不等结果」的问题。
我刚开始接触 Binder 时,觉得这俩不就是加个 oneway 关键字嘛,有啥好讲的?后来在项目里踩了坑,才发现这里面的门道比想象中深得多。
今天咱们就好好掰扯掰扯,同步调用和异步调用到底差在哪,什么时候该用哪个。
同步调用:你发完请求,必须等回复
同步调用是 Binder 的默认行为。客户端发起一个请求后,会阻塞在 transact() 调用上,直到服务端处理完并返回结果,客户端才能继续往下走。
我打个比方:你去柜台办业务,把材料递进去,然后站在窗口等着。柜员处理完了,把回执单递给你,你才离开。这就是同步。
代码上看,同步调用长这样:
// 客户端代码
IBinder binder = serviceManager.getService("my_service");
IMyService service = IMyService.Stub.asInterface(binder);
// 这行会阻塞,直到服务端返回
int result = service.doSomething(input);
// 只有拿到 result 后,才会执行下一行
Log.d("TAG", "result = " + result);
同步调用的特点很明显:
- 简单直观:写起来就像普通函数调用,不需要回调
- 天然保证顺序:前一个请求处理完,后一个才开始
- 会阻塞线程:如果服务端处理慢,客户端线程就卡住了
异步调用(oneway):发完就走,不等回复
异步调用,就是在 AIDL 接口方法前加上 oneway 关键字。客户端调用后立即返回,不等待服务端处理结果。
还是那个柜台的例子:你把材料递进去,柜员说「好了你走吧,办完了通知你」。你扭头就走,该干嘛干嘛。这就是异步。
代码上这样写:
// AIDL 定义
oneway interface IMyService {
void doSomethingAsync(in String input);
// 注意:oneway 方法不能有返回值,也不能有 out 参数
}
// 客户端调用
IMyService service = IMyService.Stub.asInterface(binder);
service.doSomethingAsync(input); // 立即返回,不阻塞
// 这里立刻执行,不等服务端处理完
Log.d("TAG", "请求已发出");
异步调用的特点:
- 不阻塞线程:调用立即返回,客户端可以继续做其他事
- 没有返回值:因为不等结果,所以方法必须是
void类型 - 参数有限制:只能有
in参数,不能有out或inout
核心区别对比
| 对比维度 | 同步调用 | 异步调用(oneway) |
|---|---|---|
| 调用是否阻塞 | 阻塞,直到服务端返回 | 立即返回,不阻塞 |
| 返回值 | 可以有返回值 | 必须是 void |
| 参数方向 | in / out / inout 都可以 | 只能 in |
| 调用线程 | 不能是 UI 线程 | 可以在 UI 线程 |
| 可靠性 | 高,有明确的结果 | 低,可能丢消息 |
| 典型场景 | 查询数据、获取状态 | 通知、事件上报、日志 |
底层原理:Binder 驱动怎么处理?
你想想看,同步和异步在 Binder 驱动层面是怎么实现的?
其实核心区别在于 BC_TRANSACTION 和 BC_REPLY 这两个命令的处理方式。
同步调用时:
- 客户端发送
BC_TRANSACTION,然后进入等待状态 - 服务端处理完后,发送
BC_REPLY回来 - 驱动唤醒客户端,把结果传回去
异步调用时:
- 客户端发送
BC_TRANSACTION,但transaction_flags中设置了TF_ONE_WAY - 驱动收到后,直接把请求扔给服务端,不等待回复
- 客户端线程立即返回,继续执行
说白了,同步调用是「一问一答」,异步调用是「只问不答」。
关键点:异步调用在驱动层面不会创建 BC_REPLY 事务。这意味着服务端即使崩溃了,客户端也感知不到。我曾经调试过一个 bug,服务端进程被 kill 了,客户端还傻乎乎地继续发 oneway 请求,数据全丢了。所以用 oneway 时,一定要做好「消息丢了怎么办」的预案。
什么时候用同步,什么时候用异步?
我个人习惯这样判断:
用同步的场景:
- 你需要拿到结果才能继续
- 操作必须保证执行完成
- 调用频率不高,服务端处理快
用异步的场景:
- 你只是通知一下,不关心结果
- 调用频率很高,不想阻塞客户端
- 数据可以丢,丢了也无所谓
举个例子:
- 获取联系人列表 → 同步,因为你要展示给用户
- 上报用户点击事件 → 异步,丢了就丢了,不影响功能
- 设置系统音量 → 同步,要确保设置成功
- 发送心跳包 → 异步,频繁调用,不阻塞主流程
SVG 流程图:同步 vs 异步调用流程
避坑指南
我曾经在做一个日志上报模块时,把所有接口都设计成 oneway,觉得这样性能好。结果上线后发现,某些关键日志丢了,排查了半天才发现是驱动队列满了。
后来我学乖了:
- 关键数据用同步,确保送达
- 非关键数据用异步,但加上本地缓存和重试机制
- 高频调用一定要控制频率,别把驱动队列撑爆
总结一下:同步和异步没有绝对的好坏,关键看场景。同步保可靠,异步保性能。选对了,系统稳如老狗;选错了,bug 追到秃头。
公众号:蓝海资料掘金营,微信deep3321