有两种思路来解析Android Input 子系统的代码(注一),一是从应用的事件获取开始,自上而下的解析Input相关代码的功能及调用关系(从 注二 的内容可知,输入事件是通过View类中的事件监听接口获取的);二是从内核驱动开始,自下而上找出调用关系;本文采用第二种思路。
1. 内核输入驱动
内核输入驱动包括两个类别协同工作的驱动,即输入设备驱动和输入事件驱动,设备驱动负责与输入的硬件设备进行通讯,而事件驱动提供供用户空间应用访问的接口(即 /dev/input/xxx 设备节点)。因为输入驱动已提供了标准化的事件接口,新写一个事件驱动相对来说较为简单,大致步骤为:
a). 初始化时调用标准函数input_allocate_device分配一个input_dev结构体并设置要产生的事件及事件码
b). 调用input_register_device来注册一个输入事件驱动
c). 调用input_report_xxx(rel/abs…)和input_sync来上报事件。
2. EventHub
应用程序获得输入事件的方法为打开输入设备节点并调用poll来查询和read来读取输入事件。在Android中,读取输入事件在 frameworks/base/services/input/EventHub.cpp中实现。EventHub类中有两个关键成员函数: openDeviceLocked和getEvents,它们的作用分别为打开设备节点和读取输入事件。
openDeviceLocked实现的具体功能为:
- 打开设备节点
- 获得设备名
- 检查设备是否在不支持设备之列
- 获得设备版本号和标识符
- 获得设备唯一的ID
- 为使用poll函数,将文件描述符设为非阻塞类型
- 获得设备类别(输入设备类别在EventHub.h中定义,具体如下)
INPUT_DEVICE_CLASS_KEYBOARD 键盘
INPUT_DEVICE_CLASS_ALPHAKEY 数字键盘
INPUT_DEVICE_CLASS_TOUCH 触摸屏(单点或多点)
INPUT_DEVICE_CLASS_CURSOR 光标(如轨迹球或鼠标)
INPUT_DEVICE_CLASS_TOUCH_MT 多点触摸屏
INPUT_DEVICE_CLASS_DPAD 方向键
INPUT_DEVICE_CLASS_GAMEPAD 游戏手柄
INPUT_DEVICE_CLASS_SWITCH 开关键
INPUT_DEVICE_CLASS_JOYSTICK 操纵杆
INPUT_DEVICE_CLASS_EXTERNAL 外部设备
getEvents调用read函数来读取输入设备上报的事件并直接将事件信息填充进RawEvent结构体数组缓冲区(即传入参数buffer)中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
size_t
EventHub::getEvents(
int
timeoutMillis, RawEvent* buffer,
size_t
bufferSize) {
...
for
(;;) {
....
// Grab the next input event.
bool
deviceChanged =
false
;
while
(mPendingEventIndex < mPendingEventCount) {
const
struct
epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
if
(eventItem.data.u32 == EPOLL_ID_INOTIFY) {
if
(eventItem.events & EPOLLIN) {
mPendingINotify =
true
;
}
else
{
LOGW(
"Received unexpected epoll event 0x%08x for INotify."
, eventItem.events);
}
continue
;
}
if
(eventItem.data.u32 == EPOLL_ID_WAKE) {
if
(eventItem.events & EPOLLIN) {
LOGV(
"awoken after wake()"
);
awoken =
true
;
char
buffer[16];
ssize_t nRead;
do
{
nRead = read(mWakeReadPipeFd, buffer,
sizeof
(buffer));
}
while
((nRead == -1 &&
errno
== EINTR) || nRead ==
sizeof
(buffer));
}
else
{
LOGW(
"Received unexpected epoll event 0x%08x for wake read pipe."
,
eventItem.events);
}
continue
;
}
ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
if
(deviceIndex < 0) {
LOGW(
"Received unexpected epoll event 0x%08x for unknown device id %d."
,
eventItem.events, eventItem.data.u32);
continue
;
}
Device* device = mDevices.valueAt(deviceIndex);
if
(eventItem.events & EPOLLIN) {
int32_t readSize = read(device->fd, readBuffer,
sizeof
(
struct
input_event) * capacity);
if
(readSize == 0 || (readSize < 0 &&
errno
== ENODEV)) {
// Device was removed before INotify noticed.
LOGW(
"could not get event, removed? (fd: %d size: %d bufferSize: %d capacity: %d errno: %d)\n"
,
device->fd, readSize, bufferSize, capacity,
errno
);
deviceChanged =
true
;
closeDeviceLocked(device);
}
else
if
(readSize < 0) {
if
(
errno
!= EAGAIN &&
errno
!= EINTR) {
LOGW(
"could not get event (errno=%d)"
,
errno
);
}
}
else
if
((readSize %
sizeof
(
struct
input_event)) != 0) {
LOGE(
"could not get event (wrong size: %d)"
, readSize);
}
else
{
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
size_t
count =
size_t
(readSize) /
sizeof
(
struct
input_event);
for
(
size_t
i = 0; i < count; i++) {
const
struct
input_event& iev = readBuffer[i];
LOGV(
"%s got: t0=%d, t1=%d, type=%d, code=%d, value=%d"
,
device->path.string(),
(
int
) iev.
time
.tv_sec, (
int
) iev.
time
.tv_usec,
iev.type, iev.code, iev.value);
#ifdef HAVE_POSIX_CLOCKS
// Use the time specified in the event instead of the current time
// so that downstream code can get more accurate estimates of
// event dispatch latency from the time the event is enqueued onto
// the evdev client buffer.
//
// The event's timestamp fortuitously uses the same monotonic clock
// time base as the rest of Android. The kernel event device driver
// (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
// The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
// calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
// system call that also queries ktime_get_ts().
event->when = nsecs_t(iev.
time
.tv_sec) * 1000000000LL
+ nsecs_t(iev.
time
.tv_usec) * 1000LL;
LOGV(
"event time %lld, now %lld"
, event->when, now);
#else
event->when = now;
#endif
event->deviceId = deviceId;
event->type = iev.type;
event->scanCode = iev.code;
event->value = iev.value;
event->keyCode = AKEYCODE_UNKNOWN;
event->flags = 0;
if
(iev.type == EV_KEY && device->keyMap.haveKeyLayout()) {
status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code,
&event->keyCode, &event->flags);
LOGV(
"iev.code=%d keyCode=%d flags=0x%08x err=%d\n"
,
iev.code, event->keyCode, event->flags, err);
}
event += 1;
}
capacity -= count;
if
(capacity == 0) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
mPendingEventIndex -= 1;
break
;
}
}
}
else
{
LOGW(
"Received unexpected epoll event 0x%08x for device %s."
,
eventItem.events, device->identifier.name.string());
}
}
}
....
}
|
注一: 本文所使用Android代码的版本为: 4.0.3
注二: http://developer.android.com/guide/topics/ui/ui-events.html