Android事件处理下(按键触摸屏和滚动球的一些实现细节)

幽映每白日,清辉照衣裳。这篇文章主要讲述Android事件处理下(按键触摸屏和滚动球的一些实现细节)相关的知识,希望能为你提供帮助。
http://www.cnblogs.com/andtt/articles/2145563.html
对于按键事件,调用mDevices[i]-> layoutMap-> map进行映射。映射实际是由 KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置 文件 qwerty.kl 决定键值的映射关系。你可以通过修 改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。 
JNI 函数 

在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. }   


readEvent调用hub-> getEvent读了取事件,然后转换成JAVA的结构。 
事件中转线程 

在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个线程,它循环的读取事件,然后把事件放入事件队列里。 


  1. Thread mThread =  new  Thread("InputDeviceReader") {    
  2.                 public  void  run() {    
  3.                         android.os.Process.setThreadPriority(    
  4.                                         android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);    
  5.    
  6.                         try  {    
  7.                                 RawInputEvent ev =  new  RawInputEvent();    
  8.                                 while  (true) {    
  9.                                         InputDevice di;    
  10.    
  11.                                         readEvent(ev);    
  12.    
  13.                                         send = preprocessEvent(di, ev);    
  14.                                         addLocked(di, curTime, ev.flags, ..., me);    
  15.                                 }    
  16.                 }    
  17.         };    
【Android事件处理下(按键触摸屏和滚动球的一些实现细节)】

输入事件分发线程 

在frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。 


  1. mQueue.getEvent    
  2. dispatchKey/dispatchPointer/dispatchTrackball   




按键,触摸屏流程分析 

按键触摸屏流程分析: 
WindowManagerService类的构造函数 
WindowManagerService() 
mQueue = new KeyQ();  
因为 WindowManagerService.java (frameworks\\base\\services\\java\\com\\android\\server)中有:       
private class KeyQ extends KeyInputQueue 
KeyQ 是抽象类 KeyInputQueue 的实现,所以 new KeyQ类的时候实际上在 KeyInputQueue 类中创建了 
一个线程 InputDeviceReader 专门用来冲设备读取按键事件,代码: 
Thread mThread = new Thread("InputDeviceReader") { 
public void run() 

              在循环中调用:readEvent(ev);  
      ... 
      send = preprocessEvent(di, ev);  
              实际调用的是 KeyQ 类的 preprocessEvent 函数 
      ... 
      int keycode = rotateKeyCodeLocked(ev.keycode);  
          int[] map = mKeyRotationMap;  
          for (int i=0; i< N; i+=2) 
          { 
              if (map[i] == keyCode) 
                  return map[i+1];  
          } // 
      addLocked(di, curTime, ev.flags,RawInputEvent.CLASS_KEYBOARD,newKeyEvent(di, di.mDownTime, curTime, down,keycode, 0, scancode,...));  
          QueuedEvent ev = obtainLocked(device, when, flags, classType, event);  



readEvent() 实际上调用的是 com_android_server_KeyInputQueue.cpp (frameworks\\base\\services\\jni)中的: 
static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,jobject event) 
bool res = hub-> getEvent(& deviceId, & type, & scancode, & keycode,& flags, & value, & when);  
调用的是 EventHub.cpp (frameworks\\base\\libs\\ui)中的: 
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, 
              int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, 
              int32_t* outValue, nsecs_t* outWhen) 
在函数中调用了读设备操作:res = read(mFDs[i].fd, & iev, sizeof(iev));  


在构造函数 WindowManagerService()调用 new KeyQ() 以后接着调用了: 
mInputThread = new InputDispatcherThread();              
...         
mInputThread.start();  
来启动一个线程 InputDispatcherThread 
run() 
process();  
      QueuedEvent ev = mQueue.getEvent(...) 
因为WindowManagerService类中: final KeyQ mQueue;  
所以实际上 InputDispatcherThread 线程实际上从 KeyQ 的事件队列中读取按键事件。 
switch (ev.classType) 
case RawInputEvent.CLASS_KEYBOARD: 
      ... 
      dispatchKey((KeyEvent)ev.event, 0, 0);  
      mQueue.recycleEvent(ev);  
      break;  
case RawInputEvent.CLASS_TOUCHSCREEN: 
      //Log.i(TAG, "Read next event " + ev);  
      dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);  
      break;  

=============================================================== 


KeyInputQueue.java (frameworks\\base\\services\\java\\com\\android\\server): 
的线程 Thread mThread = new Thread("InputDeviceReader") 本地调用: 
readEvent(ev); 读取按键。readEvent 调用的是文件: 
com_android_server_KeyInputQueue.cpp (frameworks\\base\\services\\jni)中的函数: 
static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz, 
                                                                                  jobject event) 
android_server_KeyInputQueue_readEvent中有: 
hub = new EventHub;  
bool res = hub-> getEvent(& deviceId, & type, & scancode, & keycode, 
                      & flags, & value, & when);  

hub-> getEvent 调用的是 
EventHub.cpp (frameworks\\base\\libs\\ui) 文件中的函数: 
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, 
              int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, 
              int32_t* outValue, nsecs_t* outWhen) 
读取按键。 

class RefBase::weakref_impl : public RefBase::weakref_type 



在系统启动后,android 会通过 
static const char *device_path = "/dev/input";  
bool EventHub::openPlatformInput(void) 
res = scan_dir(device_path);  


通过下面的函数打开设备。 
int EventHub::open_device(const char *deviceName) 

... 
fd = open(deviceName, O_RDWR);  
... 
mFDs[mFDCount].fd = fd;  
mFDs[mFDCount].events = POLLIN;  
... 
ioctl(mFDs[mFDCount].fd, EVIOCGNAME(sizeof(devname)-1), devname);  
... 
const char* root = getenv("ANDROID_ROOT");  
snprintf(keylayoutFilename, sizeof(keylayoutFilename), 
                                "%s/usr/keylayout/%s.kl", root, tmpfn);  
... 
device-> layoutMap-> load(keylayoutFilename);  
... 

打开设备的时候,如果 device-> classes& CLASS_KEYBOARD 不等于 0 表明是键盘。 
常用输入设备的定义有: 
enum { 
              CLASS_KEYBOARD          = 0x00000001, //键盘 
              CLASS_ALPHAKEY          = 0x00000002, // 
              CLASS_TOUCHSCREEN    = 0x00000004, //触摸屏 
              CLASS_TRACKBALL        = 0x00000008 //轨迹球 
      };  
打开键盘设备的时候通过上面的 ioctl 获得设备名称,命令字 EVIOCGNAME 的定义在文件: 
kernel/include/linux/input.h 中。 
#define EVIOCGNAME(len)    _IOC(_IOC_READ, \'E\', 0x06, len) /* get device name */ 
在内核键盘驱动文件 drivers/input/keyboard/pxa27x_keypad.c 中定义了设备名称:pxa27x-keypad 
static struct platform_driver pxa27x_keypad_driver = { 
      .probe              = pxa27x_keypad_probe, 
      .remove              = __devexit_p(pxa27x_keypad_remove), 
      .suspend      = pxa27x_keypad_suspend, 
      .resume              = pxa27x_keypad_resume, 
      .driver              = { 
              .name      = "pxa27x-keypad", 
              .owner      = THIS_MODULE, 
      }, 
};  
ANDROID_ROOT 为环境变量,在android的命令模式下通过 printenv 可以知道它为: system 
所以 keylayoutFilename 为:/system/usr/keylayout/pxa27x-keypad.kl 
pxa27x-keypad.kl 定义了按键映射,具体内容如下: 
---------------------- 
# NUMERIC KEYS 3x4 
key 2    1 
key 3    2 
key 4    3 
key 5    4 
key 6    5 
key 7    6 
key 8    7 
key 9    8 
key 10 9 
key 11 0 
key 83 POUND 
key 55 STAR 

# FUNCTIONAL KEYS 
key 231 MENU              WAKE_DROPPED 
key 192 BACK                    WAKE_DROPPED 
key 193 HOME            WAKE 
key 107 DEL              WAKE 
key 102 CALL              WAKE_DROPPED 
key 158 ENDCALL        WAKE_DROPPED 
key 28    DPAD_CENTER        WAKE 
key 115 VOLUME_UP 
key 114 VOLUME_DOWN 
---------------------- 
如果没有定义键盘映射文件,那么默认使用系统的 /system/usr/keylayout/qwerty.kl 
可以修改 /system/usr/keylayout/qwerty.kl 文件改变Android公司的按键映射。 

device-> layoutMap-> load(keylayoutFilename) 调用的是文件: 
KeyLayoutMap.cpp (frameworks\\base\\libs\\ui)中的函数: 
status_t KeyLayoutMap::load(const char* filename)通过解析 pxa27x-keypad.kl 
把按键的映射关系保存在 :KeyedVector< int32_t,Key> m_keys; 中。 
当获得按键事件以后调用: 
status_t KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags) 
由映射关系 KeyedVector< int32_t,Key> m_keys 把扫描码转换成andorid上层可以识别的按键。

    推荐阅读