关闭

Android 按键事件处理详解

560人阅读 评论(0) 收藏 举报
分类:

本文转自 http://blog.csdn.net/chengzhaoan2010/article/details/7944266


公司最近做一个按键触感就对这块研究了一番,以下是个人心得。
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,jobject event)     
  3. {     
  4.      gLock.lock();     
  5.      sp hub = gHub;     
  6.      if (hub == NULL) {     
  7.          hub = new EventHub;     
  8.          gHub = hub;     
  9.           }     
  10.      gLock.unlock();     
  11.     
  12.      int32_t deviceId;     
  13.      int32_t type;     
  14.      int32_t scancode, keycode;     
  15.      uint32_t flags;     
  16.      int32_t value;     
  17.      nsecs_t when;     
  18.     bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,     
  19.              &flags, &value, &when);     
  20.     
  21.      env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);     
  22.      env->SetIntField(event, gInputOffsets.mType, (jint)type);     
  23.      env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);     
  24.      env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);     
  25.      env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);     
  26.      env->SetIntField(event, gInputOffsets.mValue, value);     
  27.      env->SetLongField(event, gInputOffsets.mWhen,     
  28.                          (jlong)(nanoseconds_to_milliseconds(when)));     
  29.     
  30.     return res;     
  31. }    


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


  3.在读取完用户输入的信息,在WindowManagerService中就要分发消息,具体实现是交给了InputDispatcherThread这个线程处理,下来详解InputDispatcherThread处理流程:
  
  1. -->WindowManagerService.main  
  2.         --mInputThread = new InputDispatcherThread();//创建一个消息分发线程,读取并处理mQueue中消息  
  3.     InputDispatcherThread.run  
  4.   -->windowManagerService.process{                 
  5.            ...  
  6.            while (true) {                 
  7.                // 从mQueue(KeyQ)获取一个用户输入事件,正上调用我上面提到的getEvent方法,若队列为空,线程阻塞挂起  
  8.                QueuedEvent ev = mQueue.getEvent(  
  9.                    (int)((!configChanged && curTime < nextKeyTime)  
  10.                            ? (nextKeyTime-curTime) : 0));  
  11.                ...  
  12.                try {  
  13.                    if (ev != null) {  
  14.                        ...  
  15.                        if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件  
  16.                            eventType = eventType((MotionEvent)ev.event);  
  17.                        } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||  
  18.                                    ev.classType == RawInputEvent.CLASS_TRACKBALL) {//键盘输入事件  
  19.                            eventType = LocalPowerManager.BUTTON_EVENT;  
  20.                        } else {  
  21.                            eventType = LocalPowerManager.OTHER_EVENT;//其他事件  
  22.                        }  
  23.                        ...  
  24.                        switch (ev.classType) {  
  25.                            case RawInputEvent.CLASS_KEYBOARD:  
  26.                                ...  
  27.                                dispatchKey((KeyEvent)ev.event, 0, 0);//键盘输入,派发key事件  
  28.                                mQueue.recycleEvent(ev);  
  29.                                break;  
  30.                            case RawInputEvent.CLASS_TOUCHSCREEN:  
  31.                                dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派发touch事件  
  32.                                break;  
  33.                            case RawInputEvent.CLASS_TRACKBALL:  
  34.                                dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滚轮事件,派发Trackball事件  
  35.                                break;  
  36.                            case RawInputEvent.CLASS_CONFIGURATION_CHANGED:  
  37.                                configChanged = true;  
  38.                                break;  
  39.                            default:  
  40.                                mQueue.recycleEvent(ev);//销毁事件  
  41.                            break;  
  42.                        }  
  43.                    }  
  44.                } catch (Exception e) {  
  45.                    Slog.e(TAG,  
  46.                        "Input thread received uncaught exception: " e, e);  
  47.                }  
  48.            }         
  49.   }  


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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:52519次
    • 积分:817
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:64篇
    • 译文:0篇
    • 评论:4条
    文章分类
    最新评论