4. Android触摸事件转发至QNX
任务目标
在QNX与Android共存的座舱系统中,触摸屏通常由QNX侧管理(作为安全关键域),而Android侧运行IVI应用。本案例实现Android侧触摸事件通过共享内存通道转发至QNX侧,使QNX侧应用能够感知并响应Android界面的触摸操作,完成跨系统交互闭环。
方案选型与架构
采用共享内存 + 事件标志(eventfd)通知方案:Android侧通过ION/ashmem分配共享内存,将触摸事件结构体写入环形缓冲区;QNX侧通过shm_open映射同一物理内存,并监听事件通知。相比Socket IPC,共享内存延迟更低(微秒级),适合高频触摸事件流。
共享内存协议定义
触摸事件结构体与环形缓冲区布局需在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
验证方法与通过标准
- 共享内存连通性:Android侧写入后,QNX侧执行
pidin mem查看映射区域,确认物理地址一致。 - 事件转发验证:在QNX侧实现
on_touch_event回调,将事件坐标打印到终端。在Android侧模拟触摸(input tap 500 300),观察QNX终端应输出对应坐标事件。 - 判定标准:连续执行10次触摸操作,QNX侧成功收到全部事件且坐标误差小于0.01(归一化),无事件丢失或乱序,即判定通过。
常见问题
- 共享内存权限:Android SELinux需允许system_server访问ashmem区域;QNX侧需以root或具有
PROCMGR_AID_MEM权限运行。 - 缓存一致性:ARM架构下需使用
__sync_synchronize()或dmb指令确保写入顺序,否则QNX侧可能读到旧数据。 - 背压处理:若QNX侧消费不及时,环形缓冲区满时Android侧丢弃新事件并打印警告,实际工程可增加丢弃计数上报。