4. Android触摸事件转发至QNX

任务目标

在QNX与Android共存的座舱系统中,触摸屏通常由QNX侧管理(作为安全关键域),而Android侧运行IVI应用。本案例实现Android侧触摸事件通过共享内存通道转发至QNX侧,使QNX侧应用能够感知并响应Android界面的触摸操作,完成跨系统交互闭环。

方案选型与架构

采用共享内存 + 事件标志(eventfd)通知方案:Android侧通过ION/ashmem分配共享内存,将触摸事件结构体写入环形缓冲区;QNX侧通过shm_open映射同一物理内存,并监听事件通知。相比Socket IPC,共享内存延迟更低(微秒级),适合高频触摸事件流。

Android侧(非安全域) QNX侧(安全域) InputReader 触摸事件源 TouchForwarder 本案例核心逻辑 环形缓冲区写入 TouchConsumer QNX侧接收线程 事件分发 QNX应用回调 环形缓冲区读取 共享内存 /touch_shm 环形缓冲 + 状态区 eventfd 写入通知(QNX侧poll监听)

共享内存协议定义

触摸事件结构体与环形缓冲区布局需在Android与QNX两侧保持一致,定义如下(C语言头文件,两侧共用):

// touch_protocol.h(Android与QNX共用)
#define TOUCH_SHM_NAME  "/touch_shm"
#define TOUCH_RING_SIZE 64          // 环形缓冲区槽位数(2的幂)
#define TOUCH_EVENT_MAX 10          // 单次最多触点

typedef struct {
    int32_t type;       // 0=DOWN, 1=MOVE, 2=UP, 3=CANCEL
    int32_t action_idx; // 触点索引
    int64_t timestamp;  // 纳秒时间戳
    float x, y;         // 归一化坐标 [0.0, 1.0]
    float pressure;     // 压力值
} touch_point_t;

typedef struct {
    uint32_t point_count;
    touch_point_t points[TOUCH_EVENT_MAX];
} touch_event_t;

typedef struct {
    volatile uint32_t head;  // 写入端索引
    volatile uint32_t tail;  // 读取端索引
    touch_event_t slots[TOUCH_RING_SIZE];
} touch_ring_t;

Android侧实现(关键完整代码)

在Android系统服务中注册InputMonitor回调,将触摸事件写入共享内存环形缓冲区,并通过eventfd通知QNX侧。以下为最小可编译实现:

// TouchForwarder.cpp(Android侧,运行于system_server进程)
#include <android-base/unique_fd.h>
#include <cutils/ashmem.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
#include <utils/Log.h>
#include "touch_protocol.h"

namespace android {

class TouchForwarder : public InputMonitorInterface {
public:
    TouchForwarder() : ring_(nullptr), shm_fd_(-1), efd_(-1) {}

    bool init() {
        // 1. 创建共享内存(ashmem,大小=环形结构体)
        shm_fd_ = ashmem_create_region("touch_shm", sizeof(touch_ring_t));
        if (shm_fd_ < 0) return false;

        // 2. 映射到本进程地址空间
        void* addr = mmap(nullptr, sizeof(touch_ring_t),
                          PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd_, 0);
        if (addr == MAP_FAILED) return false;
        ring_ = static_cast<touch_ring_t*>(addr);
        memset(ring_, 0, sizeof(touch_ring_t));

        // 3. 创建eventfd通知句柄
        efd_ = eventfd(0, EFD_CLOEXEC);
        if (efd_ < 0) return false;

        // 4. 将fd信息写入系统属性供QNX侧获取(实际工程用binder传递)
        __system_property_set("sys.touch_shm_fd", std::to_string(shm_fd_).c_str());
        __system_property_set("sys.touch_efd", std::to_string(efd_).c_str());
        return true;
    }

    // InputMonitorInterface回调:每次触摸事件到达时被调用
    void onTouchEvent(const TouchEvent& ev) override {
        if (!ring_) return;

        // 构造协议事件
        touch_event_t out{};
        out.point_count = std::min(ev.pointCount, TOUCH_EVENT_MAX);
        for (uint32_t i = 0; i < out.point_count; i++) {
            out.points[i].type = static_cast<int32_t>(ev.type);
            out.points[i].action_idx = ev.pointId[i];
            out.points[i].timestamp = ev.timestamp;
            out.points[i].x = ev.x[i] / display_width_;   // 归一化
            out.points[i].y = ev.y[i] / display_height_;
            out.points[i].pressure = ev.pressure[i];
        }

        // 写入环形缓冲区(单生产者)
        uint32_t next = (ring_->head + 1) % TOUCH_RING_SIZE;
        if (next == ring_->tail) {
            ALOGW("Touch ring buffer full, dropping event");
            return;
        }
        ring_->slots[ring_->head] = out;
        __sync_synchronize();  // 确保数据写入完成
        ring_->head = next;

        // 通知QNX侧(写入eventfd计数)
        uint64_t one = 1;
        write(efd_, &one, sizeof(one));
    }

private:
    touch_ring_t* ring_;
    int shm_fd_;
    int efd_;
    uint32_t display_width_ = 1920;   // 实际从DisplayManager获取
    uint32_t display_height_ = 720;
};

}  // namespace android

QNX侧实现(关键完整代码)

QNX侧进程通过shm_open映射同一共享内存,使用poll()监听eventfd(通过资源管理器传递),读取触摸事件并回调应用层:

// touch_consumer.c(QNX侧,neutrino进程)
#include <sys/mman.h>
#include <sys/neutrino.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "touch_protocol.h"

static touch_ring_t* ring;
static int efd;

// 应用层回调(由集成方实现)
extern void on_touch_event(const touch_event_t* ev);

static void* consumer_thread(void* arg) {
    struct pollfd pfd = { .fd = efd, .events = POLLIN };

    while (1) {
        int ret = poll(&pfd, 1, -1);
        if (ret <= 0) continue;

        // 读取并清空eventfd计数
        uint64_t count;
        read(efd, &count, sizeof(count));

        // 消费环形缓冲区中所有待处理事件
        while (ring->tail != ring->head) {
            touch_event_t ev = ring->slots[ring->tail];
            __sync_synchronize();
            ring->tail = (ring->tail + 1) % TOUCH_RING_SIZE;
            on_touch_event(&ev);   // 分发到QNX应用
        }
    }
    return NULL;
}

int main(int argc, char** argv) {
    // 1. 打开共享内存(名称与Android侧一致)
    int shm_fd = shm_open(TOUCH_SHM_NAME, O_RDWR, 0666);
    if (shm_fd < 0) { perror("shm_open"); return 1; }

    ring = mmap(NULL, sizeof(touch_ring_t), PROT_READ | PROT_WRITE,
                MAP_SHARED, shm_fd, 0);
    if (ring == MAP_FAILED) { perror("mmap"); return 1; }

    // 2. 获取eventfd(实际工程通过资源管理器传递,此处用命令行参数)
    if (argc > 1) {
        efd = atoi(argv[1]);
    } else {
        fprintf(stderr, "Usage: %s <eventfd>\n", argv[0]);
        return 1;
    }

    // 3. 启动消费线程
    pthread_t tid;
    pthread_create(&tid, NULL, consumer_thread, NULL);
    pthread_join(tid, NULL);
    return 0;
}

编译与运行

Android侧:将TouchForwarder.cpp编译进system_server(或作为独立系统服务),在InputManagerService中注册该Monitor。需链接libinputflinger与libandroid_runtime。

QNX侧:使用qcc编译并运行:

qcc -Vgcc_ntox86_64 -o touch_consumer touch_consumer.c -lpthread
# 运行(eventfd号由Android侧通过属性/binder传递)
./touch_consumer 42

验证方法与通过标准

  1. 共享内存连通性:Android侧写入后,QNX侧执行pidin mem查看映射区域,确认物理地址一致。
  2. 事件转发验证:在QNX侧实现on_touch_event回调,将事件坐标打印到终端。在Android侧模拟触摸(input tap 500 300),观察QNX终端应输出对应坐标事件。
  3. 判定标准:连续执行10次触摸操作,QNX侧成功收到全部事件且坐标误差小于0.01(归一化),无事件丢失或乱序,即判定通过。

常见问题

  • 共享内存权限:Android SELinux需允许system_server访问ashmem区域;QNX侧需以root或具有PROCMGR_AID_MEM权限运行。
  • 缓存一致性:ARM架构下需使用__sync_synchronize()或dmb指令确保写入顺序,否则QNX侧可能读到旧数据。
  • 背压处理:若QNX侧消费不及时,环形缓冲区满时Android侧丢弃新事件并打印警告,实际工程可增加丢弃计数上报。