Android的窗口机制分析-事件处理

本文深入剖析了Android的事件处理机制,包括事件的传递过程、不同类型的输入映射器(InputMapper)及其工作原理,以及InputDispatcher如何管理和调度事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android的窗口机制分析-事件处理

[日期:2011-11-21]来源:Linux社区  作者:windskier

2. 事件传递

    经过分析事件处理系统的初始化过程之后,我们已经对事件处理系统的整体架构有了一定程度的理解,那么下面的事件传递过程就会显得很easy了。

    2.1 InputReaderThread线程操作

     当input系统有事件发生时,会被InputReaderThread线程轮询到,InputReader会根据事件的device id来选择的InputDevice,然后再根据事件的类型来选择InputDevice中的InputMapper,InputMapper会将事件信息通知给InputDispatcher;

    目前adroid在InputReader中实现了5种设备类型的InputMapper,分别为滑盖/翻盖SwitchInputMapper、键盘KeyboardInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单点触屏SingleTouchInputMapper。

设备类型

InputManager

EventType

Notify InputDispatcher

滑盖/翻盖

SwitchInputMapper

EV_SW

notifySwitch()

键盘

KeyboardInputMapper

EV_KEY

notifyKey()

轨迹球

TrackballInputMapper

EV_KEY, EV_REL,

EV_SYN

notifyMotion()

单点触屏

SingleTouchInputMapper

EV_KEY, EV_ABS,

EV_SYN

notifyMotion()

多点触屏

MultiTouchInputMapper

EV_ABS,

EV_SYN

notifyMotion()

    其中EV_REL为事件相对坐标,EV_ABS为绝对坐标,EV_SYN表示Motion的一系列动作结束。

    Notify InputDispatcher表示不同的事件通知InputDispatcher的函数调用,这几个函数虽然是被InputReaderThread调用的,单却是在InputDispatcher定义的。

    

    2.1.1 notifySwitch()

  1. void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,  
  2.         uint32_t policyFlags) {  
  3. #if DEBUG_INBOUND_EVENT_DETAILS  
  4.     LOGD("notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x",  
  5.             switchCode, switchValue, policyFlags);  
  6. #endif   
  7.   
  8.     policyFlags |= POLICY_FLAG_TRUSTED;  
  9.     mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);  
  10. }  
    Switch事件的处理是比较简单的,这是一个与Activity无关的事件,因此我们根本不需要将其dispatch到ViewRoot,所以在notifySwitch()方法中直接通知给PhoneWindowManager去处理即可。从上面的类图中我们其实可以发现mPolicy指向的就是NativeInputManager,
  1. void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,  
  2.         int32_t switchValue, uint32_t policyFlags) {  
  3. #if DEBUG_INPUT_DISPATCHER_POLICY  
  4.     LOGD("notifySwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",  
  5.             when, switchCode, switchValue, policyFlags);  
  6. #endif   
  7.   
  8.     JNIEnv* env = jniEnv();  
  9.   
  10.     switch (switchCode) {  
  11.     case SW_LID:  
  12.         env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged,  
  13.                 when, switchValue == 0);  
  14.         checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged");  
  15.         break;  
  16.     }  
  17. }  
    NativeInputManager的notifySwitch()最终会调用到notifySwitch()@PhoneWindowManager.java

    2.1.2 notifyKey()

  1. void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,  
  2.         uint32_t policyFlags, int32_t action, int32_t flags,  
  3.         int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {  
  4. #if DEBUG_INBOUND_EVENT_DETAILS  
  5.     LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "  
  6.             "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",  
  7.             eventTime, deviceId, source, policyFlags, action, flags,  
  8.             keyCode, scanCode, metaState, downTime);  
  9. #endif  
  10.     if (! validateKeyEvent(action)) {  
  11.         return;  
  12.     }  
  13.   
  14.     policyFlags |= POLICY_FLAG_TRUSTED;  
  15.     mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,  
  16.             keyCode, scanCode, /*byref*/ policyFlags);  
  17.   
  18.     bool needWake;  
  19.     { // acquire lock  
  20.         AutoMutex _l(mLock);  
  21.   
  22.         int32_t repeatCount = 0;  
  23.         KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,  
  24.                 deviceId, source, policyFlags, action, flags, keyCode, scanCode,  
  25.                 metaState, repeatCount, downTime);  
  26.   
  27.         needWake = enqueueInboundEventLocked(newEntry);  
  28.     } // release lock  
  29.   
  30.     if (needWake) {  
  31.         mLooper->wake();  
  32.     }  
  33. }  
    InputDispatcher对KeyBoard事件的处理如上述代码所述,
    首先,InputDispatcher会截取这个按键事件,根据当前设备的状况来优先消化这个事件,这个过程当然是在将事件dispatch给ViewRoot之前。同样的就像notifySwitch()一样,最终该过程交由interceptKeyBeforeQueueing()@PhoneWindowManager.java来处理。interceptKeyBeforeQueueing()主要是对一些特殊案件的特殊处理,并判断该按键是够应该传递给ViewRoot。通过设置标志位policyFlags的值来判断是否给ViewRoot,例如policyFlags&POLICY_FLAG_PASS_TO_USER == 1 则应该传递给ViewRoot。
    interceptKeyBeforeQueueing()特殊处理主要是针对在锁屏或者屏幕不亮的情况的下收到特殊的键值,如音量键或者wake键。wake键是指能够点亮屏幕的键时的操作。
    其次,InputDispatcher再将该按键信息存储在一个队列中( enqueueInboundEventLocked()@InputDispatcher.cpp)。    
 
  1. Queue<EventEntry> mInboundQueue;  

    2.1.3 notifyMotion()

  1. mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);  
    首先,同样的,InputDispatcher会截取这个motion事件,不同的是motion事件的截取处理NativeInputManager完全有能力处理,所以并没有交给PhoneWindowManager来处理。查看代码interceptGenericBeforeQueueing()@com_ Android_server_InputManager.cpp.
    其次,InputDispatcher再将该motion事件信息存储在 mInboundQueue队列中( enqueueInboundEventLocked()@InputDispatcher.cpp)。

    2.2 InputDispatcherThread线程操作

    InputDispatcherThread线程的轮询过程dispatchOnce()-->dispatchOnceInnerLocked(), InputDispatcherThread线程不停的执行该操作,以达到轮询的目的,我们的研究重点也就放在这2个函数处理上。

    2.2.1 InputDispatcherThread基本流程

    InputDispatcherThread的主要操作是分两块同时进行的,

    一部分是对InputReader传递过来的事件进行dispatch前处理,比如确定focus window,特殊按键处理如HOME/ENDCALL等,在预处理完成 后,InputDispatcher会将事件存储到对应的focus window的outBoundQueue,这个outBoundQueue队列是InputDispatcher::Connection的成员函数,因此它是和ViewRoot相关的。

    一部分是对looper的轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件,如果NativeInputQueue处理完成事件,它就会向通过管道向InputDispatcher发送消息指示consume完成,只有NativeInputQueue consume完成一个事件,InputDispatcher才会向共享内存写入另一个事件。

    


    2.2.3 丢弃事件

    并不是所有的InputReader发送来的事件我们都需要传递给应用,比如上节讲到的翻盖/滑盖事件,除此之外的按键,触屏,轨迹球(后两者统一按motion事件处理),也会有部分的事件被丢弃,InputDispatcher总会根据一些规则来丢弃掉一部分事件,我们来分析以下哪些情况下我们需要丢弃掉部分事件?

    InputDispatcher.h中定义了一个包含有丢弃原因的枚举:

  1. enum DropReason {  
  2.     DROP_REASON_NOT_DROPPED = 0,  
  3.     DROP_REASON_POLICY = 1,  
  4.     DROP_REASON_APP_SWITCH = 2,  
  5.     DROP_REASON_DISABLED = 3,  
  6. };  
    1. DROP_REASON_NOT_DROPPED

     不需要丢弃

    2. DROP_REASON_POLICY

   设置为DROP_REASON_POLICY主要有两种情形:

    A. 在InputReader notify InputDispatcher之前,Policy会判断不需要传递给应用的事件。如上一节所述。

    B. 在InputDispatcher dispatch事件前,PhoneWindowManager使用方法interceptKeyBeforeDispatching()提前consume掉一些按键事件,如上面的流程图所示。

    interceptKeyBeforeDispatching()主要对HOME/MENU/SEARCH按键的特殊处理,如果此时能被consume掉,那么在InputDispatcher 中将被丢弃。

    3.DROP_REASON_APP_SWITCH

    当有App switch 按键如HOME/ENDCALL按键发生时,当InputReader向InputDispatcher 传递app switch按键时,会设置一个APP_SWITCH_TIMEOUT 0.5S的超时时间,当0.5s超时时,InputDispatcher 尚未dispatch到这个app switch按键时,InputDispatcher 将会丢弃掉mInboundQueue中所有处在app switch按键前的按键事件。这么做的目的是保证app switch按键能够确保被处理。此时被丢弃掉的按键会被置为DROP_REASON_APP_SWITCH。

    4. DROP_REASON_DISABLED

    这个标志表示当前的InputDispatcher 被disable掉了,不能dispatch任何事件,比如当系统休眠时或者正在关机时会用到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值