mtk输入子系统键盘事件处理流程

MT6572平台来看

一、输入子系统得到事件信息

输入子系统首先由systemserver启动:

inputManager = new InputManagerService(context, wmHandler);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();

在InputManagerService中start方法会通过JNI调用,启动Native层的InputReaderThread,InputDispatcherThread线程,从而开始Input系统的运行。InputReaderThread主要是执行和InputReader相关的内容,主要是从EventHub中读取事件,预处理事件,然会是根据policy来处理此事件,最后发送一个消息到InputDispatcher中通知事件的产生。紧接着InputDispatcher会开始事件的分发,通过InputChannel把事件分发给WindowManager或者应用程序。说以一个事件的流程是从 Eventhub  ---> InputReader  ---> InputDispatcher  ---> InputPublisher  ---> InputChannel  ---> InputConsumer  --->  WindowManager or Application

先看InputManagerService的构造函数,代码如下:

public InputManagerService(Context context, Handler handler) {//这里的handler是WindowManagerService处理消息专用的线程,InputManagerService会把消息发送到这个线程中loop
          this.mContext = context;
          this.mHandler = new InputManagerHandler(handler.getLooper());//而和InputManagerService相关的消息的处理时在这个对象中完成的
  
          mUseDevInputEventForAudioJack =
                  context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
          Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                  + mUseDevInputEventForAudioJack);
          mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());//通过JNI调用来启动native层的input系统,然后把返回值存放在mPtr中
     }
从代码可以看出,InputManagerService的构造是很简单的,只是在最后通过JNI方法初始化了native层的Input系统。接下来我们就看看在native层都做了些什么,代码如下:
  static jint nativeInit(JNIEnv* env, jclass clazz,
          jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
      sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
      if (messageQueue == NULL) {
          jniThrowRuntimeException(env, "MessageQueue is not initialized.");
          return 0;
      }
    //
这里实例化了NativeInputManagerService的一个对象,使用的Java层的MessageQueue的Looper,意味着Java层消息和Native消息是在同一个MessageQueue中的
      NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
             messageQueue->getLooper());
     im->incStrong(0);
     return reinterpret_cast(im);//把新建的NativeInputManager强制转换,返回给Java层
 }

在native层初始化的时候,创建了一个名叫NativeInputMnager的对象,这个对象主要负责和系统的其他模块交互,而且InputReader和InputDispatcher都是只运行在Native层中,如果需要调用Java函数也是通过这个对象进行的,另外他实现了InputReaderPolicyInterface和InputDispatcherPolicyInterface,是一个重要的Policy。NativeInputManager在构造过程中,完成了InputManager在native基本运行组件的创建,比如创建了EventHub对象,它是事件的Android系统的起源地,所有的事件都是它从驱动中读取出来的;还创建了InputReaderThread线程用来执行InputReader的功能;InputDispatcherThread用来执行InputDispatcher的功能;同时也创建了InputManager来管理EventHub,InputReader,InputReaderThread,InputDispatcher,InputDispatcherThread这些Native运行的基本对象。这些对象的创建过程中并没有非常重要的调用,这里略过代码。不过要注意一点的是NativeInputManager是InputReaderPolicyInterface和InputDispatcherPolicyInterface的子类,因此在构造InputReader和InputDispatcher的时候要用到NativieInputManager对象。

  在对象构建完成后,开始执行start方法,让之前创建的这些对象运行起来。start方法通过JNI调用让native层的Input系统运行起来,然后在Java层把自己列入WatchDog的监视范围内。之后定义下自己需要接受的外部通知等。


在NativeInputManager@com_android_server_input_InputManagerService.cpp中会新建一下EventHub及InputManager。并把EventHub作为参数传入InputManager。这个类函数比较简单,只维护了几个相关的对象:

    sp mReader;
    sp mReaderThread;
    sp mDispatcher;
    sp mDispatcherThread;

在InputManager::start 函数中启动了两个进程:InputDispatcher、InputReader

InputReader类是Android系统中重要的部分, 在InputReader创建的时候,这里把InputDispatcher作为参数传递进来,然后以InputDispatcher作为参数构造出了QueuedInputListener对象。所以现在有这么一个关系:InputReader持有一个QueuedInputListener,而QueuedInputListener持有InputDispatcher对象。主要功能就是:
(1) 从EventHub读取事件,这些事件是元事件,即没有经过加工或者仅仅是简单加工的处理的事件;
(2)把这些事件加工处理,生成inputEvent事件,这样封装之后的事件,可以满足Android系统的一些需求;
(3)把这些事件发送到事件监听器,即QueuedInputListener,这个监听器可以把事件传递给InputDispatcher。

void InputReader::loopOnce() {
      int32_t oldGeneration;
      int32_t timeoutMillis;
      bool inputDevicesChanged = false;
      Vector inputDevices;
      ...
      //如果系统刚刚启动,或者有新的设备加入的话,timeoutMillis一般为0,意味着无需等待,可以立即返回;timeoutMillis一般为-1,意味着无限等待
      size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
     { 
         AutoMutex _l(mLock);
         mReaderIsAliveCondition.broadcast();
         if (count) {
             processEventsLocked(mEventBuffer, count);
//开始处理读取出来的元事件
         }
         ...
     } 
     if (inputDevicesChanged) {
         mPolicy->notifyInputDevicesChanged(inputDevices);
     }
     //把QueuedInputListener中的消息全部都开始处理
     mQueuedListener->flush();
 }

继续处理该元事件:

[email protected]
 -> [email protected]   //该函数根据不同的事件类型调用不同的处理函数,如按键事件调用:

    -> KeyboardInputMapper::process  //如果是按键事件,则通用getEventHub()->mapKey 获取scanCode到keycode的映射。
    -> KeyboardInputMapper::processKey 

        在该函数最终生成按键事件并通知道到输入监听器

    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
    getListener()->notifyKey(&args);


mEventHub->[email protected] 函数中读读取驱动中的RawEvent元数据的时候,会初始化对应Device的信息,如按键设备,会初始化按键的键值映射关系等。

Device初始化过程如下:

在接收到事件后有两个函数(scanDevicesLocked、readNotifyLocked)会触发初始化设备函数

openDeviceLocked传入相应的设备路径。通过该路径从内核中获取该设备的一些属性如:

device name.
identifier.bus
identifier.product
identifier.vendor
identifier.version
identifier.location
identifier.uniqueId
identifier.descriptor


->loadConfigurationLocked(device);  先load 该设备的配置文件,   在该函数中根据product、vendor、 version查找属性文件的路径:先system/usr/idc 如不存在,则从 /data/system/devices/idc.中查找。 见函数:--->getInputDeviceConfigurationFilePathByDeviceIdentifier

 

->loadKeyMapLocked  在该函数中直接调用[email protected]函数。 load函数中:如果配置文件中有指定本设备的*.kl 或*.kcm文件则使用该文件。如果传入的deviceIdenfifier文件名存在则用该该件。否则使用Generic.kl。所以MT6572平台最终使作的是Generic.kl 作为键盘映射文件。

  -->[email protected] 如下:

    if (!haveKeyLayout()) {
        loadKeyLayout(deviceIdentifier, keyMapName);
    }
    if (!haveKeyCharacterMap()) {
        loadKeyCharacterMap(deviceIdentifier, keyMapName);
    }

在该函数中将*.kl 或*.kcm 同时load到系统。

键盘的映射在KeyLayoutMap::[email protected]中处理

字符的映射在KeyCharacterMap::[email protected]中处理


 InputReader把事件发送到InputDispatcher

   前面我们已经知道了,InputReader把元事件处理完毕后,构造了一个NotifyArgs,并把这个对象压入了QueuedInputListener的队列中,然后就返回了。

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
         int32_t scanCode, uint32_t policyFlags) {
     ...//省略了对于元事件处理过程的代码,主要就是发生事件,事件代码,扫描码,是按下还是弹起,
     //总之,用于构建下面NotifyKeyArgs的参数大都是在这里获取的。
     NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
             down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
             AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
     getListener()->notifyKey(&args);
 }

void NotifyKeyArgs::notify(const sp& listener) const {
    listener->notifyKey(this);//调用InputDispatcher的对应的方法。
}

到这里,我们对于InputReader的功能的分析就完成了。总结一下,基本过程说就是:InputReader从EventHub中读取出来元事件,预处理加工这些元事件成为NotifyArgs,然后通过QueuedInputListener把他们通知给InputDispatcher


二、事件的分发流程(内容太多了,以下从其它地方拷贝)

在开始介绍InputDispatcher的功能之前,先看看Android文档对于其功能的描述:把输入事件发送到他的目标中去。他的目标可能是应用程序,也可能是WindowManagerService。如果是应用程序的话,可以通过registerInputChannel来定义输入事件的目标。我们已经了解InputDispatcher的唯一一个功能就是分发事件。知道了其功能之后,我们就开始分析InputDispatcher是如何实现这些功能的吧。先看他的构造函数,InputDispatcher创建了一个Looper,代码如下:

 

1  mLooper = new Looper(false);

 

这意味着,InputDispatcher有自己的Looper,没有和别人共用,信息也是自己在循环的。这个Looper是native层的Looper,由C++代码实现。在构建Looper过程中,新建了一个管道,这个管道仅仅起到了唤醒Looper,让其能从阻塞等待中返回。Looper中创建的管道是实现Looper功能的重要的方式,是通用的,不是仅仅为了InputDispatcher准备的。看完构造函数之后,我们接着分析InputDispatcher的功能,接着上节中的QueuedInputListener通知InputDispatcher有新的按键事件说起。这里还是接着上面的以按键的处理,接着看InputDispatcher是如何实现分发的,代码如下:

复制代码
 1 void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
 2     ...
 3     KeyEvent event;//在这里通过传递进来的NotifyArgs为参数,构建KeyEvent
 4     event.initialize(args->deviceId, args->source, args->action,
 5             flags, args->keyCode, args->scanCode, metaState, 0,
 6             args->downTime, args->eventTime);
 7   //通过NativeInputManager把这个KeyEvent最终传递给WindowManagerService去处理
 8     mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
 9     ...
10     bool needWake;
11     ...
12         int32_t repeatCount = 0;//这下面构建KeyEntry
13         KeyEntry* newEntry = new KeyEntry(args->eventTime,
14                 args->deviceId, args->source, policyFlags,
15                 args->action, flags, args->keyCode, args->scanCode,
16                 metaState, repeatCount, args->downTime);
17 
18         needWake = enqueueInboundEventLocked(newEntry);
19         mLock.unlock();
20 
21     if (needWake) {//唤醒等待Looper
22         mLooper->wake();
23     }
24 }
复制代码

我们先从代码中的line 8开始,这行代码的意思是,把KeyEvent发送出去,至于目的地是哪儿,在InputDispatcherPolicy中会有决定。是NativeInputManager实现了这个Policy,所以代码的执行会进入NativeInputManager中。

 事件在入队前(before enqueue)的处理

    在文章的前面已经说到过,NativeInputManager负责和系统的其他模块交互--是其功能之一。把这个KeyEvent传递到NativeInputManager之后,继续分发,最终会把这个KeyEvent传递到PhoneWindowManager中去处理这个事件,传递过程如下:NativeInputManager->interceptKeyBeforeQueueing  ----> InputManagerService.interceptKeyBeforeQueueing ----> InputMonitor.interceptKeyBeforeQueueing ---->  PhoneWindowManager.interceptKeyBeforeQueueing.大致过程是这样的,具体细节不再赘述。在传递过程中是跨线程的。通过这一系列的方法的名字可以看出,是在事件进入InputDispatcher的队列之前,进行的一些处理。在PhoneWindowManager处理事件之后,会有一个返回值来标记这一事件处理的结果是怎样的,为后面的事件进入队列做准备。在PhoneWindowManager对事件进行前期的拦截处理过程时,一般首先把事件都标记上PASS_TO_USER,即这个事件要交给应用程序去处理,但是在处理过程中决定,有些事件是没必要传递给应用程序的,比如:在通过过程中按下音量相关的事件,挂断电话的事件,power键的处理,以及拨打电话的事件。这些事件的处理结果都是不必传递到应用程序的,这个结果最为返回值,最终会一步一步地返回到NativeInputManager中,这个返回值会作为NativeInputManager的policyFlags的一部分,供InputDispatcher使用。在PhoneWindowManager对事件处理完成后,才会把这个事件构造成为一个形式为EventEntry放入队列。到这里,我们的工作仍在InputReaderThread的线程中,虽然是对InputDispatcher的操作。接下来才是真正进入InputDispatcherTread线程对InputDispatcher操作。通过InputDispatcher的mLooper的wake方法,唤醒InputDispatcherThread线程。关于Looper如何在wake时是如何通过管道的方式去实现的,这个过程应该放在一篇单独的文章中详细地去说明,在以后的文章中,我会说到Looper在native实现时的一些特点的。这里,我们知道InputDispatcherThread线程被唤醒了。如果你已忘记InputDispatcherThread线程是何时被阻塞,那就回头再重新看看吧。学习别人的思路就是这样,反复回头看,才能不至于迷失在别人的思维中。然后就开始执行InputDispatcher的threadLoop函数,之后就调用InputDispatcher的dispatchOnce方法,代码如下:

复制代码
 1 void InputDispatcher::dispatchOnce() {
 2     nsecs_t nextWakeupTime = LONG_LONG_MAX;//应该是64位二进制所能表示的最大值,大概是2^63-1,即9223372036854775807
 3     { 
 4         AutoMutex _l(mLock);
 5         mDispatcherIsAliveCondition.broadcast();
 6     //如果没有等待执行的命令的话,就开始一次循环分发。在循环过程中,可能会有一些命令产生。这里的命令大概是模式设计中的:命令模式吧
 7         if (!haveCommandsLocked()) {
 8             dispatchOnceInnerLocked(&nextWakeupTime);
 9         }
10     //如果任何等待执行的命令的话,那么就执行这些命令;假如有命令已经执行了,那么下次poll的时候直接唤醒
11         if (runCommandsLockedInterruptible()) {
12             nextWakeupTime = LONG_LONG_MIN;//#define LONG_LONG_MIN  (-__LONG_LONG_MAX__-1LL), 即-9223372036854775808
13         }
14     } // release lock
15 
16     nsecs_t currentTime = now();
17     int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
18     mLooper->pollOnce(timeoutMillis);
19 }
复制代码

 InputDispatcher的主要功能就在这段代码中,这是个轮廓。要想知道具体的功能的实现,还要需要逐步分析下去。先看line7和line8中的代码,如果是一次正常的分发循环(dispatch loop)的话,应该是没有等待执行的命令。为什么会没有等待执行的命令,在后面会说到原因,先不要着急。所以接下就开始dispatchOnceInnerLocke方法,从这个方法的名字可以看出,这应该是功能的核心实现部分。看其代码是如何实现的:

复制代码
 1 void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
 2     nsecs_t currentTime = now();
 3    //如果等待处理的事件不存在的话
 4     if (! mPendingEvent) {
 5         if (mInboundQueue.isEmpty()) {
 6             ...//省略了,当等待处理事件不存在且事件队列为空的时候的处理
 7         } else {//从事件队列的头部取出一个事件
 8             mPendingEvent = mInboundQueue.dequeueAtHead();
 9             traceInboundQueueLengthLocked();
10         }
11         if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
12           //通知某些Activity一些事件的发生,通过这个方法的名字可以联想一下,一些社交网站中的“捅一下”应用,或者QQ中的震动窗口功能,
13             pokeUserActivityLocked(mPendingEvent);//这个方法的功能就类似于那些作用。只不过这里主要是用来“捅一下”PowerManagerService的
14         }
15 
16         // Get ready to dispatch the event.
17         resetANRTimeoutsLocked();
18     }
19   //现在我们有事件需要开始处理了
20     ALOG_ASSERT(mPendingEvent != NULL);
21     bool done = false;
22     DropReason dropReason = DROP_REASON_NOT_DROPPED;//在开始处理之前,所有的事件都不必丢弃
23     if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
24         dropReason = DROP_REASON_POLICY;
25     } else if (!mDispatchEnabled) {
26         dropReason = DROP_REASON_DISABLED;
27     }
28 
29     if (mNextUnblockedEvent == mPendingEvent) {
30         mNextUnblockedEvent = NULL;
31     }
32 
33     switch (mPendingEvent->type) {
34 ...//省略了对于config change类别的事件的处理
35 ...//省略了对于设备重置事件的处理
36     case EventEntry::TYPE_KEY: {
37         KeyEntry* typedEntry = static_cast(mPendingEvent);
38         if (isAppSwitchDue) {//下面这些内容,是对于事件是否需要丢弃的分析
39             if (isAppSwitchKeyEventLocked(typedEntry)) {
40                 resetPendingAppSwitchLocked(true);
41                 isAppSwitchDue = false;
42             } else if (dropReason == DROP_REASON_NOT_DROPPED) {
43                 dropReason = DROP_REASON_APP_SWITCH;
44             }
45         }
46         if (dropReason == DROP_REASON_NOT_DROPPED
47                 && isStaleEventLocked(currentTime, typedEntry)) {
48             dropReason = DROP_REASON_STALE;
49         }
50         if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
51             dropReason = DROP_REASON_BLOCKED;
52         }//无论事件是否要被丢弃,都要经过如下的处理
53         done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
54         break;
55     }
56     ...//省略了对于motion事件的处理
57     }
58     ...
59 }
复制代码

这个方法中的大部分功能都已经在代码中注释了,主要就是取出事件,分析是否需要丢弃,然后就是开始按照类型分发事件,我们假设的是按键事件,所以接下来就是调用dispatchKeyLocked方法来分发。

复制代码
 1 bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
 2         DropReason* dropReason, nsecs_t* nextWakeupTime) {
 3     if (! entry->dispatchInProgress) {
 4         ...//省略了对于重复事件在各种情况下的处理
 5     }
 6 
 7    ...//在入队列之前,对于事件有个一次intercept,这里是对事件的intercept结果的处理
 8     Vector inputTargets;
 9     int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
10             entry, inputTargets, nextWakeupTime);//寻找事件发送到的目标窗口
11      // 分发事件
12     dispatchEventLocked(currentTime, entry, inputTargets);
13     return true;
14 }
复制代码

这个方法中主要就是寻找到事件应该分发到的目标,可能是应用窗口.这个目标应用的窗口寻找与应用程序启动时设置到窗口有关。在下一小节中会说到这个窗口是如何找到的。其代码不是很复杂,自己看看的话也很容易能够明白。其他的内容在上面的注释中也有说明。下面还是将注意力集中在事件分发上,注意这里传入dispatchEventLocked的参数中inputTargets是复数,也就是说可能有多个目标。所以在方法dispatchEventLocked中就是根据每一个target对应的inputChannel找到connection,然后 prepareDispatchCycleLocked使用这个connection把事件逐个分发到target中。 在prepareDispatchCycleLocked方法中,主要就是根据事件是否可以分割,分别把事件放入队列。在入队列的之后,InputPublisher的发布事件的队列就不再为空,然后会调用 startDispatchCycleLocked方法,通过InputPublisher开始发布事件。大致过程如此,为了减少篇幅,这里就不再列出代码了。流程图如下:

 

整个的流程图太大了,不太方便,这里仅仅是其中的一部分。说明一点:图中Looper到InputDispatcher中的dispatcherOnce不是调用关系,只是Looper把其所在的进程即InputDispatcherThread线程给唤醒,所以开始执行dispatchOnce。这里到最后就是调用InputPublisher的publishKeyEvent方法,把事件发布出去。在前面我们说到过这么一个问题,等待执行的命令为什么在一次正常的事件分发之后应该为空?这些命令产生的地方分别在pokeUserActivity方法中, 和dispatchKeyLocked中等等在使用postCommand把命令放入队列的地方。在上面这个过程执行完毕后,会返回到dispatchOnce方法中,接着往下执行,也就是执行代码:

runCommandsLockedInterruptible()

也就是前面dispatchOnce方法的line 11. 这个方法的功能就是执行之前放入命令队列的命令。具体的代码不再列出。到这里,关于InputDispatcher的功能--唯一的一个功能--事件分发,就算介绍完了。



你可能感兴趣的:(mtk输入子系统键盘事件处理流程)