Android+按键事件处理详解

公司最近做一个按键触感就对这块研究了一番,以下是个人心得。

1.开始肯定先说的是驱动这块,硬件是软件服务的,在Android这块C和java交互,有两种方式:

   1.1:驱动--JNI--服务-事件分发-上层应用处理。

   1.2:上层直接调用通过lib库的方式实现,中间使用回调机制,这种方式在Camera中有,下次再详解。

   先来看一下驱动按键映射部分的详解如下:

   映射实际是由 KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置 文件 qwerty.kl 决定键值的映射关系。你可以通过修改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。
   在frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文件中,向 JAVA提供了函数android_server_KeyInputQueue_readEvent,用于读取输入设备事件。

  1. static jboolean   
  2. android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,   
  3.                                            jobject event)   
  4. {   
  5.      gLock.lock();   
  6.      sp hub = gHub;   
  7.     if (hub == NULL) {   
  8.          hub = new EventHub;   
  9.          gHub = hub;   
  10.      }   
  11.      gLock.unlock();   
  12.   
  13.      int32_t deviceId;   
  14.      int32_t type;   
  15.      int32_t scancode, keycode;   
  16.      uint32_t flags;   
  17.      int32_t value;   
  18.      nsecs_t when;   
  19.     bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,   
  20.              &flags, &value, &when);   
  21.   
  22.      env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);   
  23.      env->SetIntField(event, gInputOffsets.mType, (jint)type);   
  24.      env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);   
  25.      env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);   
  26.      env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);   
  27.      env->SetIntField(event, gInputOffsets.mValue, value);   
  28.      env->SetLongField(event, gInputOffsets.mWhen,   
  29.                          (jlong)(nanoseconds_to_milliseconds(when)));   
  30.   
  31.     return res;   
  32. }  

 2.下面要讲重头代码WindowManagerService.java (frameworks\base\services\java\com\android\server),这个服务有承上启下的作用,读取用户输入的信息,是通过创建一个InputDeviceReader线程,KeyQ构建时,会启动一个线程去读取用户消息,具体代码在KeyInputQueue.mThread,在构造函数中,mThread会start,接下来,接研究一下mThread.run:
    //用户输入事件消息读取线程
    Thread mThread = new Thread("InputDeviceReader") {
        public void run() {
            RawInputEvent ev = new RawInputEvent();
            while (true) {//开始消息读取循环
                try {
                    InputDevice di;
                    //本地方法实现,读取用户输入事件
                    readEvent(ev);
                    //根据ev事件进行相关处理
                    ...
                    synchronized (mFirst) {//mFirst是keyQ队列头指针
                    ...
                    addLocked(di, curTimeNano, ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);
                    ...
                    }
                }
        }
       }

  3.在读取完用户输入的信息,在WindowManagerService中就要分发消息,具体实现是交给了InputDispatcherThread这个线程处理,下来详解InputDispatcherThread处理流程:

   -->WindowManagerService.main
         --mInputThread = new InputDispatcherThread();//创建一个消息分发线程,读取并处理mQueue中消息

     InputDispatcherThread.run
-->windowManagerService.process{               
            ...
            while (true) {               
                // 从mQueue(KeyQ)获取一个用户输入事件,正上调用我上面提到的getEvent方法,若队列为空,线程阻塞挂起
                QueuedEvent ev = mQueue.getEvent(
                    (int)((!configChanged && curTime < nextKeyTime)
                            ? (nextKeyTime-curTime) : 0));
                ...
                try {
                    if (ev != null) {
                        ...
                        if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件
                            eventType = eventType((MotionEvent)ev.event);
                        } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
                                    ev.classType == RawInputEvent.CLASS_TRACKBALL) {//键盘输入事件
                            eventType = LocalPowerManager.BUTTON_EVENT;
                        } else {
                            eventType = LocalPowerManager.OTHER_EVENT;//其他事件
                        }
                        ...
                        switch (ev.classType) {
                            case RawInputEvent.CLASS_KEYBOARD:
                                ...
                                dispatchKey((KeyEvent)ev.event, 0, 0);//键盘输入,派发key事件
                                mQueue.recycleEvent(ev);
                                break;
                            case RawInputEvent.CLASS_TOUCHSCREEN:
                                dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派发touch事件
                                break;
                            case RawInputEvent.CLASS_TRACKBALL:
                                dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滚轮事件,派发Trackball事件
                                break;
                            case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
                                configChanged = true;
                                break;
                            default:
                                mQueue.recycleEvent(ev);//销毁事件
                            break;
                        }

                    }
                } catch (Exception e) {
                    Slog.e(TAG,
                        "Input thread received uncaught exception: " + e, e);
                }
            }       
   }

4.KeyEvent事件的传递主要可以划分为三步:过滤器、View树、Activity.

  过滤器部分对应的是(frameworks/base/policy/base/phone/com/Android/internal/policy/impl/PhoneWindowManager.java)PhoneWindowManager.java中的interceptKeyTq和interceptKeyTi这两个方法。它们的代码可以在中看到。

这两个过滤器最大的不同就是interceptKeyTq用于RawEvent,而interceptKeyTi用于KeyEvent。

在一个没有实体键盘的机器上,Power键会被interceptKeyTq这个过滤器吃掉用来调用关机对话框或者使机器休眠。而Home键会被interceptKeyTi这个过滤器吃掉,用来把当前Activity切换到后台并把桌面程序切换到前台。所以,应用程序在View和Activity的onKeyDown/Up中是监听不到这两个按键的。除了这两个键以外的按键,都有机会继续前进。接下来,KeyEvent会先经过interceptKeyTi过滤器,如果这个过滤器不吃掉的话,就会继续前进,进入View树,如果没有被哪个View吃掉的话,最后进入到Activity的onKeyDown/Up方法中。

当一个KeyEvent经过上面的过程还没有被吃掉的话,系统就会利用它做一些定制的功能。比如音量键被系统用来调整声音,多媒体按键用来控制媒体播放,搜索键用来快速打开搜索功能,返回键用来退出当前Activity等。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,View的按键派发流程可以分为三个阶段:事件捕获阶段、事件处理阶段和事件分发阶段。具体流程如下: 1. 事件捕获阶段:从根View开始,依次向下遍历其所有的子View,直到找到最深层的子View。在这个过程中,每个View都有机会处理该事件,即调用onKeyDown()、onKeyUp()等方法进行事件处理。 2. 事件处理阶段:当找到最深层的子View之后,事件开始进行处理。在这个阶段,View会根据自身的状态和属性来处理该事件,例如,判断是否处于可用状态、是否需要获取焦点等。 3. 事件分发阶段:当View处理完该事件之后,事件会根据事件分发规则,向上传递给父View进行处理。如果父View需要处理该事件,则继续进行事件捕获和事件处理阶段;如果不需要处理,则事件传递到下一个父View进行处理,直到传递到根View,或者事件被某个View消费掉。 需要注意的是,在事件分发阶段,View可以通过返回值来控制事件是否被消费。如果View处理了该事件,并认为该事件不需要再传递给下一个View,可以返回true,表示该事件已被消费;如果View没有处理该事件,或者认为该事件需要继续传递给下一个View,可以返回false,表示该事件需要继续传递。 总之,View的按键派发流程是一个非常复杂的过程,需要开发者深入理解和掌握。只有理解了该流程,才能正确地处理按键事件,提升应用程序的交互性和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值