您的位置:首页 > 移动开发 > Android开发

Android+按键事件处理详解

2014-05-18 13:28 309 查看
http://blog.sina.com.cn/s/blog_896b1e670100xfuf.html

公司最近做一个按键触感就对这块研究了一番,以下是个人心得。

1.开始肯定先说的是驱动这块,硬件是软件服务的,在Android这块C和java交互,有两种方式:

1.1:驱动--JNI--服务-事件分发-上层应用处理。

1.2:上层直接调用通过lib库的方式实现,中间使用回调机制,这种方式在Camera中有,下次再详解。

先来看一下驱动按键映射部分的详解如下:

映射实际是由 KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置 文件 qwerty.kl 决定键值的映射关系。你可以通过修改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。

在frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文件中,向 JAVA提供了函数android_server_KeyInputQueue_readEvent,用于读取输入设备事件。

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;

}

2.下面要讲重头代码WindowManagerService.java (frameworks\base\services\java\com\android\server),这个服务有承上启下的作用,读取用户输入的信息,是通过创建一个InputDeviceReader线程,KeyQ构建时,会启动一个线程去读取用户消息,具体代码在KeyInputQueue.mThread,在构造函数中,mThread会start,接下来,接研究一下mThread.run:

//用户输入事件消息读取线程

Thread mThread = new Thread("InputDeviceReader") {

public void run() {

RawInputEvent ev = new RawInputEvent();

while (true) {//开始消息读取循环

try {

InputDevice di;

//本地方法实现,读取用户输入事件

readEvent(ev);

//根据ev事件进行相关处理

...

synchronized (mFirst) {//mFirst是keyQ队列头指针

...

addLocked(di, curTimeNano, ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);

...

}

}

}

}

3.在读取完用户输入的信息,在WindowManagerService中就要分发消息,具体实现是交给了InputDispatcherThread这个线程处理,下来详解InputDispatcherThread处理流程:

-->WindowManagerService.main

--mInputThread = new InputDispatcherThread();//创建一个消息分发线程,读取并处理mQueue中消息

InputDispatcherThread.run

-->windowManagerService.process{

...

while (true) {

// 从mQueue(KeyQ)获取一个用户输入事件,正上调用我上面提到的getEvent方法,若队列为空,线程阻塞挂起

QueuedEvent ev = mQueue.getEvent(

(int)((!configChanged && curTime < nextKeyTime)

? (nextKeyTime-curTime) : 0));

...

try {

if (ev != null) {

...

if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件

eventType = eventType((MotionEvent)ev.event);

} else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||

ev.classType == RawInputEvent.CLASS_TRACKBALL) {//键盘输入事件

eventType = LocalPowerManager.BUTTON_EVENT;

} else {

eventType = LocalPowerManager.OTHER_EVENT;//其他事件

}

...

switch (ev.classType) {

case RawInputEvent.CLASS_KEYBOARD:

...

dispatchKey((KeyEvent)ev.event, 0, 0);//键盘输入,派发key事件

mQueue.recycleEvent(ev);

break;

case RawInputEvent.CLASS_TOUCHSCREEN:

dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派发touch事件

break;

case RawInputEvent.CLASS_TRACKBALL:

dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滚轮事件,派发Trackball事件

break;

case RawInputEvent.CLASS_CONFIGURATION_CHANGED:

configChanged = true;

break;

default:

mQueue.recycleEvent(ev);//销毁事件

break;

}

}

} catch (Exception e) {

Slog.e(TAG,

"Input thread received uncaught exception: " + e, e);

}

}

}

4.KeyEvent事件的传递主要可以划分为三步:过滤器、View树、Activity.

  过滤器部分对应的是(frameworks/base/policy/base/phone/com/Android/internal/policy/impl/PhoneWindowManager.java)PhoneWindowManager.java中的interceptKeyTq和interceptKeyTi这两个方法。它们的代码可以在中看到。

这两个过滤器最大的不同就是interceptKeyTq用于RawEvent,而interceptKeyTi用于KeyEvent。

在一个没有实体键盘的机器上,Power键会被interceptKeyTq这个过滤器吃掉用来调用关机对话框或者使机器休眠。而Home键会被interceptKeyTi这个过滤器吃掉,用来把当前Activity切换到后台并把桌面程序切换到前台。所以,应用程序在View和Activity的onKeyDown/Up中是监听不到这两个按键的。除了这两个键以外的按键,都有机会继续前进。接下来,KeyEvent会先经过interceptKeyTi过滤器,如果这个过滤器不吃掉的话,就会继续前进,进入View树,如果没有被哪个View吃掉的话,最后进入到Activity的onKeyDown/Up方法中。

当一个KeyEvent经过上面的过程还没有被吃掉的话,系统就会利用它做一些定制的功能。比如音量键被系统用来调整声音,多媒体按键用来控制媒体播放,搜索键用来快速打开搜索功能,返回键用来退出当前Activity等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐