资讯动态

19、InputEventReceiver的JNI层:nativeInit()与nativeConsumeEvent(),与C++层的交互

发布时间:2026/10/8 7:25:37 来源:尧图企业网站定制
19.1 nativeInit()创建C层的InputEventReceiver先看nativeInit()。Java层调用它的时候传了一个InputChannel的引用进来。这个InputChannel说白了就是一块共享内存加上一对socket文件描述符。JNI层的实现在android_view_InputEventReceiver.cpp里。我直接贴核心代码static jlong nativeInit(JNIEnv* env, jclass clazz, jobject inputChannelObj, jobject messageQueueObj) { spInputChannel inputChannel android_view_InputChannel_getInputChannel(env, inputChannelObj); spLooper looper android_os_MessageQueue_getLooper(env, messageQueueObj); spNativeInputEventReceiver receiver new NativeInputEventReceiver(inputChannel, looper); status_t status receiver-initialize(); if (status) { // 初始化失败抛出异常 jniThrowRuntimeException(env, Failed to initialize input event receiver.); return 0; } // 把C对象的指针存到Java层的long字段里 receiver-incStrong((void*)nativeInit); return reinterpret_castjlong(receiver.get()); }嗯这里要注意几个关键点InputChannel从Java层传过来的里面封装了socket pair。一个用于发送一个用于接收。Looper从MessageQueue里拿到的。这个Looper负责监听socket的可读事件。NativeInputEventReceiver这才是真正的C层接收器。它继承自LooperCallback可以注册到Looper的监听列表里。我在项目中遇到过一个问题如果InputChannel传了个空指针进来nativeInit会直接崩溃。后来我们加了一层判空保护才把这个问题堵住。所以你们写代码时记得对JNI传参做校验。注意nativeInit返回的是一个long值这个值就是C对象的地址。Java层把它当成一个指针来用。如果这个值被篡改或者丢失整个事件分发系统就会崩掉。我曾经见过有人用反射去改这个值结果系统直接挂掉——千万别这么干。19.2 NativeInputEventReceiver的初始化接着看initialize()方法。它干了三件事把InputChannel的socket文件描述符拿出来注册到Looper的监听队列里监听可读事件设置回调函数为NativeInputEventReceiver自身代码大概长这样status_t NativeInputEventReceiver::initialize() { int receiveFd mInputChannel-getReceivePipeFd(); // 注册到Looper监听可读事件 mLooper-addFd(receiveFd, 0, Looper::EVENT_INPUT, this, NULL); return OK; }这里有个细节Looper::EVENT_INPUT表示只监听可读事件。为什么因为InputChannel是单向的——Java层只负责读不负责写。写操作由InputDispatcher负责。你想想看这其实是一个典型的生产者-消费者模型。InputDispatcher是生产者往socket里写事件数据。InputEventReceiver是消费者从socket里读事件数据。Looper就是那个中间人负责通知消费者有货了。19.3 nativeConsumeEvent()从底层捞事件好现在Looper通知Java层说socket可读了。Java层会调用nativeConsumeEvent()来真正读取事件数据。看JNI实现static jboolean nativeConsumeEvent(JNIEnv* env, jclass clazz, jlong receiverPtr, jint seqId, jobject eventObj, jboolean consumeBatches) { spNativeInputEventReceiver receiver reinterpret_castNativeInputEventReceiver*(receiverPtr); InputEvent* event nullptr; bool consumed false; // 核心从InputChannel里读取事件 status_t status receiver-consumeEvent(env, seqId, eventObj, consumeBatches, event, consumed); if (status) { // 读取失败返回false return JNI_FALSE; } return consumed ? JNI_TRUE : JNI_FALSE; }这里有个关键点consumeEvent()方法会从InputChannel的socket里读取原始字节流然后解析成C层的InputEvent对象。接着通过JNI调用把C对象的数据填充到Java层的InputEvent对象里。说白了这就是一个反序列化的过程。底层传过来的是二进制数据JNI层负责把它变成Java对象。核心流程InputDispatcher往socket里写事件数据二进制Looper检测到socket可读回调NativeInputEventReceiverNativeInputEventReceiver调用consumeEvent()读取数据JNI层把C的InputEvent转换成Java的InputEventJava层拿到事件开始分发19.4 C层的consumeEvent()实现我们再看C层的consumeEvent()具体干了什么status_t NativeInputEventReceiver::consumeEvent(JNIEnv* env, jint seqId, jobject eventObj, bool consumeBatches, InputEvent** outEvent, bool* outConsumed) { // 1. 从InputChannel读取事件 InputEvent* event; status_t status mInputChannel-receiveMessage(event); if (status) { return status; } // 2. 把C事件转换成Java事件 if (event-getType() AINPUT_EVENT_TYPE_MOTION) { // 处理MotionEvent MotionEvent* motionEvent static_castMotionEvent*(event); // ... 填充Java层的MotionEvent对象 } else if (event-getType() AINPUT_EVENT_TYPE_KEY) { // 处理KeyEvent KeyEvent* keyEvent static_castKeyEvent*(event); // ... 填充Java层的KeyEvent对象 } // 3. 标记事件已消费 *outEvent event; *outConsumed true; return OK; }嗯这里有个坑。我曾经遇到过一个问题如果socket里的事件数据损坏了receiveMessage()会返回错误码。但JNI层没有做充分的错误处理导致Java层拿到一个空事件然后空指针崩溃。后来我们在JNI层加了异常捕获才把这个坑填上。避坑指南在JNI层做数据校验非常重要。不要假设底层传过来的数据一定是合法的。我曾经在生产环境遇到过因为InputChannel的socket缓冲区被写坏导致整个系统输入卡死的情况。加一层校验能省很多事。19.5 事件消费后的反馈事件被消费后Java层会调用finishInputEvent()来通知底层这个事件我用完了你可以处理下一个了。这个方法的JNI实现会调用C层的finishEvent()void NativeInputEventReceiver::finishEvent(InputEvent* event, bool handled) { // 把消费结果写回InputChannel mInputChannel-sendReply(event-getId(), handled); }这个反馈很重要。InputDispatcher会根据这个反馈来决定事件的后续处理。比如如果一个触摸事件没有被任何View消费InputDispatcher可能会把它转给下一个窗口。你想想看整个流程其实是一个闭环InputDispatcher写事件 → socket → NativeInputEventReceiver读事件 → JNI转Java → Java层分发 → 消费结果写回socket → InputDispatcher收到反馈这个闭环保证了事件分发的可靠性和有序性。我在做性能优化时曾经尝试过跳过这个反馈环节结果发现事件会乱序导致触摸跟踪完全错乱。所以这个闭环不能省。19.6 总结好了我们来理一下nativeInit()和nativeConsumeEvent()的核心要点方法作用关键点nativeInit()创建C层的NativeInputEventReceiver注册到LooperInputChannel Looper 指针存储nativeConsumeEvent()从InputChannel读取事件转换成Java对象反序列化 JNI数据填充finishInputEvent()反馈事件消费结果给InputDispatcher闭环反馈保证有序性我个人觉得理解JNI层的关键在于理解它只是一个数据搬运工。它不负责业务逻辑只负责把数据从C层搬到Java层再把结果搬回去。但就是这个搬运过程最容易出问题——数据类型转换、内存管理、异常处理每一个环节都可能翻车。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑