Android按鍵事件處理分析 .

按鍵事件

對於按鍵事件,調用mDevices->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代碼:

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代碼:

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;

                    // block, doesn't release the monitor
                    readEvent(ev);

                    boolean send = false;
                    boolean configChanged = false;
                   
                    if (false) {
                        Log.i(TAG, "Input event: dev=0x"
                                + Integer.toHexString(ev.deviceId)
                                + " type=0x" + Integer.toHexString(ev.type)
                                + " scancode=" + ev.scancode
                                + " keycode=" + ev.keycode
                                + " value=" + ev.value);
                    }
                   
                    if (ev.type == RawInputEvent.EV_DEVICE_ADDED) {
                        synchronized (mFirst) {
                            di = newInputDevice(ev.deviceId);
                            mDevices.put(ev.deviceId, di);
                            configChanged = true;
                        }
                    }

            ......
          }
        }
      }
};

 

按鍵、觸摸屏流、軌跡球程分析

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


按鍵觸摸屏流程分析:

    WindowManagerService類的構造函數

    WindowManagerService()

    mQueue = new KeyQ();

因爲 WindowManagerService.java (frameworks/base/services/java/com/android/server)中有:

private class KeyQ extends KeyInputQueue implements KeyInputQueue.FilterCallback

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  ==  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.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 的事件隊列中讀取按鍵事件,在process() 方法中進行處理事件。

    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;

  case RawInputEvent.CLASS_TRACKBALL:
        dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
        break;

 

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

補充一些內容:

在寫程序時,需要捕獲KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER這幾個按鍵,但是這幾個按鍵系統做了特殊處理,

在進行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分發給任何其他APP,ENDCALL和POWER也類似,所以需要我們系統

處理之前進行處理。

我的做法是自己定義一個FLAG,在自己的程序中添加此FLAG,然後在WindowManagerServices.java中獲取當前窗口的FLAG屬性,如果是我

們自己設置的那個FLAG,則不進行特殊處理,直接分發按鍵消息到我們的APP當中,由APP自己處理。

這部分代碼最好添加在

@Override
boolean preprocessEvent(InputDevice device, RawInputEvent event)

方法中,這個方法是KeyInputQueue中的一個虛函數,在處理按鍵事件之前的一個“預處理”。

PS:對HOME鍵的處理好像必需要修改PhoneWindowManager.java中的interceptKeyTi方法,具體可以參考對KeyGuard程序的處理。

 

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

 

系統底層事件處理過程


在系統啓動後,android 會通過

    static const char *device_path = "/dev/input";

    bool EventHub::penPlatformInput(void)

    res = scan_dir(device_path);

通過下面的函數打開設備。

int  EventHub::pen_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上層可以識別的按鍵。

   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章