InputManagerService 十分钟让你了解Android触摸事件原理( 三 )


void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) { ... mWindowHandles = inputWindowHandles; ...谁会调用这个函数呢? 真正的入口是WindowManagerService中的InputMonitor会简介调用InputDispatcher::setInputWindows , 这个时机主要是跟窗口增改删除等逻辑相关 , 以addWindow为例:

InputManagerService 十分钟让你了解Android触摸事件原理

文章插图
 
从上面流程可以理解为什么说WindowManagerService跟InputManagerService是相辅相成的了 , 到这里 , 如何找到目标窗口已经解决了 , 下面就是如何将事件发送到目标窗口的问题了 。
如何将事件发送到目标窗口找到了目标窗口 , 同时也将事件封装好了 , 剩下的就是通知目标窗口 , 可是有个最明显的问题就是 , 目前所有的逻辑都是在SystemServer进程 , 而要通知的窗口位于APP端的用户进程 , 那么如何通知呢?下意识的可能会想到Binder通信 , 毕竟Binder在Android中是使用最多的IPC手段了 , 不过Input事件处理这采用的却不是Binder:高版本的采用的都是Socket的通信方式 , 而比较旧的版本采用的是Pipe管道的方式 。
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) { pokeUserActivityLocked(eventEntry); for (size_t i = 0; i < inputTargets.size(); i++) { const InputTarget& inputTarget = inputTargets.itemAt(i); ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); preparedispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); } else { } }}代码逐层往下看会发现最后会调用到InputChannel的sendMessage函数 , 最会通过socket发送到APP端(Socket怎么来的接下来会分析) , 
InputManagerService 十分钟让你了解Android触摸事件原理

文章插图
 
这个Socket是怎么来的呢?或者说两端通信的一对Socket是怎么来的呢?其实还是要牵扯到WindowManagerService , 在APP端向WMS请求添加窗口的时候 , 会伴随着Input通道的创建 , 窗口的添加一定会调用ViewRootImpl的setView函数:
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { <!--创建InputChannel容器--> mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); <!--添加窗口 , 并请求开辟Socket Input通信通道--> res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); }... <!--监听 , 开启Input信道--> if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); }在IWindowSession.aidl定义中 InputChannel是out类型 , 也就是说需要服务端进行填充 , 那么接着看服务端WMS如何填充的呢?
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {... if (outInputChannel != null && (attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { String name = win.makeInputChannelName(); <!--关键点1创建通信信道 --> InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); <!--本地用--> win.setInputChannel(inputChannels[0]); <!--APP端用--> inputChannels[1].transferTo(outInputChannel); <!--注册信道与窗口--> mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); }WMS首先创建socketpair作为全双工通道 , 并分别填充到Client与Server的InputChannel中去;之后让InputManager将Input通信信道与当前的窗口ID绑定 , 这样就能知道哪个窗口用哪个信道通信了;最后通过Binder将outInputChannel回传到APP端 , 下面是SocketPair的创建代码:
status_t InputChannel::openInputChannelPair(const String8& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { status_t result = -errno; ... return result; }int bufferSize = SOCKET_BUFFER_SIZE; setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); <!--填充到server inputchannel--> String8 serverChannelName = name; serverChannelName.append(" (server)"); outServerChannel = new InputChannel(serverChannelName, sockets[0]); <!--填充到client inputchannel--> String8 clientChannelName = name; clientChannelName.append(" (client)"); outClientChannel = new InputChannel(clientChannelName, sockets[1]); return OK;}


推荐阅读