Android事件處理

Init-----------zygote---------system-server-------------------windosmanager  ------------------------------------------------------------ UEventObserver
------------------------------------------------------------ InputDeviceRead
-------------------------------------------------------------InputDispatcher
-------------------------------------------------------------DisplayEventThr
-------------------------------------------------------------ActivityManager

EventHub:
而事件的傳入是從EventHub開始的,EventHub是事件的抽象結構,維護着系統設備的運行情況,設備類型包括Keyboard、TouchScreen、TraceBall。它在系統啓動的時候會通過open_device方法將系統提供的輸入設備都增加到這個抽象結構中,並維護一個所有輸入設備的文件描述符,如果輸入設備是鍵盤的話還會讀取/system/usr/keylayout/目錄下對應鍵盤設備的映射文件,另外getEvent方法是對EventHub中的設備文件描述符使用poll操作等侍驅動層事件的發生,如果發生的事件是鍵盤事件,則調用Map函數按照映射文件轉換成相應的鍵值並將掃描碼和鍵碼返回給KeyInputQueue。
KeyLayoutMap主要是讀取鍵盤映射文件並將鍵盤掃描碼和鍵碼進行轉換

frameworks\base\core\jni\server\ com_android_server_KeyInputQueue.cpp
EventHub和KeyinputQueue的JNI接口層


KeyinputQueue:
在線程InputDeviceReader中會根據事件的類型以及事件值進行判斷處理,從而確定這個事件對應的設備狀態是否發生了改變並相應的改變對這個設備的描述結構InputDevice。
getEvent:在給定時間段時看是否有事件發生,如果有的話返回true否則false。

Windowmanager:
(frameworks/base/services/java/com/android/server/windowmanagerservice.java)
進程Windowmanager會創建一個線程(InputDispatcherThread),在這個線程裏從事件隊列中讀取發生的事件(QueuedEvent ev = mQueue.getEvent()),並根據讀取到事件類型的不同分成三類(KEYBOARD、TOUCHSCREEN、TRACKBALL),分別進行處理,例如鍵盤事件會調用dispatchKey((KeyEvent)ev.event, 0, 0)以將事件通過Binder發送給具有焦點的窗口應用程序,然後調用 mQueue.recycleEvent(ev)繼續等侍鍵盤事件的發生;如果是觸摸屏事件則調用dispatchPointer(ev, (MotionEvent)ev.event, 0, 0),這裏會根據事件的種類(UP、DOWN、MOVE、OUT_SIDE等)進行判斷並處理,比如Cancel或將事件發送到具有權限的指定的窗口中去;

Android 輸入事件流程

EventHub

EventHub對輸入設備進行了封裝。輸入設備驅動程序對用戶空間應用程序提供一些設備文件,這些設備文件放在/dev/input裏面。

EventHub掃描/dev/input下所有設備文件,並打開它們。

C代碼  收藏代碼
  1. bool EventHub::openPlatformInput(void)  
  2. {  
  3. ...  
  4.     mFDCount = 1;  
  5.     mFDs = (pollfd *)calloc(1, sizeof(mFDs[0]));  
  6.     mDevices = (device_t **)calloc(1, sizeof(mDevices[0]));  
  7.     mFDs[0].events = POLLIN;  
  8.     mDevices[0] = NULL;  
  9.   
  10.     res = scan_dir(device_path);  
  11. ...  
  12.     return true;  
  13. }  


EventHub對外提供了一個函數用於從輸入設備文件中讀取數據。

C代碼  收藏代碼
  1. bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,  
  2.         int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,  
  3.         int32_t* outValue, nsecs_t* outWhen)  
  4. {  
  5.     ...  
  6.     while(1) {  
  7.   
  8.         // First, report any devices that had last been added/removed.  
  9.         if (mClosingDevices != NULL) {  
  10.             device_t* device = mClosingDevices;  
  11.             LOGV("Reporting device closed: id=0x%x, name=%s\n",  
  12.                  device->id, device->path.string());  
  13.             mClosingDevices = device->next;  
  14.             *outDeviceId = device->id;  
  15.             if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  
  16.             *outType = DEVICE_REMOVED;  
  17.             delete device;  
  18.             return true;  
  19.         }  
  20.         if (mOpeningDevices != NULL) {  
  21.             device_t* device = mOpeningDevices;  
  22.             LOGV("Reporting device opened: id=0x%x, name=%s\n",  
  23.                  device->id, device->path.string());  
  24.             mOpeningDevices = device->next;  
  25.             *outDeviceId = device->id;  
  26.             if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  
  27.             *outType = DEVICE_ADDED;  
  28.             return true;  
  29.         }  
  30.   
  31.         release_wake_lock(WAKE_LOCK_ID);  
  32.   
  33.         pollres = poll(mFDs, mFDCount, -1);  
  34.   
  35.         acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);  
  36.   
  37.         if (pollres <= 0) {  
  38.             if (errno != EINTR) {  
  39.                 LOGW("select failed (errno=%d)\n", errno);  
  40.                 usleep(100000);  
  41.             }  
  42.             continue;  
  43.         }  
  44.   
  45.         for(i = 1; i < mFDCount; i++) {  
  46.             if(mFDs[i].revents) {  
  47.                 LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);  
  48.                 if(mFDs[i].revents & POLLIN) {  
  49.                     res = read(mFDs[i].fd, &iev, sizeof(iev));  
  50.                     if (res == sizeof(iev)) {  
  51.                         LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",  
  52.                              mDevices[i]->path.string(),  
  53.                              (int) iev.time.tv_sec, (int) iev.time.tv_usec,  
  54.                              iev.type, iev.code, iev.value);  
  55.                         *outDeviceId = mDevices[i]->id;  
  56.                         if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  
  57.                         *outType = iev.type;  
  58.                         *outScancode = iev.code;  
  59.                         if (iev.type == EV_KEY) {  
  60.                             err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);  
  61.                             LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n",  
  62.                                 iev.code, *outKeycode, *outFlags, err);  
  63.                             if (err != 0) {  
  64.                                 *outKeycode = 0;  
  65.                                 *outFlags = 0;  
  66.                             }  
  67.                         } else {  
  68.                             *outKeycode = iev.code;  
  69.                         }  
  70.                         *outValue = iev.value;  
  71.                         *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);  
  72.                         return true;  
  73.                     } else {  
  74.                         if (res<0) {  
  75.                             LOGW("could not get event (errno=%d)", errno);  
  76.                         } else {  
  77.                             LOGE("could not get event (wrong size: %d)", res);  
  78.                         }  
  79.                         continue;  
  80.                     }  
  81.                 }  
  82.             }  
  83.         }  
  84.     ...  
  85. }  



對於按鍵事件,調用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,用於讀 取輸入設備事件。

C代碼  收藏代碼
  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 裏創建了一個線程,它循環的讀取事件,然後把事件放入事件隊列裏。

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


輸入事件分發線程

在frameworks/base/services/java/com/android/server/WindowManagerService.java裏創建了一個輸入事件分發線程,它負責把事件分發到相應的窗口上去。

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上層可以識別的按鍵。
發佈了3 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章