Android Input(五)-InputChannel通信

原创内容,转载请注明出处,多谢配合。

上节讲到InputDispatcher通过publishKeyEvent把input事件发送给客户端,我们知道InputDispatcher是属于system_server进程,而客户端属于应用进程,两种通信属于跨进程通信,那么本篇文章就来分析下system_server与应用建立通信的过程。

源头得追溯到应用的setView开始:

frameworks/base/core/java/android/view/ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
      ...
      if ((mWindowAttributes.inputFeatures
          & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
          mInputChannel = new InputChannel(); //创建客户端的InputChannel对象
      }
      //通过Binder调用,进入system进程的Session
      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                  getHostVisibility(), mDisplay.getDisplayId(),
                  mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                  mAttachInfo.mOutsets, mInputChannel);//这里将客户端的InputChannel对象作为参数传入
      ...
      if (mInputChannel != null) {
          if (mInputQueueCallback != null) {
              mInputQueue = new InputQueue();
              mInputQueueCallback.onInputQueueCreated(mInputQueue);
          }
          //创建WindowInputEventReceiver对象
          mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                  Looper.myLooper());
      }
    }
}

应用在setView()的是会调用IWindowSession的addToDisplay()函数。
前面图形系统系列提过这个函数,这是添加窗口流程,它包含了App与SurfaceFlinger服务建立连接的部分,也是包含了今天要介绍的InputChannel建立连接的部分。它本身是个binder调用,服务端是WMS,最终调用的是WMS的addWindow方法。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

   public int addWindow(Session session, IWindow client, int seq,
           WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
           Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
           InputChannel outInputChannel) {
           …
           //WindowState是窗口对象
           WindowState win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
           ...
           if (outInputChannel != null && (attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
               String name = win.makeInputChannelName();
               // 创建socket pair用于通信
               InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
               // 将服务端的socket赋给服务端的WindowState
               win.setInputChannel(inputChannels[0]);
               // 将客户端的socket放入outInputChannel,最终返回客户端应用进程
               inputChannels[1].transferTo(outInputChannel);
               // 将服务端的socket(win.mInputChannel)注册到InputDispatcher中
               mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
           }

InputChannel通过InputChannel.openInputChannelPair分别窗建一对InputChannel,然后将Server端的InputChannel注册到InputDispatcher中,将Client端的InputChannel返回给客户端应用。

socket pair的创建过程这里不详细说,简单总结下:

openInputChannelPair 生成了两个Socket的fd, 代表一个双向通道的两端。初始化了两端的包括Native层和Java层的InputChannel对象,InputChannel封装了name和fd。

1)Server端的InputChannel注册到InputDispatcher中:

InputManagerService.java 执行registerInputChannel 走的JNI:nativeRegisterInputChannel,最终调到如下方法:

status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, const sp& inputChannel, const sp& inputWindowHandle, bool monitor) {
   return mInputManager->getDispatcher()->registerInputChannel(inputChannel, inputWindowHandle, monitor);
}

通过InputDispatcher的registerInputChannel注册

frameworks/native/services/inputflinger/InputDispatcher.cpp

status_t InputDispatcher::registerInputChannel(const sp& inputChannel, const sp& inputWindowHandle, bool monitor) {
   sp connection = new Connection(inputChannel, inputWindowHandle, monitor);
   int fd = inputChannel->getFd();
   mConnectionsByFd.add(fd, connection);
   ...
   mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
   mLooper->wake();
   return OK;
}

frameworks/native/services/inputflinger/InputDispatcher.h
class Connection : public RefBase {
...
    sp inputChannel; // never null
 ...
   Queue outboundQueue;
...
   Queue waitQueue;

    explicit Connection(const sp& inputChannel,

            const sp& inputWindowHandle, bool monitor);
...
};

这里,一个Connection 对象被创建出来,这个Connection表示客户端和服务端的一个输入数据通道,每个Connection都对应一个服务端的InputChannel,每个服务端的InputChannel又对应一个socket pair的fd。InputDispatcher用fd为索引,将所有的Connection保存在mConnectionByFd中。再将这个fd注册在InputDispatcher的Looper的监控列表里,这样一旦对端的socket写入数据,Looper就会被唤醒,接着就会调用回调函数handleReceiveCallback。另外,一个Dispatcher可能有多个Connection(多个Window)同时存在。

2)client端获取客户端的socket fd

inputChannels[1].transferTo(outInputChannel);
这个outInputChannel是客户端setView创建的,当参数传递过来的。
这里很明显就是将client端的fd传给outInputChannel,而AMS与应用通信是通过binder,因此此处fd是通过binder传递到应用。

然后在setView中会创建WindowInputEventReceiver对象,构造方法会调用super,在其父类InputEventReceiver的构造方法中会执行nativeInit,最终执行如下代码:

frameworks/base/core/jni/android_view_InputEventReceiver.cpp

status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);  
    return OK;
}
void NativeInputEventReceiver::setFdEvents(int events) {
  if (mFdEvents != events) {
      mFdEvents = events;
      int fd = mInputConsumer.getChannel()->getFd();
      if (events) {
          //将socket客户端的fd添加到主线程的消息池
          mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
      } else {
          mMessageQueue->getLooper()->removeFd(fd);
      }
  }
}

此处的Looper便是应用主线程的Looper,将socket客户端的fd添加到应用线程的Looper来监听,回调方法为NativeInputEventReceiver。

至此,socket正式建立连接。

两端连接建立的调用流程:

Android Input(五)-InputChannel通信_第1张图片
核心代码调用

其实这部分还是比较复杂的,下面来捋一下关系:

ViewRootImpl setView的时候binder调用执行WMS的addWindow方法,主要任务是通过openInputChannelPair来创建socket pair,对应一对fd。

注册fd:

socket服务端fd保存到system_server中的WindowState的mInputChannel;
socket客户端fd通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;

两端都通过各自Looper监听了对端的写操作,一旦对端搞事情,马上回调响应。

回调:

服务端:收到消息后回调InputDispatcher.handleReceiveCallback()。
客户端:收到消息后回调NativeInputEventReceiver.handleEvent()。

客户端其实相当比较简单,通过binder拿到socket fd 加到主线程looper中,epoll wait等待服务端Dispatcher给input event。但是客户端这边稍微复杂一点点,有如下几个类需要捋一下关系:

InputChannel:包含了channelName和对应端socket fd的信息,以及包括了发送和接收消息的功能封装。

Connection:描述的是一个连接通道,主要包含:服务端的inputChannel 、outboundQueue以及waitQueue。它属于一个连接之后数据操作的渠道。

mConnectionsByFd 对Connection的统一管理,以服务端fd为key,Connection为value,当Dispatcher线获取到事件,会按目前focusWindow对应的应用程序,从mConnectionsByFd(KeyedVector)中找出Connection中对应的fd,然后把事件放入其中,那么客户端的fd马上就能收到事件。

整个通信流程总结:

1)那么InputDispatcher通过focusWindow的fd获取到对应的connection,将DipatcherEntry放入outboundQueue中,InputDispatcher线程调用InputPublisher的publishKeyEvent向应用主线程发送input事件,然后对应事件从outboundQueue移到waitQueue,这个过程是异步的不用等待。

2)应用主线程收到消息后回调NativeInputEventReceiver.handleEvent()接收到该事件,调用InputConsumer的consumeEvents来处理该事件, 一路执行到ViewRootImpl.deliverInputEvent()方法。

3)应用程序事件分发完成后,则会执行finishInputEvent()方法.再进一步调用InputConsumer::sendFinishedSignal 告知InputDispatcher线程该时事件已处理完成。

4)InputDispatcher线程收到该事件后, 执行InputDispatcher::handleReceiveCallback();最终会调用doDispatchCycleFinishedLockedInterruptible()方法 ,将dispatchEntry事件从等待队列(waitQueue)中移除.

Android Input(五)-InputChannel通信_第2张图片
通信过程

最后再来思考一个问题:为什么system_server与应用程序进程之前的通信选择socket而不是binder呢?看过一个比较好的解释的版本:
Socket可以实现异步的通知,且只需要两个线程参与(Pipe两端各一个),假设系统有N个应用程序,跟输入处理相关的线程数目是 n+1 (1是发送(Input Dispatcher)线程)。然而,如果用Binder实现的话,为了实现异步接收,每个应用程序需要两个线程,一个Binder线程,一个后台处理线程,(不能在Binder线程里处理输入,因为这样太耗时,将会堵塞住发送端的调用线程)。在发送端,同样需要两个线程,一个发送线程,一个接收线程来接收应用的完成通知,所以,N个应用程序需要 2(N+1)个线程。两种都能满足异步通知,但是明显socket需要的线程明显少于binder,因此选择socket更为高效。

自此,两端的连接过程就分析到这。

下一篇文章:
Android Input(六)-ViewRootImpl接收事件

参考:
https://www.cnblogs.com/samchen2009/p/3368158.html
http://gityuan.com/2016/12/24/input-ui/

你可能感兴趣的:(Android Input(五)-InputChannel通信)