Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android中Key Event流程

Android中Key Event流程

編輯:Android開發實例

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. }  
bool EventHub::openPlatformInput(void)
{
...
    mFDCount = 1;
    mFDs = (pollfd *)calloc(1, sizeof(mFDs[0]));
    mDevices = (device_t **)calloc(1, sizeof(mDevices[0]));
    mFDs[0].events = POLLIN;
    mDevices[0] = NULL;

    res = scan_dir(device_path);
...
    return true;
}



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. }  
    static jboolean
    android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
                                              jobject event)
    {
        gLock.lock();
        sp hub = gHub;
        if (hub == NULL) {
            hub = new EventHub;
            gHub = hub;
        }
        gLock.unlock();
    
        int32_t deviceId;
        int32_t type;
        int32_t scancode, keycode;
        uint32_t flags;
        int32_t value;
        nsecs_t when;
        bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
                &flags, &value, &when);
    
        env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
        env->SetIntField(event, gInputOffsets.mType, (jint)type);
        env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
        env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
        env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
        env->SetIntField(event, gInputOffsets.mValue, value);
        env->SetLongField(event, gInputOffsets.mWhen,
                            (jlong)(nanoseconds_to_milliseconds(when)));
    
        return res;
    }


    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.      };  
    Thread mThread = new Thread("InputDeviceReader") {
            public void run() {
                android.os.Process.setThreadPriority(
                        android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
    
                try {
                    RawInputEvent ev = new RawInputEvent();
                    while (true) {
                        InputDevice di;
    
                        readEvent(ev);
    
                        send = preprocessEvent(di, ev);
                        addLocked(di, curTime, ev.flags, ..., me);
                    }
            }
        };


    輸入事件分發線程

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

    Java代碼
    1. mQueue.getEvent   
    2. dispatchKey/dispatchPointer/dispatchTrackball  
    mQueue.getEvent
    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上層可以識別的按鍵。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved