3. AOSP通信机制:Binder、Handler、Service

做AOSP测试框架,绕不开这三兄弟——Binder、Handler、Service。说白了,整个Android系统的“骨架”就是靠它们撑起来的。你要Mock回放,就得先把它们怎么通信、怎么调度、怎么绑定搞清楚。不然你写出来的Mock方案,很容易在真实设备上崩得一塌糊涂。

3.1 Binder:跨进程的“快递员”

Binder是Android里最核心的IPC机制。几乎所有跨进程调用,背后都是Binder在跑。我刚开始接触AOSP时,一直觉得Binder很玄乎——不就是个远程调用嘛?后来踩了坑才明白,它的设计思路和传统Linux IPC完全不同。

  • 一次拷贝:传统IPC(如Socket、管道)需要两次数据拷贝(用户态→内核态→用户态),而Binder只需要一次,性能高不少。
  • C/S架构:Client发起请求,Service提供服务,Binder驱动负责中转。每个Service都有一个唯一的标识(handle)。
  • 引用计数:Binder对象有强引用和弱引用,跨进程传递时不会乱释放。

举个例子,你在测试框架里Mock一个系统服务,实际上就是接管了那个Service的Binder接口。我建议你在做回放之前,先把目标Service的Binder接口走一遍,用ServiceManager.getService()拿到真实的Binder对象,然后序列化它的调用数据。

// 获取真实Binder对象
IBinder binder = ServiceManager.getService("window");
// 解析接口描述符
String descriptor = binder.getInterfaceDescriptor();
// 记录每次transact调用
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
int code = TRANSACTION_ADD_WINDOW;
binder.transact(code, data, reply, 0);
// 把data和reply存起来,后面回放用

3.2 Handler:线程间的“邮递员”

Handler是Android里线程通信的基础。为什么需要它?因为UI线程不允许阻塞,也不允许非UI线程直接更新UI。Handler+Looper+MessageQueue这三件套,构成了一个消息驱动模型。

我记得有一次写自动化测试,要模拟用户点击事件。直接用InputManager.injectInputEvent()发现事件没被处理,折腾了半天——原来是因为事件是在测试线程发的,而目标窗口的Handler绑定的是主线程Looper,消息队列没对上。后来我手动把事件post到主线程Handler里,问题就解决了。

在Mock回放框架里,Handler的回放尤其容易出问题。为什么呢?因为时间敏感。真实场景中,Handler消息有when字段,表示延迟时间。回放时如果忽略这个时间,可能会导致状态不一致。

字段含义回放处理方式
what消息ID严格匹配
obj附带对象需要深拷贝或序列化
when触发时间(毫秒)建议按比例压缩或保持相对延迟
callbackRunnable回调回放时直接执行,不用再创建Looper
曾经踩过的坑:某个测试用例里,Handler消息带了Bundle参数。回放时我直接用了原始引用,结果多个回放线程共享同一个Bundle,数据互相覆盖。后来我统一做了Parcel.obtain()反序列化,每个回放线程独立一份数据。

3.3 Service:系统能力提供者

Service在Android里分两种:系统服务(System Service)和应用服务。我们搞测试框架,主要打交道的是系统服务——比如ActivityManagerService、WindowManagerService、PackageManagerService等等。

每个系统服务都运行在system_server进程中,通过Binder对外暴露接口。在AOSP源码里,你可以在frameworks/base/services/core/java/com/android/server/下面找到它们。

你想Mock一个系统服务,传统做法是用Mockito或者EasyMock打桩。但对于AOSP这种大型系统,Mockito搞不定——因为有些服务内部引用了大量Android Framework的内部类,静态方法、final类比比皆是。

所以我们的框架走的是另一个路子:录制-回放。先让真实设备跑一遍测试用例,把所有Binder调用、Handler消息都记录下来,然后在Mock环境里原样回放。

关键点:录制时,不仅要记录Binder的transact()参数,还要记录当前线程、Looper状态、系统时间戳。回放时,需要把这些上下文全部还原,否则Mock出来的行为跟真实情况对不上。

3.4 三者协作:一张图看懂

下面这张图,画出了Binder、Handler、Service在AOSP测试框架里的协作关系。我画了很多遍才定稿——每次改版都会发现新的依赖关系。

AOSP通信机制协作图(测试框架视角) 真实系统服务 AMS / WMS / PMS ... Mock服务代理 继承Binder或使用BinderProxy Binder驱动 transact() / onTransact() Parcel序列化 / 反序列化 用户/测试线程 发送消息 Handler + Looper 消息队列 / dispatchMessage 录制回放框架 Interceptor → Recorder → Matcher → Replayer Service层(真实/代理) Binder驱动(IPC核心) Handler消息(线程通信) 录制回放框架(Mock核心)
我的习惯:在搭建Mock回放框架时,我会先把目标服务的Binder接口单独抽成一个接口层,这样录制时只记录接口调用,不关心底层Binder驱动细节。回放时,接口层直接返回预设的Parcel数据,效率高很多。

3.5 总结:回放时要关注的三件事

  1. 线程归属:Binder调用是在Binder线程池,Handler消息是在目标Looper线程,Service运行在system_server。回放时不能混用线程。
  2. 时间顺序:Handler消息的when字段和Binder调用的时间戳,决定了事件发生的先后顺序。乱序回放会导致逻辑错误。
  3. 状态依赖:很多Service有内部状态(比如Activity栈、窗口Z-order),回放前需要先初始化到录制时的初始状态。