分析一下 Android 是如何读取按键及Touch Panel 的驱动的。主要在 $(ANDROID_DIR)/frameworks/base/libs/ui/EventHub.cpp 这个文件中,这是在 HAL 层,将一步步分析 Android 上层是如何接受事件的。 一, 先看一下 Android HAL Class EventHub 在$(ANDROID_DIR)/frameworks/base/include/ui/eventhub.h 定义. i. scan_dir(const char *dirname) // dirname = "/dev/input" 扫描 dirname 目录, 该目录下有 event0, event1 ..., 等设备. ii.open_device(devname); 打开 /dev/input/event0,/dev/input/event1 等设备. 这里以打开 /dev/input/event0 设备为例, 分析按键的底层处理. for (attempt = 0; attempt < 10; attempt++) {
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 专门用来从设备读取按键事件.

代码:

ThreadmThread = 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,...));

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中的一个虚函数,在处理按键事件之前的一个“预处理”。

更多相关文章

  1. Android(安卓)的OpenGL ES与EGL
  2. Android(安卓)NDK c调用java代码
  3. android button按键得到焦点和点击后改变背景图片
  4. 设备驱动-----Android关机流程总结
  5. Android(安卓)对话框(Dialog)【大全】
  6. 高级UI-事件传递
  7. Android(安卓)菜单(Menu)控件的使用
  8. Android(安卓)编程下 Touch 事件的分发和消费机制
  9. Android(安卓)EventBus 源码解析

随机推荐

  1. eclipse android 错误列表
  2. Android(安卓)更新升级下载 自定义Update
  3. Android中的界面布局之帧布局,相对布局
  4. android 获取路径目录方法以及判断目录是
  5. 关于Android(安卓)studio混淆遇到的问题
  6. Android(安卓)Studio 常见错误 之unable
  7. android定位的实现
  8. Android(安卓)Timer使用
  9. Android(安卓)resource linking failed.
  10. android重启流程