Android7 Input(八)App Input事件接收器InputEventReceiver

概述

        上一个章节,我们讲解了App如何使用InputChannel通道与input系统服务建立通信的桥梁的过程,本章我们讲述App如何从input系统服务中获取上报的输入事件,也就是我们本章讲述的InputEventReceiver。

本文涉及的源码路径

frameworks/base/core/java/android/view/InputChannel.java

frameworks/base/core/java/android/view/ViewRootImpl.java

frameworks/base/core/jni/android_view_InputChannel.cpp

frameworks/base/core/jni/android_view_InputEventReceiver.cpp

frameworks/base/core/jni/com_android_server_input_InputManagerService.cpp

frameworks/base/services/core/java/com/android/server/wm/Session.java

frameworks/base/core/java/android/view/WindowState.java

WindowInputEventReceiver事件接收器

上一章节,我们讲述了当App初始化完成InputChannel后,会创建一个事件接收器,如下所示:

mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());

其中 WindowInputEventReceiver是InputEventReceiver的子类,实现如下所示:

final class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {enqueueInputEvent(event, this, 0, true);}@Overridepublic void onBatchedInputEventPending() {if (mUnbufferedInputDispatch) {super.onBatchedInputEventPending();} else {scheduleConsumeBatchedInput();}}@Overridepublic void dispose() {unscheduleConsumeBatchedInput();super.dispose();}}

从中我们可以知道,核心实现是InputEventReceiver,因此接下来我们重点讲述InputEventReceiver的实现。

InputEventReceiver 输入事件接收器

InputEventReceiver的类,我们主要讲述核心方法的实现,如下所示:

public abstract class InputEventReceiver {......private long mReceiverPtr;private InputChannel mInputChannel;private MessageQueue mMessageQueue;// Map from InputEvent sequence numbers to dispatcher sequence numbers.private final SparseIntArray mSeqMap = new SparseIntArray();private static native long nativeInit(WeakReference<InputEventReceiver> receiver,InputChannel inputChannel, MessageQueue messageQueue);private static native void nativeDispose(long receiverPtr);private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,long frameTimeNanos);public InputEventReceiver(InputChannel inputChannel, Looper looper) {......mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);}public void onInputEvent(InputEvent event) {finishInputEvent(event, false);}public final void finishInputEvent(InputEvent event, boolean handled) {if (event == null) {throw new IllegalArgumentException("event must not be null");}if (mReceiverPtr == 0) {Log.w(TAG, "Attempted to finish an input event but the input event "+ "receiver has already been disposed.");} else {int index = mSeqMap.indexOfKey(event.getSequenceNumber());if (index < 0) {Log.w(TAG, "Attempted to finish an input event that is not in progress.");} else {int seq = mSeqMap.valueAt(index);mSeqMap.removeAt(index);nativeFinishInputEvent(mReceiverPtr, seq, handled);}}event.recycleIfNeededAfterDispatch();}// Called from native code.@SuppressWarnings("unused")private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}// Called from native code.@SuppressWarnings("unused")private void dispatchBatchedInputEventPending() {onBatchedInputEventPending();}public static interface Factory {public InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);}
}

InputEventReceiver的初始化

InputEventReceiver的构造方法实现,如下:

public InputEventReceiver(InputChannel inputChannel, Looper looper) {......mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);}

该方法的初始化使用了App自己的inputChannel通道和App的Looper消息队列,然后调用JNI方法完成最终的初始化,如下所示:

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);if (inputChannel == NULL) {jniThrowRuntimeException(env, "InputChannel is not initialized.");return 0;}sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == NULL) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);status_t status = receiver->initialize();if (status) {String8 message;message.appendFormat("Failed to initialize input event receiver.  status=%d", status);jniThrowRuntimeException(env, message.string());return 0;}receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_cast<jlong>(receiver.get());
}

nativeInit方法主要完成如下的逻辑:

1、找到java层的InputChannel对象对应的Native层的C++对象inputChannel;

2、找到java层的messageQueue对象对应的Native层的C++对象messageQueue;

3、调用NativeInputEventReceiver的initialize完成初始化;

我们继续讲解NativeInputEventReceiver的实现,如下所示:

class NativeInputEventReceiver : public LooperCallback {
public:NativeInputEventReceiver(JNIEnv* env,jobject receiverWeak, const sp<InputChannel>& inputChannel,const sp<MessageQueue>& messageQueue);status_t initialize();......virtual int handleEvent(int receiveFd, int events, void* data);
};

NativeInputEventReceiver继承LooperCallback, 并重载实现了handleEvent。我们这里重点提及到LooperCallback,是因为App获取Input输入事件是通过监控inputChannel通道,并通过LooperCallback实现输入事件的获取,监控代码如下所示:

void NativeInputEventReceiver::setFdEvents(int events) {if (mFdEvents != events) {mFdEvents = events;int fd = mInputConsumer.getChannel()->getFd();if (events) {mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);} else {mMessageQueue->getLooper()->removeFd(fd);}}
}

该接口的实现,我加入了对ALOOPER_EVENT_INPUT事件类型的监控,InputDispatcher上报的输入事件会触发ALOOPER_EVENT_INPUT事件,并最终调用到NativeInputEventReceiver中的handleEvent方法。关于Looper的实现,我们这里不详细展开,感兴趣的同学可以,自行去阅读AOSP源码。

App Input输入事件的获取

当InputDispatcher上报事件时,将输入事件写入InputChannel后,最终会触发NativeInputEventReceiver中的handleEvent执行,如下所示:

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {......if (events & ALOOPER_EVENT_INPUT) {JNIEnv* env = AndroidRuntime::getJNIEnv();status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");return status == OK || status == NO_MEMORY ? 1 : 0;}......return 1;
}

我们只保留了InputDispatcher上报事件时的触发回调代码,然后继续调用consumeEvents接口,如下所示:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {......for (;;) {uint32_t seq;InputEvent* inputEvent;status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);......if (!skipCallbacks) {......switch (inputEvent->getType()) {case AINPUT_EVENT_TYPE_KEY:if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received key event.", getInputChannelName());}inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));break;case AINPUT_EVENT_TYPE_MOTION: {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());}MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);break;}default:assert(false); // InputConsumer should prevent this from ever happeninginputEventObj = NULL;}if (inputEventObj) {env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);} else {ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());skipCallbacks = true;}}......}
}

consumeEvents实现比较复杂,涉及到的逻辑比较多,为了简化描述,我们只保留首次上报输入事件的处理,主要实现逻辑如下所示:

1、mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent);获取事件;

2、根据不同的事件类型,封装为一个inputEventObj对象;

3、调用InputEventReceiver的java方法env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);正式开始向App上报输入事件;

调用InputEventReceiver的dispatchInputEvent如下所示:

// Called from native code.@SuppressWarnings("unused")private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}

最终调用到了 WindowInputEventReceiver子类的onInputEvent方法,如下所示:

 final class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {enqueueInputEvent(event, this, 0, true);}......}

继续调用enqueueInputEvent,如下所示:

void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {adjustInputEventForCompatibility(event);QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);......QueuedInputEvent last = mPendingInputEventTail;if (last == null) {mPendingInputEventHead = q;mPendingInputEventTail = q;} else {last.mNext = q;mPendingInputEventTail = q;}mPendingInputEventCount += 1;Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,mPendingInputEventCount);if (processImmediately) {doProcessInputEvents();} else {scheduleProcessInputEvents();}}

1、将上报的输入事件加入到处理队列中;

2、调用doProcessInputEvents立即开始App对输入事件的处理,我们在后续文章进行讲解。这里不再叙述。

总结

本文讲述了App通过注册输入事件接收器的方式,当InputDispacther上报输入事件时,通过Looper的回调机制App获取到了事件,并通过doProcessInputEvents接口,正式开启App对input事件的处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/83826.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS2017编译librdkafka 2.1.0

VS2017编译librdkafka 2.1.0 本篇是 Windows系统编译Qt使用的kafka(librdkafka)系列中的其中一篇,编译librdkafka整体步骤大家可以参考: Windows系统编译Qt使用的kafka(librdkafka) 由于项目需要,使用kafka,故自己编译了一次,编译的过程,踩了太多的坑了,特写了本篇…

第 1 章:数字 I/O 与串口通信(GPIO UART)

本章目标: 掌握 GPIO 的硬件原理、寄存器配置与典型驱动框架 深入理解 UART/USART 的帧格式、波特率配置、中断与 DMA 驱动 通过实战案例,将 GPIO 与 UART 结合,实现 AT 命令式外设控制 章节结构 GPIO 概述与硬件原理 GPIO 驱动实现:寄存器、中断与去抖 UART/USART 原理与帧…

通义千问-langchain使用构建(三)

目录 序言docker 部署xinference1WSL环境docker安装2拉取镜像运行容器3使用的界面 本地跑chatchat1rag踩坑2使用的界面2.1配置个前置条件然后对话2.2rag对话 结论 序言 在前两天的基础上&#xff0c;将xinference调整为wsl环境&#xff0c;docker部署。 然后langchain chatcha…

winfrom中创建webapi

参照一下两篇 Winform窗体利用WebApi接口实现ModbusTCP数据服务_winform webapi-CSDN博客 C#.NET WebApi返回各种类型(图片/json数据/字符串)&#xff0c;.net图片转二进制流或byte - 冰封的心 - 博客园

文本分类任务Qwen3-0.6B与Bert:实验见解

文本分类任务Qwen3-0.6B与Bert&#xff1a;实验见解 前言 最近在知乎上刷到一个很有意思的提问Qwen3-0.6B这种小模型有什么实际意义和用途。查看了所有回答&#xff0c;有人提到小尺寸模型在边缘设备场景中的优势&#xff08;低延迟&#xff09;、也有人提出小模型只是为了开…

前端获取用户的公网 IP 地址

可以使用免费的免费的公共服务网站 一&#xff1a;https://www.ipify.org/ 获取 JSON 格式的 IP 地址 // 旧地址不好使 // https://api.ipify.org/?formatjson // 新地址 https://api64.ipify.org/?formatjson 二&#xff1a;https://ipinfo.io/ https://ipinfo.io/ 三&a…

12.vue整合springboot首页显示数据库表-实现按钮:【添加修改删除查询】

vue整合springboot首页显示数据库表&#xff1a;【添加修改删除查询】 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系…

LLM笔记(九)KV缓存(2)

文章目录 1. 背景与动机2. 不使用 KV Cache 的情形2.1 矩阵形式展开2.2 计算复杂度 3. 使用 KV Cache 的优化3.1 核心思想3.2 矩阵形式展开3.3 计算复杂度对比 4. 总结5. GPT-2 中 KV 缓存的实现分析5.1 缓存的数据结构与类型5.2 在注意力机制 (GPT2Attention) 中使用缓存5.3 缓…

2025年渗透测试面试题总结-各厂商二面试题02(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 各厂商二面试题02 模块六&#xff1a;基础技术扩展 1. HTTP请求方式 2. 域名解析工具与技术 3. Web十…

专业漏洞扫描机构如何助力企业保障安全并提升竞争力?

在这个信息化的当下&#xff0c;专业漏洞扫描机构扮演着至关重要的角色。他们运用专业的技术和手段&#xff0c;对各种软件和系统进行细致的漏洞检测&#xff0c;确保其安全可靠&#xff0c;同时帮助企业提高产品质量和市场竞争力。 服务项目 我们专注于向客户供应周到详尽的…

卷积神经网络中的二维卷积与三维卷积详解

【内容摘要】 本文聚焦卷积神经网络中的二维卷积与三维卷积&#xff0c;详细解析两者的区别、操作原理及应用场景&#xff0c;涵盖二维/三维卷积操作示意图、多通道输入处理方式&#xff0c;以及RGB图像不采用三维卷积的原因&#xff0c;助力理解不同卷积类型的特性与适用场景。…

Oracle 的 ASSM 表空间

Oracle 的 ASSM&#xff08;Automatic Segment Space Management&#xff09;表空间 是一种自动管理段空间的技术&#xff0c;通过位图&#xff08;Bitmap&#xff09;机制跟踪数据块的使用情况&#xff0c;替代传统的手动管理&#xff08;MSSM&#xff0c;即 Freelist 管理&am…

蚂蚁金服大数据面经及参考答案

Java 如何保证跨平台性?请从 JVM 底层适配机制及向上提供的统一接口角度说明 Java 的跨平台性是其核心优势之一,依赖于 JVM(Java Virtual Machine)的底层适配机制和向上层提供的统一接口。从底层来看,JVM 针对不同操作系统和硬件平台进行了定制化实现,负责解析和执行 Ja…

P1009 [NOIP 1998 普及组] 阶乘之和

题目描述 用高精度计算出 S1!2!3!⋯n!&#xff08;n≤50&#xff09;。 其中 ! 表示阶乘&#xff0c;定义为 n!n(n−1)(n−2)⋯1。例如&#xff0c;5!54321120。 输入格式 一个正整数 n。 输出格式 一个正整数 S&#xff0c;表示计算结果。 输入输出样例 输入 3 输出…

Python 的 os 库常见使用方法(操作目录及文件)

前言&#xff1a; os 模块是 Python 标准库中用于与操作系统交互的核心模块&#xff0c;提供了许多操作文件和目录的功能。以下是常见的使用方法&#xff1a; 1. 目录操作 方法功能说明示例os.getcwd()获取当前工作目录print(os.getcwd())os.chdir(path)切换当前工作目录os.ch…

vue3 el-table实现字段可编辑

在Vue 3中&#xff0c;如果你想让el-table&#xff08;Element Plus的表格组件&#xff09;的字段可编辑&#xff0c;你可以通过以下方式来实现&#xff1a; 使用cell-mouse-enter和cell-mouse-leave事件动态显示编辑图标或控件 你可以在鼠标进入单元格时显示一个编辑图标或输…

基于shardingsphere的分库分表方案

一、准备docker容器 启动两个mysql的docker容器 docker run -v /root/mysql_volume/data:/var/lib/mysql -v /root/mysql_volume/conf:/etc/mysql/conf.d -v /root/mysql_volume/my.cnf:/etc/my.cnf -p 3306:3306 --name mysql --restartalways --privilegedtrue -e MYSQL_RO…

SearxNG本地搜索引擎

SearxNG 是一个强大、开源的 元搜索引擎(meta search engine),它不会存储用户信息,注重隐私保护,并支持从多个搜索引擎聚合结果,用户可以自建部署,打造一个无广告、可定制的搜索平台。 🔍 什么是 SearxNG? SearxNG 是 Searx 的一个积极维护的分支(fork),意在改进…

Vue3.5 企业级管理系统实战(十九):菜单管理

篇幅原因&#xff0c;本节先探讨菜单管理页面增删改查相关功能&#xff0c;角色菜单&#xff0c;菜单权限&#xff0c;动态菜单等内容放在后面。 1 菜单 api 在 src/api/menu.ts 中添加菜单 api&#xff0c;代码如下&#xff1a; //src/api/menu.ts import service from &qu…

【android bluetooth 协议分析 01】【HCI 层介绍 8】【ReadLocalVersionInformation命令介绍】

1. HCI_Read_Local_Version_Information 命令介绍 1. 功能&#xff08;Description&#xff09; HCI_Read_Local_Version_Information 命令用于读取本地 Bluetooth Controller 的版本信息&#xff0c;包括 HCI 和 LMP 层的版本&#xff0c;以及厂商 ID 和子版本号。 这类信息用…