按鍵事件
對于按鍵事件,調(diào)用mDevices->layoutMap->map進行映射。映射實際是由 KeyLayoutMap::map完成的,KeyLayoutMap類里讀取配置文件qwerty.kl,由配置 文件 qwerty.kl 決定鍵值的映射關(guān)系。你可以通過修 改./development/emulator/keymaps/qwerty.kl來改變鍵值的映射關(guān)系。
JNI 函數(shù)
在 frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文 件中,向 JAVA提供了函數(shù)android_server_KeyInputQueue_readEvent,用于讀 取輸入設(shè)備事件。
C代碼:

jobject event )
{
gLock. lock ();
sphub = gHub;
if (hub == NULL){
hub = new EventHub;
gHub = hub;
}
gLock.unlock();
int32_tdeviceId;
int32_ttype;
int32_tscancode,keycode;
uint32_tflags;
int32_tvalue;
nsecs_twhen;
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調(diào)用hub->getEvent讀了取事件,然后轉(zhuǎn)換成JAVA的結(jié)構(gòu)。
事件中轉(zhuǎn)線程
在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里創(chuàng)建了一個線程,它循環(huán)的讀取事件,然后把事件放入事件隊列里。
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;
}
}
按鍵、觸摸屏流、軌跡球程分析
輸入事件分發(fā)線程
在frameworks/base/services/java/com/android/server/WindowManagerService.java里創(chuàng)建了一個輸入事件分發(fā)線程,它負(fù)責(zé)把事件分發(fā)到相應(yīng)的窗口上去。
按鍵觸摸屏流程分析:
WindowManagerService類的構(gòu)造函數(shù)
WindowManagerService()
mQueue = new KeyQ();
因為 WindowManagerService.java (frameworks/base/services/java/com/android/server)中有:
private class KeyQ extends KeyInputQueue implements KeyInputQueue.FilterCallback
KeyQ 是抽象類 KeyInputQueue 的實現(xiàn),所以 new KeyQ類的時候?qū)嶋H上在 KeyInputQueue 類中創(chuàng)建了一個線程 InputDeviceReader 專門用來從設(shè)備讀取按鍵事件,
代碼:
public void run(){
// 在循環(huán)中調(diào)用:
...
send = preprocessEvent(di,ev);
...
int keycode = rotateKeyCodeLocked(ev.keycode);
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,...));
QueuedEventev = obtainLocked(device,when,flags,classType,event);
}
};
readEvent() 實際上調(diào)用的是 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)調(diào)用的是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)
在函數(shù)中調(diào)用了讀設(shè)備操作:res = read(mFDs.fd, &iev, sizeof(iev));
在構(gòu)造函數(shù) WindowManagerService()調(diào)用 new KeyQ() 以后接著調(diào)用了:
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;
===============================================================
補充一些內(nèi)容:
在寫程序時,需要捕獲KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER這幾個按鍵,但是這幾個按鍵系統(tǒng)做了特殊處理,
在進行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分發(fā)給任何其他APP,ENDCALL和POWER也類似,所以需要我們系統(tǒng)
處理之前進行處理。
我的做法是自己定義一個FLAG,在自己的程序中添加此FLAG,然后在WindowManagerServices.java中獲取當(dāng)前窗口的FLAG屬性,如果是我
們自己設(shè)置的那個FLAG,則不進行特殊處理,直接分發(fā)按鍵消息到我們的APP當(dāng)中,由APP自己處理。
這部分代碼最好添加在
@Override
boolean preprocessEvent(InputDevice device, RawInputEvent event)
方法中,這個方法是KeyInputQueue中的一個虛函數(shù),在處理按鍵事件之前的一個“預(yù)處理”。
PS:對HOME鍵的處理好像必需要修改PhoneWindowManager.java中的interceptKeyTi方法,具體可以參考對KeyGuard程序的處理。
===============================================================
系統(tǒng)底層事件處理過程
在系統(tǒng)啟動后,android 會通過
static const char *device_path = "/dev/input";
bool EventHub::penPlatformInput(void)
res = scan_dir(device_path);
通過下面的函數(shù)打開設(shè)備。

{
...
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);
...
}
打開設(shè)備的時候,如果 device->classes&CLASS_KEYBOARD 不等于 0 表明是鍵盤。
常用輸入設(shè)備的定義有:
enum {
CLASS_KEYBOARD = 0x00000001, //鍵盤
CLASS_ALPHAKEY = 0x00000002, //
CLASS_TOUCHSCREEN = 0x00000004, //觸摸屏
CLASS_TRACKBALL = 0x00000008 //軌跡球
};
打開鍵盤設(shè)備的時候通過上面的 ioctl 獲得設(shè)備名稱,命令字 EVIOCGNAME 的定義在文件:
kernel/include/linux/input.h 中。
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */
在內(nèi)核鍵盤驅(qū)動文件 drivers/input/keyboard/pxa27x_keypad.c 中定義了設(shè)備名稱: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 為環(huán)境變量,在android的命令模式下通過 printenv 可以知道它為: system
所以 keylayoutFilename 為:/system/usr/keylayout/pxa27x-keypad.kl
pxa27x-keypad.kl 定義了按鍵映射,具體內(nèi)容如下:

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
#FUNCTIONALKEYS
key 231 MENUWAKE_DROPPED
key 192 BACKWAKE_DROPPED
key 193 HOMEWAKE
key 107 DELWAKE
key 102 CALLWAKE_DROPPED
key 158 ENDCALLWAKE_DROPPED
key 28 DPAD_CENTERWAKE
key 115 VOLUME_UP
key 114 VOLUME_DOWN
如果沒有定義鍵盤映射文件,那么默認(rèn)使用系統(tǒng)的 /system/usr/keylayout/qwerty.kl 可以修改 /system/usr/keylayout/qwerty.kl 文件改變Android公司的按鍵映射。
device->layoutMap->load(keylayoutFilename) 調(diào)用的是文件 KeyLayoutMap.cpp (frameworks/base/libs/ui)中的函數(shù):
status_t KeyLayoutMap::load(const char* filename)通過解析 pxa27x-keypad.kl
把按鍵的映射關(guān)系保存在 :KeyedVector<int32_t,Key> m_keys; 中。
當(dāng)獲得按鍵事件以后調(diào)用:
status_t KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags)
由映射關(guān)系 KeyedVector<int32_t,Key> m_keys 把掃描碼轉(zhuǎn)換成andorid上層可以識別的按鍵。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
