InputManager - android_2.3

总的流程把握:
在键盘输入事件的WMS 与 APP 的交互中,通过 EventHub 获得事件,通过 InputReader 线程读取事件,通过 InputDispatcher 线程分发事件。通过在 WSM 创建 server 与 client 对应的两个 InputChannel,内部通过一块匿名共享内存。两个 pipe 进行通信。
server 获得事件,将写入focus的Window对应的匿名共享内存,在server 的 pipe 写端口写入一个字符,client 监听到pipe 写入则从匿名共享内存读取事件,在主线程处理该事件。处理完毕后在 client 的pipe 写端口写入一个字符通过 server 事件处理完毕。而后 server 继续分发下一个事件。


ps:Touch 事件的处理在本质上和内置键盘的点击处理都是一样的,都是由 EventHub 产生,InputRead 读取,只是处理的时候Mapper 不同,在 Android-2.3下,内置键盘点击由KeyboardInputMapper处理,touch 事件SingleTouchInputMapper等处理。


Part One:

  • main( )@WSM 会开启一个线程,常规的 Looper 的创建,WSM 服务的实例化。
  • WSM 实例化的过程会实例化一个 InputManager;InputManager 管理键盘输入事件,实例化的时候会调用 nativeInit,实例化一个 C++层的对象 NativeInputManager并且绑定到 Java 层的 InputManager,NativeInputManager 可以认为是真正的 InputManager。
  • NativeInputManager 实例化的时候,会实例化一个 EventHub(C++) 和 InputManager(C++),前者是用来监听键盘事件的产生,后者用来读取键盘事件和分发事件。
  • InputManager(C++)的实例化的时候,初始化InputDispatcher,InputReader(持有 EventHub),前者分发事件,后者读取事件;初始化 InputReaderThread 和 InputDispatcherThread,两者都为独立线程,前者用以读取事件,后者用以分发事件。
  • 到此,初始化完成。然后调用 start( )@InputManager.java
  • 最终到了 native 的start( )@InputManager.cpp
  • 开启 DispatcherThread 和 InputReaderThread。
  • DispatcherThread 执行 InputDispatcher->dispatcherOnce( ),调用 mLooper->pollOnce( );
  • InputReaderThread 执行 InputReader->loopOnce( ),执行 getEvent( & rawEvent)@EventHub 获取键盘事件封装到 RawEvent,随后执行 process(& rawEvent)

Part Two:

  • 当 Activity 创建界面的时候,会执行到 setView( )@ViewRoot 与 WSM 通信。
  • addWindow( )@WSM 的时候创建两个 InputChannel。(内部使用 Pipe 进行进程间通信,Client 和 Server 需要互相通信,因此在 native 创建了两个交叉的 pipe)。
  • openInputChannelPair( )@InputChannel 创建两个 InputChannel。openInputChannelPair( )@InputChannel.cpp[InputTransport.cpp]。首先创建一块匿名共享内存,然后创建两个 Pipe forward[2]和 reverse[2],
    forward作为 Server向 Client 通信的 pipe,reverse 作为 Client 向 Server 通信的 pipe。同时两个 InputChannel 持有这个匿名共享内存的文件描述符。
  • Server 端的 InputChannel 绑定在该 ViewRoot 对应的WinState上,Client 端的 InputChannel 通过 Binder 通信传递给 Application 端的 ViewRoot。
  • WSM 将 server 端的 InputChannel 注册到 InputManager。内部是将该 InputChannel 注册到 InputDispatcher。
  • InputDispatcher 将 Server端的 InputChannel 封装为一个 InputDispatcher::Connection,后者通过 InputChannel 和对应的匿名内存的文件描述符 ashmenFd 构造一个InputPublisher 对象保存在 Connection 中。随后 InputDispatcherThread 线程的 mLooper 监听 Server 端的 InputChannel 的 receiveFd,即 pipe 的读。如此当 Client 端的 InputChannel 像该 pipe 写入时,mLooper 就用handleReceiveCallback(int receiveFd, int events, void* data)@InputDispatcher[InputDispatcher.cpp]去执行该回调。
  • WSM 注册 Server 端 InputChannel 结束。

Part Three:

  • ViewRoot 得到 Client端的 InputChannel 后,注册。registerInputChannel( )InputQueue。
  • registerInputChannel( )@NativeInputQueue,参数有 Client 端的 InputChannel, ViewRoot 处理消息的 mHandler,应用主线程 Looper。
  • Client 端在注册时,将 Client 端的 InputChannel 封装为NativeInputQueue::Connection
  • NativeInputQueue::Connection 内部持有该 Client 端对应的 InputChannel,持有主线程 looper 对应的 native 层的 looper,构造并且保存一个 InputConsumer,InputConsumer 持有 Client 端的 InputChannel,对应的匿名共享内存的文件描述符。
  • 以该文件描述符为 key,Connection 对象为 value 保存到 mConnectionByReceiveFd。
  • 随后主线程 looper 监听 Client 端的 receiveFd,这样当 Server 端的 InputChannel 像管道写入时,会调用handleReceiveCallback( int receiveFd, int events, void* data)@NativeInputQueue[android_view_InputQueue.cpp] 处理消息。

  • 至此,所有的准备工作都做好了,Client 端的 InputChannel 和 Server 端的 InputChannel通过一块相同的匿名共享内存的文件描述符和两个交叉管道产生了联系。Client 的 InputChannel 的 receiveFd 在 Application 的主线程 looper 进行监听。Server 的 InputChannel 的 receiveFd 在 InputDispatcherThread 的线程 looper 进行监听。双方都有handleReceiveCallback(int receiveFd, int events, void* data)对 pipe 监听进行回调

Part Four:

  • 前面说到 InputReaderThread 执行了 mRead->loopOnce( )获取EventHub 产生的事件;mEventHub->getEvent(& rawEvent);
  • EventHub会先 openPlatformInput( )打开输入设备,其中内置键盘的 ID为 0,其他设备根据打开先后顺序分配 ID。每一个打开的设备用 device_t 结构描述。
  • 在 EventHub 中,产生的事件封装为 input_event 结构体保存在 mInputBufferData 中。其中 input_event-id 表示产生该事件的 device。目前只考虑 为0 的情况。如果事件都处理完毕,则会调用poll( )监听事件生成。
  • 现在获得了一个内置键盘事件封装为 RawEvent 事件(一次只处理一个事件);随后 process( )@InputReader.cpp 处理事件。该方法会根据不同的 type 使用不同的处理函数。这里因为产生的是内置键盘事件,于是使用 consumeEvent(rawEvent)处理事件。通过 产生事件的 deviceId 找到处理的 InputDevice。
  • 因为事件可能是复合的,因此一个事件可能会有多个对象处理。这里只考虑内置键盘KeyboardInputMapper
  • process( )@KeyboardInputMapper 只处理 EV_KEY事件。内部优化该事件后通过 notifyKey( )@getDispatcher 通过 InputDispatcher分发此次事件。
  • 于是事件从 InputReader 转到了 InputDispatcher。
  • 在 notifyKey( )@InputDispatcher 中,在转发事件前,会调用 interceptKeyBeforeQueueing( )来执行事件转发前的拦截操作。比如点亮屏幕等。随后将事件封装为 KeyEntry,通过 enqueueInboundEventLocked( )将 KeyEntry 插入到转发队列 mInboundQueue 的队尾。同时判断是否要唤醒 looper。如果队列是空的,现在插入后需要唤醒去转发。如果队列不是空的,则插入到队尾即可。无须唤醒。
  • native 层的 thread 会在一个 do-while 中执行 threadLoop( ),[Theads.cpp]。于是对于 InputDispatcherThread,会不断执行 dispatchOnce( )@InputDispatcher。
  • 在 InputDispatcher 中,会通过 dispatchOnceInnerLocked( )处理事件或者 looper->pollOnce( )获得事件或者休眠。dispatchOnceInnerLocked( )会将事件分发给正激活的窗口。
  • 在分发事件的时候,比如我一直按着 A 不动,虽然没有主动产生事件,也是需要不断尝试 A 这个事件的。
  • 取出事件后,因为讨论的世界是 TYPE_KEY,于是会调用 dispatchKeyLocked( )。前面说到正在激活的 Window 会保存在 mFocusedWindow 中,在 dispatchKeyLocked( )中调用
    findFocusedWindowTargetsLocked( )将正在激活的 Window 和 Application 封装进入InputTarget并且保存到 mCurrentInputTragets 中。
  • 随后通过dispatchEventToCurrentInputTargetsLocked( )取出封装了 Window 和 Application 的 InputTraget。
  • 前面提到过,在注册 Server 端的 InputChannel 的时候,会以 InputChannel 为 key, InputDispatcher::Connection保存在 mConnectionByReceiveFd中,同时这个 InputChannel 是保存在 Window 中的。于是便取出了该 Window 关联的 InputChannel 对应的 InputDispatch::Connection 了。后面通过 prepareDispatchCycleLocked( )进行转发。
  • 在 InputDispatcher::Connection 中有一个队列 Queue。如果该队列还有事件未处理,则先处理改事件。事件在 Queue 中封装为 DispatchEntry。
  • 假设当前 Queue 为空,则先通过 mAllocator 将该事件封装为 DispatchEntry,然后插入到Connection中队列 Queue 的队尾,然后调用startDispatchCycleLocked( ),从队列取出事件,将其封装为 KeyEntry;随后通过 connection->inputPublisher.publishKeyEvent( )将KeyEntry描述为 ShareMessage 的结构写入到匿名共享内存中。通过 connection->inputPublisher.sendDispatchSignal( )真正分发事件。
  • 在 inputPublisher.sendDispatchSignal( )只是简单的调用了sendSignal( INPUT_SIGNAL_DISPATCH)@InputChannel,该方法向 Server 端的 InputChannel 的 mSendPipeFd 写入了一个字符。

Part Five:

  • 前面说到,在 Application 的主线程在监听这个 Server 端的 InputChannel 向 Client 发送数据的 pipe 的读端口。于是Client 端的handleReceiveCallback(int receiveFd, int events, void* data )@NativeInputQueue 边产生了回调。
  • 通过 receiveFd 获得了同一条 pipe 在 Client 对应的 Connection 对象。通过 Connection->inputConsumer.receiverDispatchSignal()确定本次事件是INPUT_SIGNAL_DISPATCH。随后 connection->inputConsumer.consume( )从匿名共享内存读取事件,封装到InputEvent 中。随后通过 generateFinishedToken( )根据 receiveFd, connection->id, connection->messageSquNum 生成一个 Token,在处理完事件后需要根据改 Token 告知 Server 事件处理结果。
  • 通过 Jni 调用dispatchKeyEvent(InputHandler, KeyEvent, long)@InputQueue。其中 InputHandle 是 ViewRoot中的 InputHandler,KeyEvent 是通过android_view_MotionEvent_fromNative( )@android_view_MotionEvent.cpp 将InputEvent转换为 MotionEvent(派生自 KeyEvent)。
  • 随后调用 inputHandler.handleKey(event, finishedCallback);finishedCallback 为 FinishedCallback.obtain(finishedToken);
  • 随后事件就到了 ViewRoot 中的 handleKey( )@InputHandler。保存 finishedCallback 后,将事件封装为一个 Message(DISPATCH_KEY),ViewRoot 派生自 Handle,直接自己处理。如果当前激活的窗口支持输入法处理,则会交给输入法 InputMethodManager 处理。否则交给deliverKeyEventToViewHierarchy()处理。后者会交给 dispatchKeyEvent( event)@mView 处理。前面知道这个 mView 就是 Activity 的 PhoneWindow$DecorView。在 PhoneWindow 初始化的时候,Activity(实现了 Window.callback) 在 attach 的时候会将自己作为 callback 传入PhoneWindow作为 Callback,于是会调用dispatchKeyEvent(event)@Activity。
  • 在 Activity 中,会首先调用 onUserInteraction() 表示有了交互。之后将 event 交给 superDispatchKeyEvent()@DecorView,交给了 super.dispatchKeyEvent。于是就从 DecorView(FrameLayout)开始了View 的事件分发
  • 处理完之后,finishInputEvent( )@ViewRoot将会调用。前面提到 finishCallback 是通过 InputQueue$FinishedCallback.obtain( )生成的。内部run()调用nativeFinished(mFinishToken);finish( )@NativeInputQueue
  • 通过 mFinishToken 拆解出 receiveFd, connectionId, messageSeqNum,找到对应的 Connection,调用 connection->inputConsumer.sendFinishedSignal( )在 InputChannel pipe写端口写入INPUT_SIGNAL_FINISHED。随后handleReceiveCallback()@InputDispatcher 发起回调开启下一个事件的发送

总结:在键盘输入事件的WMS 与 APP 的交互中,通过 EventHub 获得事件,通过 InputReader 线程读取事件,通过 InputDispatcher 线程分发事件。通过在 WSM 创建 server 与 client 对应的两个 InputChannel,内部通过一块匿名共享内存。两个 pipe 进行通信。
server 获得事件,将写入focus的Window对应的匿名共享内存,在server 的 pipe 写端口写入一个字符,client 监听到pipe 写入则从匿名共享内存读取事件,在主线程处理该事件。处理完毕后在 client 的pipe 写端口写入一个字符通过 server 事件处理完毕。而后 server 继续分发下一个事件。


注销也很简单,跟着 activity 的 finish 走下去,会发现注销的最关键的是 app 的主线程 mLoop 取消对于对应的 ViewRoot 的InputChannel的reveiveFd 的监听(Client)。WMS端的 InputDispatcherThread 的 mLoop 取消了对应的Window 的 InputChannel的 reveiveFd 的监听。

你可能感兴趣的:(InputManager - android_2.3)