12.1 窗口注册的起点:addWindow
应用要显示窗口,第一步就是调用WMS的addWindow方法。这个方法在WMS里大概有300多行,逻辑非常密集。我们挑重点看。
当应用通过Binder调用到WMS时,WMS会做几件事:
- 检查窗口的权限、token是否合法
- 创建
WindowState对象,代表这个窗口 - 分配Surface(这块我们后面会细讲)
- 创建InputChannel
你看,创建InputChannel是窗口注册的必经之路。没有它,事件就传不过去。
核心流程:
应用进程 → Binder调用 → WMS.addWindow() → 创建WindowState → 创建InputChannel → 返回给应用
我在项目中遇到过一个问题:某个第三方应用窗口注册成功了,但死活收不到触摸事件。查了半天,发现是InputChannel创建时出错了,但WMS没有抛异常,只是静默失败了。这种坑,真的让人头大。
12.2 InputChannel的创建与配对
好,我们来看InputChannel到底是怎么创建的。这部分代码在WMS.addWindow里,大概长这样:
// WindowManagerService.java InputChannel inputChannel = null; if (outInputChannel != null) { String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.setInputChannel(inputChannels[0]); inputChannel = inputChannels[1]; // 把服务端的那一端注册到InputManagerService mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); }这里有个关键函数——openInputChannelPair。它返回的是一个数组,里面有两个InputChannel。为什么是两个?
你想想看,事件传递是双向的吗?其实不是。事件是从WMS流向应用,但应用处理完事件后,需要反馈一个“处理完成”的信号。所以,这两个InputChannel实际上是一对SocketPair。
| 索引 | 用途 | 所在进程 |
|---|---|---|
| inputChannels[0] | 服务端(WMS侧) | system_server |
| inputChannels[1] | 客户端(应用侧) | 应用进程 |
说白了,openInputChannelPair底层就是调用了Linux的socketpair系统调用。创建了一对互相连接的Unix域套接字。一个写,另一个就能读,反之亦然。
我的经验:
我曾经调试过一个性能问题,发现事件从WMS到应用进程延迟很高。后来用strace抓了一下,发现socketpair的缓冲区大小是默认的。我们手动调大了SO_SNDBUF和SO_RCVBUF,延迟就降下来了。这种底层细节,文档里基本不会写,得自己踩坑才知道。
12.3 注册到InputManagerService
InputChannel创建好之后,WMS会把服务端的那一端(inputChannels[0])注册到InputManagerService。这一步很重要。
注册的代码在InputManagerService.java里:
// InputManagerService.java public void registerInputChannel(InputChannel inputChannel, InputWindowHandle inputWindowHandle) { nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle); }这个nativeRegisterInputChannel会走到Native层,最终调用到InputDispatcher的registerInputChannel方法。InputDispatcher会把InputChannel和对应的窗口信息存到一个HashMap里。
为什么要存起来?因为InputDispatcher在分发事件时,需要根据焦点窗口找到对应的InputChannel,然后往里面写数据。
注意:
InputChannel注册到InputDispatcher后,InputDispatcher会创建一个Connection对象。这个Connection会监听InputChannel的可读事件。一旦应用进程处理完事件并写回信号,Connection就会收到通知。如果应用进程挂掉了,这个Connection也会检测到,InputDispatcher就会把该窗口标记为“死亡”。
12.4 应用进程接收InputChannel
那应用进程是怎么拿到InputChannel的呢?还记得addWindow方法的最后一个参数outInputChannel吗?它是一个Binder对象,WMS会把inputChannels[1]通过这个Binder传回给应用进程。
应用进程的ViewRootImpl会收到这个InputChannel,然后把它包装成一个WindowInputEventReceiver。这个Receiver会创建一个InputEventReceiver的Native对象,开始监听InputChannel上的事件。
流程大概是:
- 应用调用
WindowManagerGlobal.addView - 内部调用
WMS.addWindow,传入一个空的InputChannel - WMS创建一对InputChannel,把服务端注册到IMS,客户端通过Binder返回
- 应用拿到客户端的InputChannel,开始监听事件
嗯,这里有个细节。你可能觉得InputChannel的传递是同步的,其实不是。WMS在返回addWindow结果时,InputChannel就已经通过Binder传回来了。但应用进程真正开始监听,是在ViewRootImpl.setView方法里。这中间有一个时间窗口。
我曾经遇到过一个bug:快速启动和关闭窗口时,事件会丢失。原因就是窗口已经销毁了,但InputChannel还没来得及注销,InputDispatcher还在往里面写数据。后来我们在窗口销毁时增加了同步等待,确保InputChannel注销完成才返回。
12.5 小结
好了,这一章的内容就这些。我们来理一下思路:
- 窗口注册的核心是
addWindow方法 - InputChannel通过
socketpair创建,是一对互相连接的管道 - 服务端注册到InputDispatcher,客户端传给应用进程
- InputDispatcher通过InputChannel把事件写入应用进程