Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 初級開發 >> Android事件處理:按鍵、觸摸屏和滾動球的一些實現細節

Android事件處理:按鍵、觸摸屏和滾動球的一些實現細節

編輯:初級開發

Init

zygote

system-server

Windowsmanager

UEventObserver

InputDeviceRead

InputDispatcher

DisplayEventThr

ActivityManager

EventHub:

而事件的傳入是從EventHub開始的,EventHub是事件的抽象結構,維護著系統設備的運行情況,設備類型包括Keyboard、 TouchScreen、TraceBall。它在系統啟動的時候會通過open_device方法將系統提供的輸入設備都增加到這個抽象結構中,並維護一個所有輸入設備的文件描述符,如果輸入設備是鍵盤的話還會讀取/system/usr/keylayout/目錄下對應鍵盤設備的映射文件,另外 getEvent方法是對EventHub中的設備文件描述符使用poll操作等侍驅動層事件的發生,如果發生的事件是鍵盤事件,則調用Map函數按照映射文件轉換成相應的鍵值並將掃描碼和鍵碼返回給KeyInputQueue。

KeyLayoutMap主要是讀取鍵盤映射文件並將鍵盤掃描碼和鍵碼進行轉換

frameworksasecorejniserver 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 ",

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

",

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) ", 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 ",

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 (frameworksaseservicesJavacomandroidserver)中有:

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

{

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 (frameworksaseservicesjni)中的:

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 (frameworksaselibsui)中的:

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 (frameworksaseservicesJavacomandroidserver):

的線程 Thread mThread = new Thread("InputDeviceReader") 本地調用:

readEvent(ev);讀取按鍵。readEvent 調用的是文件:

com_android_server_KeyInputQueue.cpp (frameworksaseservicesjni)中的函數:

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 (frameworksaselibsui) 文件中的函數:

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 (frameworksaselibsui)中的函數:

status_t KeyLayoutMap::load(const char* filename)通過解析 pxa27x-keypad.kl

把按鍵的映射關系保存在 :KeyedVector m_keys; 中。

當獲得按鍵事件以後調用:

status_t KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags)

由映射關系 KeyedVector m_keys 把掃描碼轉換成andorid上層可以識別的按鍵。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved