依照惯例,在研究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输入分析_第1张图片

现在从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窗口。

Android输入分析_第2张图片

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

1.2输入系统整体流程

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

Android输入分析_第3张图片

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

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

Android输入分析_第4张图片

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端。

Android输入分析_第5张图片

如何将KeyEvent对象传到Client端:

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

Android输入分析_第6张图片

看看在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 向焦点进发,完成焦点路径的遍历。

Android输入分析_第7张图片

分发函数调用栈

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. 学习Android Handler消息传递机制
  2. 为Android系统定制重启功能
  3. android recover 系统代码分析 -- 选择进入
  4. Android 文件系统移植总结
  5. Android系统构架简介

随机推荐

  1. android上各个浏览器的内核信息对比
  2. android Java 笔试考题
  3. Android扫描条形码实现
  4. Android官方ORM数据库Room技术解决方案简
  5. Android(安卓)中创建avd和sdcard
  6. Android系统简介
  7. Android(安卓)Handler消息机制从原理到应
  8. 什么是android market?国内三大类android
  9. 分享:Android浏览器,用NDK C++做底层开发
  10. Android入门教程(三)之------导入现有And