Android事件處理分析
http://blog.csdn.net/linweig/archive/2010/02/27/5330391.aspx
按鍵事件
對于按鍵事件,調用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)
在核心鍵盤驅動檔案 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上層可以識别的按鍵。
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代碼
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;
}
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代碼
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
int32_t* outValue, nsecs_t* outWhen)
{
...
while(1) {
// First, report any devices that had last been added/removed.
if (mClosingDevices != NULL) {
device_t* device = mClosingDevices;
LOGV("Reporting device closed: id=0x%x, name=%s/n",
device->id, device->path.string());
mClosingDevices = device->next;
*outDeviceId = device->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = DEVICE_REMOVED;
delete device;
return true;
}
if (mOpeningDevices != NULL) {
device_t* device = mOpeningDevices;
LOGV("Reporting device opened: id=0x%x, name=%s/n",
device->id, device->path.string());
mOpeningDevices = device->next;
*outDeviceId = device->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = DEVICE_ADDED;
return true;
}
release_wake_lock(WAKE_LOCK_ID);
pollres = poll(mFDs, mFDCount, -1);
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
if (pollres <= 0) {
if (errno != EINTR) {
LOGW("select failed (errno=%d)/n", errno);
usleep(100000);
}
continue;
}
for(i = 1; i < mFDCount; i++) {
if(mFDs[i].revents) {
LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
if(mFDs[i].revents & POLLIN) {
res = read(mFDs[i].fd, &iev, sizeof(iev));
if (res == sizeof(iev)) {
LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
mDevices[i]->path.string(),
(int) iev.time.tv_sec, (int) iev.time.tv_usec,
iev.type, iev.code, iev.value);
*outDeviceId = mDevices[i]->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = iev.type;
*outScancode = iev.code;
if (iev.type == EV_KEY) {
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d/n",
iev.code, *outKeycode, *outFlags, err);
if (err != 0) {
*outKeycode = 0;
*outFlags = 0;
}
} else {
*outKeycode = iev.code;
}
*outValue = iev.value;
*outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
return true;
} else {
if (res<0) {
LOGW("could not get event (errno=%d)", errno);
} else {
LOGE("could not get event (wrong size: %d)", res);
}
continue;
}
}
}
}
...
}
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
int32_t* outValue, nsecs_t* outWhen)
{
...
while(1) {
// First, report any devices that had last been added/removed.
if (mClosingDevices != NULL) {
device_t* device = mClosingDevices;
LOGV("Reporting device closed: id=0x%x, name=%s/n",
device->id, device->path.string());
mClosingDevices = device->next;
*outDeviceId = device->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = DEVICE_REMOVED;
delete device;
return true;
}
if (mOpeningDevices != NULL) {
device_t* device = mOpeningDevices;
LOGV("Reporting device opened: id=0x%x, name=%s/n",
device->id, device->path.string());
mOpeningDevices = device->next;
*outDeviceId = device->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = DEVICE_ADDED;
return true;
}
release_wake_lock(WAKE_LOCK_ID);
pollres = poll(mFDs, mFDCount, -1);
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
if (pollres <= 0) {
if (errno != EINTR) {
LOGW("select failed (errno=%d)/n", errno);
usleep(100000);
}
continue;
}
for(i = 1; i < mFDCount; i++) {
if(mFDs[i].revents) {
LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
if(mFDs[i].revents & POLLIN) {
res = read(mFDs[i].fd, &iev, sizeof(iev));
if (res == sizeof(iev)) {
LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
mDevices[i]->path.string(),
(int) iev.time.tv_sec, (int) iev.time.tv_usec,
iev.type, iev.code, iev.value);
*outDeviceId = mDevices[i]->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = iev.type;
*outScancode = iev.code;
if (iev.type == EV_KEY) {
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d/n",
iev.code, *outKeycode, *outFlags, err);
if (err != 0) {
*outKeycode = 0;
*outFlags = 0;
}
} else {
*outKeycode = iev.code;
}
*outValue = iev.value;
*outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
return true;
} else {
if (res<0) {
LOGW("could not get event (errno=%d)", errno);
} else {
LOGE("could not get event (wrong size: %d)", res);
}
continue;
}
}
}
}
...
}
對于按鍵事件,調用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代碼
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;
}
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;
readEvent(ev);
send = preprocessEvent(di, ev);
addLocked(di, curTime, ev.flags, ..., me);
}
}
};
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代碼
mQueue.getEvent
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)
在核心鍵盤驅動檔案 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上層可以識别的按鍵。
本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/sijiangong/archive/2009/08/04/4407193.aspx
Android輸入事件流程
轉載時請注明出處和作者聯系方式
文章出處:http://www.limodev.cn/blog
作者聯系方式:李先靜 <xianjimli at hotmail dot com>
EventHub對輸入裝置進行了封裝。輸入裝置驅動程式對使用者空間應用程式提供一些裝置檔案,這些裝置檔案放在/dev/input裡面。
EventHub掃描/dev/input下所有裝置檔案,并打開它們。
|
EventHub對外提供了一個函數用于從輸入裝置檔案中讀取資料。
|
對于按鍵事件,調用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,用于讀取輸入裝置事件。
|
readEvent調用hub->getEvent讀了取事件,然後轉換成JAVA的結構。
o 事件中轉線程
在frameworks/base/services/java/com/android/server/KeyInputQueue.java裡建立了一個線程,它循環的讀取事件,然後把事件放入事件隊列裡。
|
o 輸入事件分發線程
在frameworks/base/services/java/com/android/server/WindowManagerService.java裡建立了一個輸入事件分發線程,它負責把事件分發到相應的視窗上去。
mQueue.getEvent
dispatchKey/dispatchPointer/dispatchTrackball