InputManagerService源码分析一:准备工作
一、写在前面的话
作为一个应用程序开发,开始着手学习android源码,经常会被android海量的代码和庞大的知识图谱吓得望而却步,止步不前。放弃学习后,偶尔又心血来潮,重新出发。继续与放弃、快乐与痛苦,就这样循环交替、往复进行。每一次的重温也是对知识图谱的重新构建,对UML类图、时序图的完善。总归缺少些文字记录、缺少系统性的阐述,最近有构思将7年内关于android源码的学习与理解整理写成博客,记录下来。网上相关的介绍文章很多,总有些老生常谈,也多亏网上资料丰富,才能解决一个又一个源码阅读路上的拦路虎。最近正好有个项目需要客制化InputManagerService,遂决定以InputManagerService为始,记录下源码学习。学习InputManagerService首先需要了解:输入设备数据的获取、输入设备的挂载与删除。
InputManagerService主要职责是负责android系统输入设备事件(按压、滑动)的分发。主要功能可以分两部分介绍:1)第一部分就是事件的监听,2)第二部分就是事件的分发。本章主要介绍InputManagerService C++层关于事件读取、设备增加使用的Linux机制—— epoll、iNotify。
二、epoll读取android Input事件
android Input系统C++层使用epoll监听设备输入事件的变化。本段落主要介绍通过在使用Linux epoll机制,获取android应用屏幕输入。
本文使用的硬件环境是芯驰x9h,android软件版本为android 10。可以通过adb shell进入android系统,通过ls命令
ls /dev/input
获取系统输入设备,测试机器上显示如下
x9hp_ref:/dev/input # ls
event0 event1
我们的设备有两个设备event0 event,通过getevent命令可以获取设备输入详细信息
127|x9hp_ref:/dev/input # getevent
add device 1: /dev/input/event1
name: "Semidrive Safe TouchScreen"
add device 2: /dev/input/event0
name: "Semidrive Safe TouchScreen"
从上述信息,可知设备主要有两块屏幕。接下来我们主要介绍通过监听event0设备获取屏幕数据。
2.1 获取屏幕摄入设备句柄
通过linux API open获取屏幕设备文件的句柄。
#define DEVPATH "/dev/input/event0"
fd = open(DEVPATH, O_RDONLY);
2.2 通过ioctl获取设备信息
获取了屏幕设备文件句柄后,可以通过ioctl接口获取屏幕设备的相关信息:设备名称、版本号等。
char buffer[80];
if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
LOGE("could not get device name\n");
} else {
buffer[sizeof(buffer) - 1] = '\0';
}
LOGE("device name : %s\n", buffer);
if (ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
LOGE("could not get location\n");
} else {
buffer[sizeof(buffer) - 1] = '\0';
}
LOGE("location is : %s\n", buffer);
struct input_id inputId;
if (ioctl(fd, EVIOCGID, &inputId)) {
LOGE("could not get device input id\n");
} else {
LOGE("device bustype: %d,product: %d,vendor: %d,version: %d", inputId.bustype,
inputId.product, inputId.vendor, inputId.version);
}
uint8_t keyBitmap[(KEY_MAX + 1) / 8];
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBitmap)), sizeof(keyBitmap));
LOGE("keybitmask :%0x",keyBitmap[0]);
2.3 创建屏幕设备句柄的epoll监听
android选择epoll监听Input设备输入信息。
int epoll_fd = epoll_create(MAX_EVENTS);
if (epoll_fd == -1) {
LOGE("epoll create failed");
return 0;
}
int opts = fcntl(fd, F_GETFL);
if (opts < 0) {
LOGE("fcntl F_GETFL error: %s fd: %d", strerror(errno), fd);
return -1;
}
opts |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, opts) < 0) {
LOGE("fcntl F_SETFL error: %s", strerror(errno));
return -1;
}
struct epoll_event ev;
int ret;
ev.events = EPOLLIN;
ev.data.fd = fd;
do {
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
} while (ret < 0 && errno == EINTR);
2.4 修改挂载设备的读取权限
由于seLinux机制,非系统应用无法读取/dev/input/event0事件。通过命令可以看到文件event0的权限属性为
crw-rw---- 1 root input 13, 64 1970-01-01 08:00 event0
修改event0属性
crw-rw-rw- 1 root input 13, 64 1970-01-01 08:00 event0
可以读到屏幕数据。
2.5 通过epoll_wait获取屏幕输入数据
int n = 0;
struct input_event event;
LOGD("run...");
while (1) {
n = epoll_wait(epoll_fd, events, MAX_EVENTS, EPOLL_SLEEP);
if (n == -1) {
LOGE("epoll wait error %s", strerror(errno));
continue;
}
for (int i = 0; i < n; ++i) {
if (events[i].data.fd == fd) {
res = read(fd, &event, sizeof(event));
if (res < (int) sizeof(event)) {
LOGE("could not get event");
}
}
LOGE("\n%04x %04x %08x\n", event.type, event.code, event.value);
}
}
可以读到屏幕按压、滑动、抬起事件如下
08-25 13:58:03.286 5233-5262/com.example.blan.nativeandroid I/input: 0003 0035 00000265
0003 0036 00000202
0003 0000 00000265
0003 0001 00000202
0000 0000 00000000
08-25 13:58:03.299 5233-5262/com.example.blan.nativeandroid I/input: 0003 0035 00000260
0003 0036 0000021f
0003 0000 00000260
0003 0001 0000021f
0000 0000 00000000
08-25 13:58:03.312 5233-5262/com.example.blan.nativeandroid I/input: 0003 0035 00000259
0003 0036 00000238
0003 0000 00000259
0003 0001 00000238
0000 0000 00000000
08-25 13:58:03.326 5233-5262/com.example.blan.nativeandroid I/input: 0003 0039 ffffffff
0001 014a 00000000
三、inotify监听文件的变化
3.1 添加指定目录下的文件添加、删除监听
#define FILE_PATH "/system/etc/"
int mNotifyFd;
int mFileWatchFd;
int mEpollFd;
mNotifyFd = inotify_init();
mFileWatchFd = inotify_add_watch(mNotifyFd, FILE_PATH, IN_DELETE | IN_CREATE);
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN;
event.data.fd = mNotifyFd;
int epoll_add = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mNotifyFd, &event);
LOGD("mNotifyFd file fd: %d", mNotifyFd);
LOGD("observe file fd: %d", mFileWatchFd);
LOGD("mEpollFd file : %d", mEpollFd);
LOGD("epoll_add result %d", epoll_add);
struct epoll_event eventItem;
while (1) {
int pollResult = epoll_wait(mEpollFd, &eventItem, 16, 1000);
LOGD("result is %d and eventItem.data.fd is %d\n", pollResult, eventItem.data.fd);
if (eventItem.data.fd == mNotifyFd && (eventItem.events & EPOLLIN)) {
readNotifyLocked();
} else {
LOGD("unKnow event\n");
}
}
3.2 添加设备信息解析
void readNotifyLocked() {
char event_buf[512];
int res;
int event_size;
int event_pos = 0;
struct inotify_event *notify_event;
LOGD("notify file change\n");
res = read(mNotifyFd, event_buf, sizeof(event_buf));
if (res < (int) sizeof(*notify_event)) {
LOGD("could no get event\n");
return;
}
while (res >= (int) sizeof(*notify_event)) {
notify_event = (struct inotify_event *) (event_buf + event_pos);
if (notify_event->len) {
if (notify_event->wd == mFileWatchFd) {
if (notify_event->mask & IN_CREATE) {
LOGD("notify add file name is: %s\n", notify_event->name);
} else {
LOGD("notify remove file name is: %s\n", notify_event->name);
}
} else {
break;
}
} else {
break;
}
event_size = sizeof(*notify_event) + notify_event->len;
res -= event_size;
event_pos += event_size;
LOGD("next loop");
}
}
在/etc/system目录下创建blan.txt文件,应用程序收到通知,通知日志如下:
08-25 14:09:01.207 5308-5334/com.example.blan.nativeandroid I/Notify Test: notify file change
notify add file name is: blan.txt
next loop
可以获取到新增的文件的名称,删除亦可收到相似的通知。通过string拼接,可以获取新增、删除设备挂载路径。继而使用epoll接口,可以添加、删除设备的输入监听。
四、总结
InputManagerService C++层的事件监听已经介绍完毕,下一章介绍InputManagerService初始化流程。