對於按鍵事件,調用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 專門用來從設備讀取按鍵事件,
代碼:public void run() {
// 在循環中調用:
...
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,...));
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);
通過下面的函數打開設備。
{
...
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 定義了按鍵映射,具體內容如下:
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上層可以識別的按鍵。