企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# Android用户事件输入路径 ### 1 输入路径的一般原理 按键,鼠标消息从收集到最终将发送到焦点窗口,要经历怎样的路径,是Android GWES设计方案中需要详细考虑的问题。按键,鼠标等用户消息消息的处理可分为不同的情况进行判定: (1)用户输入根据系统状况是否应该派送。如在ScreenOff的情况下,在按键属于特殊按键的情况下等 (2)是否有拦截Listener (3)对按键事件来讲,是否存在输入法 (4)是否是焦点终点 (5)是否为焦点切换按相关键 这些情况都是设计输入路径需要考虑的基本条件。 ### 1.1一般的输入路径设计 该输入路径实际上是指的按键消息(MSG_KEYDOWN,MSG_KEYUP, MSG_LongPress)的输入路径,即从活动主窗口到焦点窗口所经历的路程。 [![inputNormal](https://box.kancloud.cn/2016-05-05_572b1a1dd5670.gif "inputNormal")](http://hi.csdn.net/attachment/201005/5/0_1273072314A4ZR.gif) 将信息输入路径分为两步: Step 1)窗口管理器将信息发送到活动窗口 Step 2)活动窗口通过缺省处理函数将该消息一层层的传递到焦点。 这样应用程序可以在活动View的处理函数中来预先处理用户输入信息,从而增强应用对用户信息的控制力。 [![inputxxx1](https://box.kancloud.cn/2016-05-05_572b1a1de9a7e.gif "inputxxx1")](http://hi.csdn.net/attachment/201005/5/0_1273072315h9Yf.gif) 传递路径是通过View的缺省处理函数Onxxx来完成。通过ActiveView ->focus->focus->focus的链条关系,一级一级的将按键消息MSG_KEYDOWN,MSG_KEYUP, MSG_CHAR等传递到focus窗口。 [![inputxxx2](https://box.kancloud.cn/2016-05-05_572b1a1e0add6.gif "inputxxx2")](http://hi.csdn.net/attachment/201005/5/0_1273072316MABo.gif) 此时用户按键输入先发送到输入法窗口,经过输入法管理器处理,过滤后将输入法产生的结果放置到焦点View。 ### 1.3输入系统整体流程 下面示意图是Android输入系统的数据流途径,通过WM的输入系统线程收集消息,分发到Focus Activity消息队列,然后通过消息系统派发。 [![InputSystemPATH](https://box.kancloud.cn/2016-05-05_572b1a1e1dbd1.gif "InputSystemPATH")](http://hi.csdn.net/attachment/201005/5/0_1273072319mmM9.gif) ### 2 Android输入路径详细描述 ### 2.1 第一步:用户数据收集及其初步判定 KeyInputQ在WindowMangerService中建立一个独立的线程InputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建RawEvent,放入到KeyQ消息队列中。 [![inputDeviceReader](https://box.kancloud.cn/2016-05-05_572b1a1e37e4c.gif "inputDeviceReader")](http://hi.csdn.net/attachment/201005/5/0_1273072322wE2w.gif) preProcessEvent()@KeyInptQ@KeyInputQueue.java这个是在输入系统中的第一个拦截函数原型。KeyQ重载了preProcessEvent()@WindowManagerService.java。在该成员函数中进行如下动作: (1) 根据PowerManager获取的Screen on,Screen off状态来判定用户输入的是否WakeUPScreen。 (2) 如果按键式应用程序切换按键,则切换应用程序。 (3) 根据WindowManagerPolicy觉得该用户输入是否投递。 ### 2.2 第二步 消息分发第一层面 InputDispatcherThread从KeyQ中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events专递到Client端。 [![inputDispatcherThread](https://box.kancloud.cn/2016-05-05_572b1a1e48299.gif "inputDispatcherThread")](http://hi.csdn.net/attachment/201005/5/0_12730723237679.gif) 如何将KeyEvent对象传到Client端: 在前面的章节(窗口管理ViewRoot,Window Manager Proxy)我们已经知道:在客户端建立Window Manager Proxy后,添加窗口到Window Manager service时,带了一个跟客户ViewRoot相关的IWindow接口实例过去,记录在WindowState中的mClient成员变量中。通过IWindow这个AIDL接口实例,Service可以访问客户端的信息,IWindow是Service连接View桥梁。 [![IWindow](https://box.kancloud.cn/2016-05-05_572b1a1e5a7e7.gif "IWindow") ](http://hi.csdn.net/attachment/201005/5/0_1273072324aNrZ.gif) 看看在Client ViewRootKeyEvent的分发过程 IWindow:dispatchKey(event) dispatchKey(event)@W@ViewRoot@ViewRoot.java ViewRoot.dispatchKey(event)@ViewRoot.java <message> sendMessageAtTime(msg)@Handler@Handler.java 至此我们通过前面的Looper,Handler详解章节的分析结论,我们可以知道Key Message已经放入到应用程序的消息队列。 ### 2.3第三步:应用消息队列分发 消息的分发,在Looper,Handler详解章节我们分析了Looper.loop()在最后后面调用了handleMesage. … ActivityThread.main() Looper.loop() ViewRoot$RootHandler().dispatch() handleMessage .... 注意到在分发的调用msg.target.dispatch(),而这个target在第二层将消息sendMessageAtTime到消息队列时填入了mag.target=this即为msg.target=ViewRoot实例。所有此时handleMessage就是ViewRoot重载的handleMessage函数。 handlerMessage@ViewRoot@ViewRoot.java deliverkeyEvent 如果输入法存在,dispatchKey到输入法服务。 否则[deliverKeyEventToViewHierarchy@ViewRoot.java](#) 在这里需要强调的是,输入法的KeyEvent的拦截并没有放入到Window Manager Service中,而是放入到了客户端的RootView中来处理。 ### 2.4第四步:向焦点进发,完成焦点路径的遍历。 [](#)[![focuspath1](https://box.kancloud.cn/2016-05-05_572b1a1e704ce.gif "focuspath1") ](http://hi.csdn.net/attachment/201005/5/0_12730723266xTM.gif) 分发函数调用栈 [deliverKeyEventToViewHierarchy@ViewRoot.java](#) mView.dispatchKeyEvent:mView是与ViewRoot相对应的Top-Level View.如果mView是一个ViewGroup则分发消息到他的mFocus。 mView.dispatchKeyEvent @ViewGroup  (ViewRoot@root) Event.dispatch mFocus.dispatchKeyEevnet 如果此时的mFocu还是一个ViewGroup,这回将事件专递到下一层的焦点,直到mFocus为一个View。通过这轮调用,就遍历了焦点Path,至此,用户事件传递完成一个段落。 ### 2.5第五步 缺省处理 如果事件在上述Focus View没有处理掉,并且为方向键之类的焦点转换相关按键,则转移焦点到下一个View。