Android(安卓)Keyboard/Touch Panel分析
fd = open(deviceName, O_RDWR);
if (fd >= 0) break;
usleep(100);
} 首先会打开传进来的设备. 然后会获取version, id等信息. if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
//fprintf(stderr, "could not get device name for %s, %s/n", deviceName, strerror(errno));
name[0] = '/0';
}
获取 driver name, 在这里也就是 /dev/input/evevnt0, 也就是要到 Driver 里面去读取. 这个名字很重要, 之后要与 keyboard map 相匹配. 这里返回的值是: name = "wayland_m_ebook_key_input" 为什么会返回这个值? 请看 event0 的 linux driver. wayland_m_ebook_keypad_probe() 函数中,有以下语句: gpio_key_input->name = "wayland_m_ebook_key_input". 所以这个值是在这个时候设置的。 int devid = 0;
while (devid < mNumDevicesById) {
if (mDevicesById[devid].device == NULL) {
break;
}
devid++;
}
if (devid >= mNumDevicesById) {
device_ent* new_devids = (device_ent*)realloc(mDevicesById,
sizeof(mDevicesById[0]) * (devid + 1));
if (new_devids == NULL) {
LOGE("out of memory");
return -1;
}
mDevicesById = new_devids;
mNumDevicesById = devid+1;
mDevicesById[devid].device = NULL;
mDevicesById[devid].seq = 0;
} 分配 new device, 将 device 信息保存至 mDeviceById[] 数组中. mNumDevicesById: device 的数量 mDevicesById: devive 的信息 new_mFDs = (pollfd*)realloc(mFDs, sizeof(mFDs[0]) * (mFDCount + 1));
new_devices = (device_t**)realloc(mDevices, sizeof(mDevices[0]) * (mFDCount + 1));
为 new_mFDs, mFDs 分配空间, 以备之后保存每个event(x)的fd. mFDs[mFDCount].fd = fd;
mFDs[mFDCount].events = POLLIN;
将 fd 放到 mFDs 数组中. // See if this is a keyboard, and classify it.
uint8_t key_bitmask[(KEY_MAX+1)/8];
memset(key_bitmask, 0, sizeof(key_bitmask));
LOGV("Getting keys...");
if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) {
//LOGI("MAP/n");
//for (int i=0; i<((KEY_MAX+1)/8); i++) {
// LOGI("%d: 0x%02x/n", i, key_bitmask[i]);
//}
for (int i=0; i<((BTN_MISC+7)/8); i++) {
if (key_bitmask[i] != 0) {
device->classes |= CLASS_KEYBOARD;
break;
}
}
if ((device->classes & CLASS_KEYBOARD) != 0) {
device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
if (device->keyBitmask != NULL) {
memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
} else {
delete device;
LOGE("out of memory allocating key bitmask");
return -1;
}
}
}
如果是 keyboard 的设备. if (test_bit(BTN_TOUCH, key_bitmask)) {
uint8_t abs_bitmask[(ABS_MAX+1)/8];
memset(abs_bitmask, 0, sizeof(abs_bitmask));
LOGV("Getting absolute controllers...");
if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0)
{
if (test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
device->classes |= CLASS_TOUCHSCREEN;
}
} } 继续分析 Android 是如何进行 keyboard 映射的: char tmpfn[sizeof(name)];
char keylayoutFilename[300]; // a more descriptive name
device->name = name; // replace all the spaces with underscores
strcpy(tmpfn, name);
for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
*p = '_'; // find the .kl file we need for this device
const char* root = getenv("ANDROID_ROOT");
snprintf(keylayoutFilename, sizeof(keylayoutFilename),
"%s/usr/keylayout/%s.kl", root, tmpfn);
bool defaultKeymap = false;
if (access(keylayoutFilename, R_OK)) {
snprintf(keylayoutFilename, sizeof(keylayoutFilename),
"%s/usr/keylayout/%s", root, "qwerty.kl");
defaultKeymap = true;
} ANDROID_ROOT 一般都设置为 /system tmpfn 也就是 name 中的内容。而 name = wayland_m_ebook_key_input 所以 keylayoutFilename = /system/usr/keylayout/wayland_m_ebook_key_input.kl. 这个文件保存有按键的信息。 可以在这个文件中修改按键的键值。 如果没有这个文件则会去读 define 的 qwerty.kl. device->layoutMap->load(keylayoutFilename); load /system/usr/keylayout/wayland_m_ebook_key_input.kl 这个文件进行分析。 KeyLayoutMap::load 在 KeyLayoutMap.cpp 中实现。 把按键的映射关系保存在 :KeyedVector<int32_t,Key> m_keys; 中。 iii. EventHub::getEvent() 主要通过 read() 函数读取按键事件 及进行 Map 键值映射。
二、再来看看 jni 层 com_android_server_KeyInputQueue.cpp 看看其中几行的代码: static JNINativeMethod gInputMethods[] = {
/* name, signature, funcPtr */
{ "readEvent", "(Landroid/view/RawInputEvent;)Z",
(void*) android_server_KeyInputQueue_readEvent }, 可以看出, 上层(即 framework层)调用的接口是 readEvent, 实现函数是本文件的android_server_KeyInputQueue_readEvent(). 这个函数调用了 getEvent 读取事件。也就是 EventHub.cpp 中的 EventHub::getEvent(). readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。 三、再看看 jni 层对应的 java 层(经常封装成 .jar 档案) KeyInputQueue.java 在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个 InputDeviceReader 线程,它循环的读取事件,然后把事件放入事件队列里,包括 key / touch panel.
四、事件分发
输入事件分发线程 在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 专门用来从设备读取按键事件.
代码:
publicvoid run(){
// 在循环中调用: readEvent(ev);
...
send= preprocessEvent(di,ev);
//实际调用的是KeyQ类的preprocessEvent函数
...
intkeycode= rotateKeyCodeLocked(ev.keycode);
int[]map= mKeyRotationMap;
for(inti=0;i<N;i+=2 ){
if(map== keyCode)
returnmap[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() 实际上调用的是 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中使用WebView, WebChromeClient和WebViewClient加载网页
- Android开发实践 事件处理机制 全面分析
- Android(安卓)home键和back键区别
- [转]JS调用Android里面的方法,Android调用JS里面的方法
- Android横竖屏切换
- Android(安卓)Studio按钮响应事件(一)
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- 横竖屏切换问题
- Android(安卓)模块化完整方案实现