Android输入系统(一)

本文深入探讨了Android输入系统的实现原理,包括设备识别的两种方式:hotplug机制与inotify机制,详细介绍了inotify及epoll函数的应用场景与测试方法。此外,还分析了输入系统中的通信机制,解释了为何不采用Binder而选择SocketPair的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android输入系统(一)

首先我们明白,在PC或者手机上我们都支持热插拔,比如现在有个键盘,现在键盘插入USB接口,就会检测到。那么系统怎么知道有设备插入,又怎么识别这个设备呢?


设备识别的两种方式

一.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[512];
    int event_size;
    int event_pos = 0;
    struct inotify_event *event;

    /* 此时read函数为阻塞式 */  
    res = read(fd, event_buf, sizeof(event_buf));
    if(res < (int)sizeof(*event)) {
        if(errno == EINTR)
            return 0;
        printf("could not get event, %s\n", strerror(errno));
        return -1;
    }

    /* 
     * 读到的数据是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 0;
}

int main(int argc, char **argv)
{
    int mINotifyFd;
    int result;
    if (argc != 2)
    {
        printf("Usage: %s <dir>\n", argv[0]);
        return -1;
    }
    /* inotify_init */
    mINotifyFd = inotify_init();
    /* add watch */
    result = inotify_add_watch(mINotifyFd, argv[1], IN_DELETE | IN_CREATE);
    /* read */
    while (1)
    {
        read_process_inotify_fd(mINotifyFd);
    }
    return 0;
}

测试方法

$ 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, 0, 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 = 16;

    // 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 < 2)
    {
        printf("Usage: %s <file1> [file2] [file3] ...\n", argv[0]);
        return -1;
    }

    /* epoll_create */
    mEpollFd = epoll_create(8);

    /* for each file:
     * open it
     * add it to epoll: epoll_ctl(...EPOLL_CTL_ADD...)
     */
    for (i = 1; 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 (1)
    {

        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        for (i = 0; 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 0;
}

测试方法

$ 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

这里写图片描述|center

缺点:(只适用如下情况:)

  • 适用于线程通信
  • 具有亲缘关系的进程间通信

如果我们要在两个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[500];
    int len;
    int cnt = 0;

    while (1)
    {
        /* 向 main线程发出: Hello, main thread  */
        len = sprintf(buf, "Hello, main thread, cnt = %d", cnt++);
        write(fd, buf, len);

        /* 读取数据(main线程发回的数据) */
        len = read(fd, buf, 500);
        buf[len] = '\0';
        printf("%s\n", buf);

        sleep(5);
    }

    return NULL;
}


int main(int argc, char **argv)
{
    int sockets[2];

    socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    /* 创建线程1 */
    pthread_t threadID;
    pthread_create(&threadID, NULL, function_thread1, (void *)sockets[1]);


    char buf[500];
    int len;
    int cnt = 0;
    int fd = sockets[0];

    while(1)
    {
        /* 读数据: 线程1发出的数据 */
        len = read(fd, buf, 500);
        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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值