依照惯例,在研究Android输入系统之前给出输入系统的本质描述: 从哲学的观点来看,输入系统就是解决从哪里来又将到哪里去问题。输入的本质上的工作就是收集用户输入信息并放置到目标位置。

Android在源代码分类上,并没有输入系统分类。本章的输入系统研究是一个综合的分析,前面的GWES的分析,特别是View的Focus Path以及Window Manager Proxy是本章分析的基础。

Android输入系统的组成


输入系统由如下几部分组成:

  1. 1)后台窗口管理服务
  2. 2)Focus Activity
  3. 3)Focus Window
  4. 4)Focus View:用来接收键盘消息

从输入系统这个角度去看Android的Window Manager服务解决了用户信息输入收集,而FocusActvitiy,Focus Window、Focus View这些概念的设计是为了解决用户输入应该放到哪里去这个问题。在整个Android系统中,同时只有一个一个Focus Window,而属于该Window的Focus View才是真正的Focus View。

在Android系统中,在设计上要求多个Actvitiy同时存在运行。在实现中,每次把Actvitiy变成Focused Actvitiy时 (setFocusedActivity@ActivityManagerService.java)激活程序的时候,就把该Activity的主窗口设置成前景窗口,即系统中的顶层窗口,AppToken概念的引进就是为了解决窗口对象的归属问题。在这个过程中,在逻辑上看,我们挑选了一个Activity作为了Focus Activity来接收系统的消息,实质上这个Focus Activity的Focus窗口就是前景窗口。

Focus窗口的改变将改变焦点View,前景窗口的改变也将引起焦点View的变化。焦点和光标的概念用于管理输入设备和输入事件的传送。光标是一个绘制在屏幕之上的小位图,指示当前的输入位置。键盘输入有类似的输入焦点和键盘输入插入符的概念。只有具有输入焦点的窗口才能获取键盘事件。改变窗口的焦点通常由特殊的按键组合或者TouchEvent事件完成。具有输入焦点的窗口通常绘制有一个键盘插入符。该插入符的存在、形式、位置,以及该插入符的控制完全是由窗口的事件处理例程完成的。

现在站在更宏观的位置来看Actvitiy的输入系统,可以从Linux Driver开始到输入框结束的整个链条,我这里给出大输入系统的概念,Android大输入系统包含:Linux driver, Window Manager, Message System, View Focus Path,Focus View。

Android输入系统架构图


现在从Android的代码分析的角度,来看看输入系统的组成。这个过程从代码中分析处理:

在Window Manager Service端

readEvent@com_android_server_KeyInputQueue.cpp

KeyQ@WindowMangerService.java

KeyInputQ@KeyInputeQueue.java

InputDispatcherThread@WindowMangerService.java

在Client端

IWindow@ViewRoot.Java

ViewRoot@ViewRoot.java

KeyInputQ在WindowMangerService中建立一个独立的线程InputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建RawEvent,并放入到KeyQ消息队列中。

InputDispatcherThread从KeyQ中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events专递到Client端。Client端在根据自己的Focus Path传递事件,直到事件被处理。


输入路径的一般原理

按键,鼠标消息从收集到最终将发送到焦点窗口,要经历怎样的路径,是Android GWES设计方案中需要详细考虑的问题。按键,鼠标等用户消息消息的处理可分为不同的情况进行判定:

(1)用户输入根据系统状况是否应该派送。如在ScreenOff的情况下,在按键属于特殊按键的情况下等

(2)是否有拦截Listener

(3)对按键事件来讲,是否存在输入法

(4)是否是焦点终点

(5)是否为焦点切换按相关键

这些情况都是设计输入路径需要考虑的基本条件。

1.1一般的输入路径设计

该输入路径实际上是指的按键消息(MSG_KEYDOWN,MSG_KEYUP, MSG_LongPress)的输入路径,即从活动主窗口到焦点窗口所经历的路程。


将信息输入路径分为两步:

Step 1)窗口管理器将信息发送到活动窗口

Step 2)活动窗口通过缺省处理函数将该消息一层层的传递到焦点。

这样应用程序可以在活动View的处理函数中来预先处理用户输入信息,从而增强应用对用户信息的控制力。

传递路径是通过View的缺省处理函数Onxxx来完成。通过ActiveView ->focus->focus->focus的链条关系,一级一级的将按键消息MSG_KEYDOWN,MSG_KEYUP, MSG_CHAR等传递到focus窗口。


此时用户按键输入先发送到输入法窗口,经过输入法管理器处理,过滤后将输入法产生的结果放置到焦点View。

1.2输入系统整体流程

下面示意图是Android输入系统的数据流途径,通过WM的输入系统线程收集消息,分发到Focus Activity消息队列,然后通过消息系统派发。


1.3 用户数据收集及其初步判定

KeyInputQ在WindowMangerService中建立一个独立的线程InputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建RawEvent,放入到KeyQ消息队列中。


preProcessEvent()@KeyInptQ@KeyInputQueue.java这个是在输入系统中的第一个拦截函数原型。KeyQ重载了preProcessEvent()@WindowManagerService.java。在该成员函数中进行如下动作:

(1) 根据PowerManager获取的Screen on,Screen off状态来判定用户输入的是否WakeUPScreen。

(2) 如果按键式应用程序切换按键,则切换应用程序。

(3) 根据WindowManagerPolicy觉得该用户输入是否投递。

1.4 消息分发第一层面

InputDispatcherThread从KeyQ中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events专递到Client端。

如何将KeyEvent对象传到Client端:

在前面的章节(窗口管理ViewRoot,Window Manager Proxy)我们已经知道:在客户端建立Window Manager Proxy后,添加窗口到Window Manager service时,带了一个跟客户ViewRoot相关的IWindow接口实例过去,记录在WindowState中的mClient成员变量中。通过IWindow这个AIDL接口实例,Service可以访问客户端的信息,IWindow是Service连接View桥梁。


看看在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已经放入到应用程序的消息队列。


1.5 应用消息队列分发


消息的分发,在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中来处理。

1.6 向焦点进发,完成焦点路径的遍历。


分发函数调用栈

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,至此,用户事件传递完成一个段落。

如果事件在上述Focus View没有处理掉,并且为方向键之类的焦点转换相关按键,则转移焦点到下一个View。




更多相关文章

  1. Log分析
  2. Android的消息机制
  3. Android之实例界面设计
  4. Android下 读写文件
  5. 学习Android(安卓)Handler消息传递机制
  6. Andriod数据推送方案
  7. Android开发实例详解之IMF(Android(安卓)SDK Sample—SoftKeyboar
  8. Android聊天室(服务器)
  9. Android之Handle全面理解

随机推荐

  1. Android实际开发bug大总结
  2. android中的滚动条ScrollView
  3. android CursorAdapter
  4. 导入color文件中的颜色值 android
  5. Android之打开和关闭软键盘
  6. 4.8.4 在Android(安卓)中fragment中获取
  7. android http中请求访问添加 cookie
  8. android 常用调用系统功能
  9. android 图片圆角 遮罩_安卓圆角、背景遮
  10. Android运行时动态全屏以及旋转屏幕时不