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>& inputChannel, const sp<InputWindowHandle>& 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>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
   sp<Connection> 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> inputChannel; // never null
 ...
   Queue<DispatchEntry> outboundQueue;
...
   Queue<DispatchEntry> waitQueue;

    explicit Connection(const sp<InputChannel>& inputChannel,

            const sp<InputWindowHandle>& 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正式建立连接。

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

核心代码调用

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

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)中移除.

通信过程

最后再来思考一个问题:为什么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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容