Android输入系统(一)
首先我们明白,在PC或者手机上我们都支持热插拔,比如现在有个键盘,现在键盘插入USB接口,就会检测到。那么系统怎么知道有设备插入,又怎么识别这个设备呢?
- Android输入系统一
- 设备识别的两种方式
- 储备知识一监测文件
- 使用inotify函数流程
- 测试方法
- 使用epoll函数流程
- 测试方法
- 储备知识二通信
- 使用socketpair函数流程
- 测试方法
设备识别的两种方式
一.hotplug机制
1.内核发现键盘接入或者拔出
2.启动一个进程(hotplug进程)
3.进程发送消息告诉输入系统
4.这个输入系统处理这个消息
二.inotify机制
系统检测/dev/input目录
发生变化进行对应处理
当然Android系统就是使用inotify机制,而使用inotify机制的核心是:
- inotify函数(用于监测目录中文件是否有删除,增加等)
- epoll函数(用于监测多个文件内容的变化)
- 有无数据读入
- 有无数据读出
储备知识一(监测文件)
使用inotify函数流程
@(参考frameworks\native\services\inputflinger\EventHub.cpp)
- fd=inotify_init()—用于初始化
- inotify_add_watch(目录名称,监测类型)—用于查看什么东西
- read(fd,…)—用于读取监测结果,返回值是inotify_event结构体
#include <unistd.h>
#include <stdio.h>
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>
int read_process_inotify_fd(int fd)
{
int res;
char event_buf[];
int event_size;
int event_pos = ;
struct inotify_event *event;
/* 此时read函数为阻塞式 */
res = read(fd, event_buf, sizeof(event_buf));
if(res < (int)sizeof(*event)) {
if(errno == EINTR)
return ;
printf("could not get event, %s\n", strerror(errno));
return -;
}
/*
* 读到的数据是1个或多个inotify_event
* 由于结构体的长度不一样
* 所以逐个处理
*/
while(res >= (int)sizeof(*event)) {
event = (struct inotify_event *)(event_buf + event_pos);
if(event->len) {
if(event->mask & IN_CREATE) {
printf("create file: %s\n", event->name);
} else {
printf("delete file: %s\n", event->name);
}
}
event_size = sizeof(*event) + event->len;
res -= event_size;
event_pos += event_size;
}
return ;
}
int main(int argc, char **argv)
{
int mINotifyFd;
int result;
if (argc != )
{
printf("Usage: %s <dir>\n", argv[]);
return -;
}
/* inotify_init */
mINotifyFd = inotify_init();
/* add watch */
result = inotify_add_watch(mINotifyFd, argv[], IN_DELETE | IN_CREATE);
/* read */
while ()
{
read_process_inotify_fd(mINotifyFd);
}
return ;
}
测试方法
$ gcc -o inotify inotify.c
$ mkdir tmp
$ ./inotify tmp &
$ echo > tmp/1
$ echo > tmp/2
$ rm tmp/1 tmp/2
使用epoll函数流程
@(参考frameworks\native\services\inputflinger\EventHub.cpp)
- epoll_create()—创建fd
- 对每个文件执行epoll_ctl(…,EPOLL_CTL_ADD,…)—表示监测
- 执行epoll_wait(等待某个文件可用)
- 不再想监测某文件,可以执行epoll_ctl(…,EPOLL_CTL_DEL,…)
#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#if 0
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
#endif
#define DATA_MAX_LEN 500
/* usage: epoll <file1> [file2] [file3] ... */
int add_to_epoll(int fd, int epollFd)
{
int result;
struct epoll_event eventItem;
memset(&eventItem, , sizeof(eventItem));
eventItem.events = EPOLLIN;//当文件有数据的时候就可以监测到
eventItem.data.fd = fd;
result = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &eventItem);
return result;
}
/*从epoll中删除需要监测的文件*/
void rm_from_epoll(int fd, int epollFd)
{
epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, NULL);
}
int main(int argc, char **argv)
{
int mEpollFd;
int i;
char buf[DATA_MAX_LEN];
// Maximum number of signalled FDs to handle at a time.
static const int EPOLL_MAX_EVENTS = ;
// The array of pending epoll events and the index of the next event to be handled.
struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
if (argc < )
{
printf("Usage: %s <file1> [file2] [file3] ...\n", argv[]);
return -;
}
/* epoll_create */
mEpollFd = epoll_create();
/* for each file:
* open it
* add it to epoll: epoll_ctl(...EPOLL_CTL_ADD...)
*/
for (i = ; i < argc; i++)
{
//int tmpFd = open(argv[i], O_RDONLY|O_NONBLOCK);
int tmpFd = open(argv[i], O_RDWR);
add_to_epoll(tmpFd, mEpollFd);
}
/* epoll_wait */
while ()
{
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -);
for (i = ; i < pollResult; i++)
{
printf("Reason: 0x%x\n", mPendingEventItems[i].events);
int len = read(mPendingEventItems[i].data.fd, buf, DATA_MAX_LEN);
buf[len] = '\0';
printf("get data: %s\n", buf);
//sleep(3);
}
}
return ;
}
测试方法
$ gcc -o -epoll epoll.c
$ mkfifo tmp/2
$ ./epoll tmp /1 tmp/2 tmp/3
$ echo aa>tmp/1
$ echo bb>tmp/2
储备知识二(通信)
首先思考一个问题,如果让你自己设计一个输入系统会怎么做呢?
- 首先创建一个进程,用于读取分发那些事件
- 写出应用程序,用于处理应用事件,并对进程状态做出回馈
@(可以看出读取进程和APP进程之间是需要双向通信)
此时我们知道Binder系统在Android中承担大部分的进程通信角色
但是在输入系统中,是不是照样适用呢?答案是否定的。
因为Binder是单向通信,Clinet发出指令,Server接收指令,Server不能主动
如果需要双向通信,Binder的任何一端口都是Server+Client,这样会使得系统很复杂。所以输入系统中用的是SocketPair
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TPn5UNZpmTwcGRPd3YUFGbkNjW1ZkMkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TO1kTMwETM4EjMxIDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
缺点:(只适用如下情况:)
- 适用于线程通信
- 具有亲缘关系的进程间通信
如果我们要在两个APP之间进行通讯,那就必须把其中的一个文件句柄比如fd2通过Binder传递给另一个APP
使用socketpair函数流程
@(参考frameworks\native\libs\input\InputTransport.cpp)
- pthread_create—创建线程1
- read—读数据: 线程1发出的数据
- sprintf—main thread向thread1 发出: Hello, thread1
- 子线程sprintf— 向 main线程发出: Hello, main thread
- read—读取数据(main线程发回的数据)
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SOCKET_BUFFER_SIZE (32768U)
void *function_thread1 (void *arg)
{
int fd = (int)arg;
char buf[];
int len;
int cnt = ;
while ()
{
/* 向 main线程发出: Hello, main thread */
len = sprintf(buf, "Hello, main thread, cnt = %d", cnt++);
write(fd, buf, len);
/* 读取数据(main线程发回的数据) */
len = read(fd, buf, );
buf[len] = '\0';
printf("%s\n", buf);
sleep();
}
return NULL;
}
int main(int argc, char **argv)
{
int sockets[];
socketpair(AF_UNIX, SOCK_SEQPACKET, , sockets);
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
/* 创建线程1 */
pthread_t threadID;
pthread_create(&threadID, NULL, function_thread1, (void *)sockets[]);
char buf[];
int len;
int cnt = ;
int fd = sockets[];
while()
{
/* 读数据: 线程1发出的数据 */
len = read(fd, buf, );
buf[len] = '\0';
printf("%s\n", buf);
/* main thread向thread1 发出: Hello, thread1 */
len = sprintf(buf, "Hello, thread1, cnt = %d", cnt++);
write(fd, buf, len);
}
}
测试方法
gcc -o socketpair socketpari.c -lpthread
./socketpari
既然我们上面说了,socketpair不能在不同APP之间通信,那么怎么做才达到在不同APP之间通信的目的呢?
@(要明白这个道理,就必须了解文件句柄与进程和线程的关系)
我们知道task_struct表示进程结构体,在进程结构体中有struct files_struct * files;
struct files_struct中有struct file __ruc **fd;数组这个数组的下标就是对应的fd。
此时如果我要把APP1进程里面的fd传递给APP2,此时就会先在APP2的进程的file__ruc数组中找出一个空项,然后让这个空项指向APP1对应的file__ruc[X]
同样对于进程2也会有一个数组
使用binder传输file\__ruc[X]