在android系统中,键盘按键事件是由SystemServer服务来管理的;然后在以消息的形式分发给应用程序处理。产生键盘按键事件则是有Linux kernel的相关驱动来实现。

键盘消息有别于其他类型的消息;需要从Linux kernel drivers产生由上层app来处理。同时按键有着不同的映射值,因此从模块独立性角度各个独立的模块应该拥有不同的键盘映射。这样以来,kernel产生的按键事件必然回经过不同的映射才到app。



1
kernel中同按键相关代码

Android 使用标准的 linux 输入事件设备(/dev/input/)和驱动按键定义在 linux 内核include/linux/input.h 中,按键的定义形式如下(仅以BACK HOME MENU为例):

有了按键的定义,就需要产生相应的按键事件了。在kernel/arch/arm/mach-msm/xxx/xxx/xxx.c会对BACK HOME和MENU进行注册。这里使用在屏幕上的坐标来对按键进行区分。这部分代码会在系统启动的时候,将相应的数据存储,以供framework查询。

(这里以xxx代替,是因为针对不同的硬件,需要的Linux kernel不同)

当然从核心板原理图到kernel是属于驱动范畴,不讨论。

2framework针对键盘事件的处理

上层对输入事件的侦听和分发是在InputManagerService 中实现

首先来看看InputManagerService的创建,

Step 1

在SystemServer.java

点击(此处)折叠或打开

  1. class ServerThread extends Thread {
  2. //省略。。
  3. public void run() {
  4. // Create a handler thread just for the window manager to enjoy.
  5. HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
  6. wmHandlerThread.start();
  7. Handler wmHandler = new Handler(wmHandlerThread.getLooper());
  8. //此处省略5k字。。
  9. Slog.i(TAG, "Input Manager");
  10. inputManager = new InputManagerService(context, wmHandler);
  11. }
  12. }

可以看到,在系统启动的时候,会首先创建一个系统级别的Handler线程wmHandlerThread用于处理键盘消息(仅说明键盘消息)。然后在创建输入管理服务inputManager,InputManagerService 的第二个参数就是用于处理按键消息的Handler。

Step 2

在往下走到 InputManagerService.java的构造函数。

点击(此处)折叠或打开

  1. public InputManagerService(Context context, Handler handler) {
  2. this.mContext = context;
  3. this.mHandler = new InputManagerHandler(handler.getLooper());
  4. mUseDevInputEventForAudioJack =
  5. context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
  6. Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
  7. + mUseDevInputEventForAudioJack);
  8. mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
  9. }

这里做了重要的两件事情,第一:将SystemServer级别的Handler赋值给 InputManagerService自己的消息处理Handler;第二:调用nativeInit继续进行初始化。

Step 3

com_android_server_ InputManagerService.cpp

点击(此处)折叠或打开

  1. static jint nativeInit(JNIEnv* env, jclass clazz,
  2. jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
  3. sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
  4. NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
  5. messageQueue->getLooper());
  6. im->incStrong(serviceObj);
  7. return reinterpret_cast<jint>(im);
  8. }

这里nativeInit直接调用了 NativeInputManager的构造函数

Step 4

点击(此处)折叠或打开

  1. NativeInputManager::NativeInputManager(jobject contextObj,
  2. jobject serviceObj, const sp<Looper>& looper) :
  3. mLooper(looper) {
  4. JNIEnv* env = jniEnv();
  5. mContextObj = env->NewGlobalRef(contextObj);
  6. mServiceObj = env->NewGlobalRef(serviceObj);
  7. {
  8. AutoMutex _l(mLock);
  9. mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
  10. mLocked.pointerSpeed = 0;
  11. mLocked.pointerGesturesEnabled = true;
  12. mLocked.showTouches = false;
  13. }
  14. sp<EventHub> eventHub = new EventHub();
  15. mInputManager = new InputManager(eventHub, this, this);
  16. }

这里需要特别注意最后两行代码。第一:创建了 EventHub;第二:创建 InputManager并将 EventHub作为参数传入InputManager。

Step 5

接下来继续看看InputManager的构造函数。

点击(此处)折叠或打开

  1. InputManager::InputManager(
  2. const sp<EventHubInterface>& eventHub,
  3. const sp<InputReaderPolicyInterface>& readerPolicy,
  4. const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
  5. mDispatcher = new InputDispatcher(dispatcherPolicy);
  6. mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
  7. initialize();
  8. }
  9. void InputManager::initialize() {
  10. mReaderThread = new InputReaderThread(mReader);
  11. mDispatcherThread = new InputDispatcherThread(mDispatcher);
  12. }

创建了InputDispatcher 和InputReader ,并调用了initialize函数创建了InputReaderThread和InputDispatcherThread。InputDispatcher类是负责把键盘消息分发给当前激活的Activity窗口的,而InputReader类则是通过 EventHub类来实现读取键盘事件的,InputReader实列mReader就是通过这里的 InputReaderThread线程实列mReaderThread来读取键盘事件的,而InputDispatcher实例mDispatcher 则是通过这里的InputDispatcherThread线程实例mDisptacherThread来分发键盘消息的。

到这里,相关的组件都已经被创建了;

Step 6

接下来看看他们是如何运行起来的。

在systemServer.java中创建inputManager之后。将InputManagerServer进行注册,并运行start()

点击(此处)折叠或打开

  1. ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
  2. inputManager.start();
  3. //InputManager的start函数:
  4. public void start() {
  5. Slog.i(TAG, "Starting input manager");
  6. nativeStart(mPtr);
  7. //省略。。
  8. }

调用nativeStart继续往下走。顺带说一下,这里的参数mPtr是指向native inputmanager service对象的,在InputManagerService构造函数中由nativeInit赋值。

Step 7

接下来又到了com_android_server_ InputManagerService.cpp中。

点击(此处)折叠或打开

  1. static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {
  2. NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
  3. status_t result = im->getInputManager()->start();
  4. if (result) {
  5. jniThrowRuntimeException(env, "Input manager could not be started.");
  6. }
  7. }

这里的im就是inputManager并且用到了上面传下来的mPtr来重新构建。

Step 8

继续往下则会调用到InputManager.cpp 的start函数

点击(此处)折叠或打开

  1. status_t InputManager::start() {
  2. status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
  3. if (result) {
  4. ALOGE("Could not start InputDispatcher thread due to error %d.", result);
  5. return result;
  6. }
  7. result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
  8. if (result) {
  9. ALOGE("Could not start InputReader thread due to error %d.", result);
  10. mDispatcherThread->requestExit();
  11. return result;
  12. }
  13. return OK;
  14. }

这个函数主要就是分别启动一个InputDispatcherThread线程和一个InputReaderThread线程来读取和分发键 盘消息的了。这里的InputDispatcherThread线程对象mDispatcherThread和InputReaderThread线程对 象是在前面的Step 9中创建的,调用了它们的run函数后,就会进入到它们的threadLoop函数中去,只要threadLoop函数返回true,函数 threadLoop就会一直被循环调用,于是这两个线程就起到了不断地读取和分发键盘消息的作用。

Step 9

在下来继续看loopOnce()这个函数。

点击(此处)折叠或打开

  1. void InputReader::loopOnce() {
  2. //......
  3. size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
  4. //......
  5. if (count) {
  6. processEventsLocked(mEventBuffer, count);
  7. }
  8. //......
  9. // Flush queued events out to the listener.
  10. // This must happen outside of the lock because the listener could potentially call
  11. // back into the InputReader's methods, such as getScanCodeState, or become blocked
  12. // on another thread similarly waiting to acquire the InputReader lock thereby
  13. // resulting in a deadlock. This situation is actually quite plausible because the
  14. // listener is actually the input dispatcher, which calls into the window manager,
  15. // which occasionally calls into the input reader.
  16. mQueuedListener->flush();
  17. }

这里面需要注意像神一样的函数 mEventHub->getEvents()。其实现原理,还有点不是很清楚;但是其功能就是负责键盘消息的读取工作,如果当前有键盘事件发生或者有键盘事件等待处理,通过mEventHub的 getEvent函数就可以得到这个事件,然后交给processEventsLocked 函数进行处理。同样需要特别注意最后一行;后面回解释。我们还会回来的~~~

点击(此处)折叠或打开

  1. /*
  2. * Wait for events to become available and returns them.
  3. * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
  4. * This ensures that the device will not go to sleep while the event is being processed.
  5. * If the device needs to remain awake longer than that, then the caller is responsible
  6. * for taking care of it (say, by poking the power manager user activity timer).
  7. *
  8. * The timeout is advisory only. If the device is asleep, it will not wake just to
  9. * service the timeout.
  10. *
  11. * Returns the number of events obtained, or 0 if the timeout expired.
  12. */
  13. virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize)

函数原型!

在成功获取input Event之后,就会用到 processEventsLocked函数来处理Event

然后在调用到 processEventsForDeviceLocked(deviceId, rawEvent, batchSize);

最后在void InputDevice::process(const RawEvent* rawEvents, size_t count)

我就在想:问什么不直接到process函数呢?其实我觉得这里体现了设计模式中的单一职责原则;这种设计可以有效的控制函数粒度(有个类粒度,这里自创函数粒度)的大小,函数承担的职责越多其复用的可能性就越小,并且当期中某一个职责发生变化,可能会影响其他职责的运作!

Step 10

接下来继续看 InputDevice::process函数。

点击(此处)折叠或打开

  1. void InputDevice::process(const RawEvent* rawEvents, size_t count) {
  2. //。。。。
  3. InputMapper* mapper = mMappers[i];
  4. mapper->process(rawEvent);
  5. }

走到这里才算是真真正正的知道了有按键发生了,调用 KeyboardInputMapper::process(const RawEvent*)处理input event; KeyboardInputMapper 继承自 InputMapper。那为什么调用的是KeyboardInputMapper而不是SwitchInputMapper等等。。

请留意

点击(此处)折叠或打开

  1. InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
  2. const InputDeviceIdentifier& identifier, uint32_t classes)

函数中的片段:

点击(此处)折叠或打开

  1. if (keyboardSource != 0) {
  2. device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
  3. }

这里Event Type有必要提一下,以下是一些常用的Event。在kernel/Documentation/input/event-codes.txt中有详细的描述。

* EV_SYN:

- Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.

* EV_KEY:

- Used to describe state changes of keyboards, buttons, or other key-like devices.

* EV_REL:

- Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.

* EV_ABS:

- Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.

* EV_MSC:

- Used to describe miscellaneous input data that do not fit into other types.

* EV_SW:

-           Used to describe binary state input switches.

Step 11

点击(此处)折叠或打开


  1. void KeyboardInputMapper::process(const RawEvent* rawEvent) {
  2. switch (rawEvent->type) {
  3. case EV_KEY: {
  4. int32_t scanCode = rawEvent->code;
  5. int32_t usageCode = mCurrentHidUsage;
  6. mCurrentHidUsage = 0;
  7. if (isKeyboardOrGamepadKey(scanCode)) {
  8. int32_t keyCode;
  9. uint32_t flags;
  10. if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
  11. keyCode = AKEYCODE_UNKNOWN;
  12. flags = 0;
  13. }
  14. processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
  15. }
  16. break;
  17. }
  18. }
  19. }

在这里,先判断isKeyboardOrGamepadKey(scanCode),然后在用getEventHub()->mapKey()检测 提供的key是否正确,在然后就开始处理了processKey

Step 12

点击(此处)折叠或打开

  1. void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
  2. int32_t scanCode, uint32_t policyFlags) {
  3. //忽略到所有的。。只看最后两行。。
  4. NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
  5. down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
  6. AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
  7. getListener()->notifyKey(&args);
  8. }

不用多解释了,直接notifyKey了。。但需要注意,这里的notifyKey 仅仅是 NotifyKeyArgs  push到消息队列中去;并没有通知上层!那到底在那儿通知的呢?

 

还记不记得在void InputReader::loopOnce()这个函数的最后一行代码,其实质是在这个函数中通知上层有按键事件发生。

这个flush()很明显,notify了之后,就delete,不存在了。问什么不是在getListener()->notifyKey(&args);的时候就真正的notify?我觉得可以做如下角度予以考虑:


第一:线程是最小的执行单位;因此每当inputThread.start()的时候,如果不flush,回造成数据混乱。

第二:flush操作是必须的,同时在loopOnce的最后操作也是最恰当的。其实这里的Listener也就是充当了一个事件分发者的角色。

这说明,到这里已经完全识别了按键了,并按照自己的键盘映射映射了一个值保存在args中,notifyKey给上层应用了。。

Step 13

其实针对BACK  HOME MENU这三个按键来说,其实质就是TouchScreen;因此在inputReader.cpp中获取Touch映射是在函数bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags)  中。这里同上面的Step 12相同。

首先检测不是多点Touch。然后使用const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit( int32_t x, int32_t y)依据坐标值查找出Touch的映射值。
到最后了啊。。。
呵呵,看看是怎么实现的。。


Android 按键消息处理Android 按键消息处理的更多相关文章

  1. Atitit.android js 的键盘按键检测Back键Home键和Menu键事件

    Atitit.android js 的键盘按键检测Back键Home键和Menu键事件 1. onKeyDown @Override public boolean onKeyDown(int keyC ...

  2. Android常用的物理按键及其触发事件

    Activity和View都能接收触摸和按键,如果响应事件只需要在继承类里复写事件函数即可:当一个视图(如一个按钮)被触摸时,该对象上的 onTouchEvent() 方法会被调用.不过,为了侦听这个 ...

  3. Android tp的虚拟按键(virtual key)处理

    Android tp的虚拟按键处理 现在在越来越多的Android的手机都是虚拟按键来操作,但是对于开发者来说可能会关心Android对虚拟按键如何处理的.对Linux熟悉的人可能会说,it's ea ...

  4. Android中通过耳机按键控制音乐播放的实现

    今天在研究Android中实现Android 4.2.2源码中的Music应用的源码,关于通过耳机按键控制音乐播放的实现,有点好奇,就仔细分析了一下源码, 主要由 MediaButtonIntentR ...

  5. Android Tv 中的按键事件 KeyEvent 分发处理流程

    这次打算来梳理一下 Android Tv 中的按键点击事件 KeyEvent 的分发处理流程.一谈到点击事件机制,网上资料已经非常齐全了,像什么分发.拦截.处理三大流程啊:或者 dispatchTou ...

  6. Android 7.0 Power 按键处理流程

    Android 7.0  Power 按键处理流程 Power按键的处理逻辑由PhoneWindowManager来完成,本文只关注PhoneWindowManager中与Power键相关的内容,其他 ...

  7. Android获取长按按键响应

    Android获取长按按键响应http://www.2cto.com/kf/201312/261719.html Android下Listview的onItemClick以及onItemLongCli ...

  8. 隐藏Android下的虚拟按键

    要隐藏Android下的虚拟按键,可通过如下办法操作 adb root adb remount adb shell ls -al /system/build.prop   (查看文件权限) -rw-r ...

  9. Android 命令行模拟按键

    /***************************************************************************** * Android 命令行模拟按键 * 说 ...

  10. Android 小米盒子游戏手柄按键捕获 - 能获取到的 home 键依旧是个痛

    Android 小米盒子游戏手柄按键捕获 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 ...

随机推荐

  1. FindWindow()&&FindWindowEx

    这个函数呢,我一般用来自动刷刷网页啥的比如我最近就在刷52破解的在线时间,好啦怎么用是你自己的事情. FindWindow()主要用来获取目标句柄 或着说窗口的权限 HWND FindWindow( ...

  2. Start my cnBlogs

    Compared to CSDN blog, althought it's my first time to use CNBlog,i felt it makes me more comfortabl ...

  3. c++ 哪些自定义的数据类型

    http://www.cnblogs.com/ShaneZhang/archive/2013/06/21/3147648.html 这些数据类型是 C99 中定义的,具体定义在:/usr/includ ...

  4. c++ std::string 用法

    std::string用法总结 在平常工作中经常用到了string类,本人记忆了不好用到了的时候经常要去查询.在网上摘抄一下总结一下,为以后的查询方便: string类的构造函数: string(co ...

  5. ORA-12505, TNS:listener does not currently know of SID given in connect descriptor

    引子: 本项目在老电脑上用的是oracle10g,换新电脑装的是oracle11g,但运行项目本没有什么关系,本来说创建个用户,用PLSQL手工导入数据,再改几下配置文件即可跑起来--但实际启动中遇到 ...

  6. Matlab中数组下标是logical,如何处理?

    K>> a = 10*ones(1,10); K>> b = [1 56 23 5 6 45 9 7 89 10]; K>> c = b<a c = 1 0 ...

  7. 17.2 The DispatcherServlet

    综述: Spring’s web MVC framework is, like many other web MVC frameworks, request-driven, designed arou ...

  8. 阿里云ubuntu12.04下安装使用mongodb

    阿里云ubuntu12.04下安装mongodb   apt-get install mongodb 阿里云ubuntu12.04下卸载mongodb,同时删除配置文件     apt-get pur ...

  9. [设计模式] 8 组合模式 Composite

    DP书上给出的定义:将对象组合成树形结构以表示“部分-整体”的层次结构.组合使得用户对单个对象和组合对象的使用具有一致性.注意两个字“树形”.这种树形结构在现实生活中随处可见,比如一个集团公司,它有一 ...

  10. 基于EBP的栈帧

    程序的OEP,一开始以 push ebp 和mov ebp esp这两句开始.   原因:c程序的开始是以一个主函数main()为开始的,而函数在访问的过程中最重要的事情就是要确保堆栈的平衡,而在wi ...