您的位置:首页 > 其它

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

2016-02-26 17:32 441 查看
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> 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<jint>(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<InputReaderInterface> mReader;

sp<InputReaderThread> mReaderThread;

sp<InputDispatcherInterface> mDispatcher;

sp<InputDispatcherThread> 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<InputDeviceInfo> 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();

}

继续处理该元事件:
processEventsLocked@InputReader.cpp

-> processEventsForDeviceLocked@InputReader.cpp //该函数根据不同的事件类型调用不同的处理函数,如按键事件调用:
-> 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->getEvents@EventHub.cpp 函数中读读取驱动中的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 在该函数中直接调用KeyMap.load@Keyboard.cpp函数。 load函数中:如果配置文件中有指定本设备的*.kl 或*.kcm文件则使用该文件。如果传入的deviceIdenfifier文件名存在则用该该件。否则使用Generic.kl。所以MT6572平台最终使作的是Generic.kl 作为键盘映射文件。

-->probeKeyMap@Keyboard.cpp 如下:

if (!haveKeyLayout()) {

loadKeyLayout(deviceIdentifier, keyMapName);

}

if (!haveKeyCharacterMap()) {

loadKeyCharacterMap(deviceIdentifier, keyMapName);

}

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

键盘的映射在KeyLayoutMap::load@KeyLayoutMap.cpp中处理

字符的映射在KeyCharacterMap::load@KeyCharacterMap.cpp中处理

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<InputListenerInterface>& 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<KeyEntry*>(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<InputTarget> 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的功能--唯一的一个功能--事件分发,就算介绍完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: