天天看点

Android输入系统(一)Android输入系统(一)

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

Android输入系统(一)Android输入系统(一)
缺点:(只适用如下情况:)
  • 适用于线程通信
  • 具有亲缘关系的进程间通信

如果我们要在两个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。

同样对于进程2也会有一个数组

此时如果我要把APP1进程里面的fd传递给APP2,此时就会先在APP2的进程的file__ruc数组中找出一个空项,然后让这个空项指向APP1对应的file__ruc[X]

使用binder传输file\__ruc[X]