Android 按键消息处理Android 按键消息处理

时间:2023-01-01 13:50:25

在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&period;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的虚拟按键&lpar;virtual key&rpar;处理

    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&period;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. 004&lowbar;Intelij 使用,Anonymous Apex

    插件安装好之后,就可以正常添加cloud 项目: 注意:免费使用是30天,为了不去买license,在过期后,去修改下机器的日期,这个日期是在安装后的一个月内,改好日期后,启动Intelij 少一个截 ...

  2. hiho&lowbar;1061&lowbar;beautiful&lowbar;string

    题目大意 题目连接:beautiful string     写代码之前,考虑清楚流程,以及需要维护的变量.... 实现 #include<iostream> #include<st ...

  3. JavaWeb学习记录(十九)——jstl自定义标签之简单标签

    一.简单标签共定义了5个方法: setJspContext方法 setParent和getParent方法 setJspBody方法 doTag方法 二.方法介绍 osetJspContext方法 用 ...

  4. wemall app商城源码Android之支付宝接口公用函数

    wemall-mobile是基于WeMall的Android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之  ...

  5. 一个简单的python选课系统

    下面介绍一下自己写的python程序,主要是的知识点为sys.os.json.pickle的模块应用,python程序包的的使用,以及关于类的使用. 下面是我的程序目录: bin是存放一些执行文件co ...

  6. Codeforces 1019C Sergey&&num;39&semi;s problem 构造

    原文链接https://www.cnblogs.com/zhouzhendong/p/CF1019C.html 题目传送门 - CF1019C 题意 给定一个有 $n$ 个节点 . $m$ 条边的有向 ...

  7. springmvc mybatis shiro ios android构建cms系统

    开发语言: java.ios.android 部署平台: linux.window jdk版本:JDK1.7以上版本 开发工具: eclipse.idea等 服务器中间件:Tomcat 6.7.Jbo ...

  8. css引入页面的三种方法

    1.内联式:直接在标签上写样式 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

  9. &lbrack;保存&rsqb;——pycharm5专业版激活方法

  10. OpenGL入门程序三:点、线、面的绘制

    1.点: void TestPoint() { //点的大小默认为一个像素,通过下面的函数可以设置一点的大小 glPointSize(50.0f); glBegin(GL_POINTS); glVer ...